import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { z } from "zod"; import { generateTypesFromJsonSchema, type JsonSchemaToolDescriptors } from "./json-schema-types"; import { sanitizeToolName } from "./utils"; import type { Executor } from "./executor"; import type { JSONSchema7 } from "json-schema"; // -- Shared utilities -- const CHARS_PER_TOKEN = 4; const MAX_TOKENS = 6000; const MAX_CHARS = MAX_TOKENS * CHARS_PER_TOKEN; function truncateResponse(content: unknown): string { const text = typeof content === "string" ? content : (JSON.stringify(content, null, 2) ?? "undefined"); if (text.length <= MAX_CHARS) { return text; } const truncated = text.slice(0, MAX_CHARS); const estimatedTokens = Math.ceil(text.length / CHARS_PER_TOKEN); return `${truncated}\n\n--- TRUNCATED ---\nResponse was ~${estimatedTokens.toLocaleString()} tokens (limit: ${MAX_TOKENS.toLocaleString()}). Use more specific queries to reduce response size.`; } function formatError(error: unknown): string { return error instanceof Error ? error.message : String(error); } // -- codeMcpServer -- const CODE_DESCRIPTION = `Execute code to achieve a goal. Available: {{types}} Write an async arrow function in JavaScript that returns the result. Do NOT use TypeScript syntax — no type annotations, interfaces, or generics. Do NOT define named functions then call them — just write the arrow function body directly. {{example}}`; /** * Wrap an existing MCP server with a single codemode `code` tool. * * Connects to the upstream server via in-memory transport, discovers its * tools, and returns a new MCP server with a `code` tool that exposes * all upstream tools as typed methods. */ export interface CodeMcpServerOptions { server: McpServer; executor: Executor; } export async function codeMcpServer( options: CodeMcpServerOptions ): Promise { const { server, executor } = options; const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); await server.connect(serverTransport); const client = new Client({ name: "codemode-proxy", version: "1.0.0" }); await client.connect(clientTransport); const { tools } = await client.listTools(); // Build type hints const toolDescriptors: JsonSchemaToolDescriptors = {}; for (const tool of tools) { toolDescriptors[tool.name] = { description: tool.description, inputSchema: tool.inputSchema as JSONSchema7 }; } const types = generateTypesFromJsonSchema(toolDescriptors); // Build executor fns — each upstream tool is a direct method const fns: Record Promise> = {}; for (const tool of tools) { const toolName = tool.name; fns[toolName] = async (args: unknown) => { const result = await client.callTool({ name: toolName, arguments: args as Record }); return result; }; } // Build example from first upstream tool with placeholder args const firstTool = tools[0]; let example = ""; if (firstTool) { const schema = firstTool.inputSchema as { properties?: Record; required?: string[]; }; const props = schema.properties ?? {}; const parts: string[] = []; for (const [key, prop] of Object.entries(props)) { if (prop.type === "number" || prop.type === "integer") { parts.push(`${key}: 0`); } else if (prop.type === "boolean") { parts.push(`${key}: true`); } else { parts.push(`${key}: "..."`); } } const args = parts.length > 0 ? `{ ${parts.join(", ")} }` : "{}"; example = `Example: async () => { const r = await codemode.${sanitizeToolName(firstTool.name)}(${args}); return r; }`; } const description = CODE_DESCRIPTION.replace("{{types}}", types).replace( "{{example}}", example ); const codemodeServer = new McpServer({ name: "codemode", version: "1.0.0" }); codemodeServer.registerTool( "code", { description, inputSchema: { code: z.string().describe("JavaScript async arrow function to execute") } }, async ({ code }) => { try { const result = await executor.execute(code, [ { name: "codemode", fns } ]); if (result.error) { return { content: [ { type: "text" as const, text: `Error: ${result.error}` } ], isError: true }; } return { content: [ { type: "text" as const, text: truncateResponse(result.result) } ] }; } catch (error) { return { content: [ { type: "text" as const, text: `Error: ${formatError(error)}` } ], isError: true }; } } ); return codemodeServer; } // -- openApiMcpServer -- export interface RequestOptions { method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; path: string; query?: Record; body?: unknown; contentType?: string; rawBody?: boolean; } export interface OpenApiMcpServerOptions { spec: Record; executor: Executor; request: (options: RequestOptions) => Promise; name?: string; version?: string; description?: string; } /** * Resolve internal $ref pointers in a JSON object against the root document. * Only handles `#/` internal refs. External file refs are left as-is. */ function resolveRefs( obj: unknown, root: Record, seen = new Set() ): unknown { if (obj === null || obj === undefined) return obj; if (typeof obj !== "object") return obj; if (Array.isArray(obj)) return obj.map((item) => resolveRefs(item, root, seen)); const record = obj as Record; if ("$ref" in record && typeof record.$ref === "string") { const ref = record.$ref; if (seen.has(ref)) return { $circular: ref }; if (!ref.startsWith("#/")) return record; seen.add(ref); const parts = ref .slice(2) .split("/") .map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~")); let resolved: unknown = root; for (const part of parts) { resolved = (resolved as Record)?.[part]; } const result = resolveRefs(resolved, root, seen); seen.delete(ref); return result; } const result: Record = {}; for (const [key, value] of Object.entries(record)) { result[key] = resolveRefs(value, root, seen); } return result; } const SPEC_TYPES = ` // OpenAPI 3.x spec with $refs resolved inline. // The spec object follows the standard OpenAPI 3.x structure. interface OperationObject { summary?: string; description?: string; operationId?: string; tags?: string[]; parameters?: Array<{ name: string; in: "query" | "header" | "path" | "cookie"; required?: boolean; schema?: unknown; description?: string; }>; requestBody?: { required?: boolean; description?: string; content?: Record; }; responses?: Record; }>; security?: Array>; deprecated?: boolean; } interface PathItem { summary?: string; description?: string; get?: OperationObject; post?: OperationObject; put?: OperationObject; patch?: OperationObject; delete?: OperationObject; head?: OperationObject; options?: OperationObject; trace?: OperationObject; parameters?: OperationObject["parameters"]; } interface OpenApiSpec { openapi: string; info: { title: string; version: string; description?: string }; paths: Record; servers?: Array<{ url: string; description?: string }>; components?: Record; tags?: Array<{ name: string; description?: string }>; } declare const codemode: { spec(): Promise; }; `; const REQUEST_TYPES = ` interface RequestOptions { method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; path: string; query?: Record; body?: unknown; contentType?: string; rawBody?: boolean; } declare const codemode: { request(options: RequestOptions): Promise; }; `; /** * Create an MCP server with search + execute tools from an OpenAPI spec. * * The search tool lets the LLM query the spec to find endpoints. * The execute tool lets the LLM call the API via a user-provided * request function that runs on the host (auth never enters the sandbox). */ export function openApiMcpServer(options: OpenApiMcpServerOptions): McpServer { const { executor, request: requestFn, name = "openapi", version = "1.0.0", description } = options; const resolved = resolveRefs(options.spec, options.spec); const server = new McpServer({ name, version }); // --- search tool --- server.registerTool( "search", { description: `Search the OpenAPI spec. All $refs are pre-resolved inline. Types: ${SPEC_TYPES} Your code must be an async arrow function that returns the result. Examples: // List all paths async () => { const spec = await codemode.spec(); return Object.keys(spec.paths); } // Find endpoints by tag async () => { const spec = await codemode.spec(); const results = []; for (const [path, methods] of Object.entries(spec.paths)) { for (const [method, op] of Object.entries(methods)) { if (op.tags?.some(t => t.toLowerCase() === 'your_tag')) { results.push({ method: method.toUpperCase(), path, summary: op.summary }); } } } return results; }`, inputSchema: { code: z .string() .describe("JavaScript async arrow function to search the spec") } }, async ({ code }) => { try { const result = await executor.execute(code, [ { name: "codemode", fns: { spec: async () => resolved } } ]); if (result.error) { return { content: [ { type: "text" as const, text: `Error: ${result.error}` } ], isError: true }; } return { content: [ { type: "text" as const, text: truncateResponse(result.result) } ] }; } catch (error) { return { content: [ { type: "text" as const, text: `Error: ${formatError(error)}` } ], isError: true }; } } ); // --- execute tool --- const executeDescription = `Execute API calls using JavaScript code. First use 'search' to find the right endpoints. Available in your code: ${REQUEST_TYPES} Your code must be an async arrow function that returns the result. Example: async () => { return await codemode.request({ method: "GET", path: "/your/endpoint" }); }${description ? `\n\n${description}` : ""}`; server.registerTool( "execute", { description: executeDescription, inputSchema: { code: z.string().describe("JavaScript async arrow function to execute") } }, async ({ code }) => { try { const result = await executor.execute(code, [ { name: "codemode", fns: { request: (args: unknown) => requestFn(args as RequestOptions) } } ]); if (result.error) { return { content: [ { type: "text" as const, text: `Error: ${result.error}` } ], isError: true }; } return { content: [ { type: "text" as const, text: truncateResponse(result.result) } ] }; } catch (error) { return { content: [ { type: "text" as const, text: `Error: ${formatError(error)}` } ], isError: true }; } } ); return server; }