UNPKG

openapi-mcp-generator

Version:

Generates MCP server code from OpenAPI specifications

223 lines (207 loc) 7.29 kB
import { extractToolsFromApi } from '../parser/extract-tools.js'; import { determineBaseUrl } from '../utils/index.js'; import { generateToolDefinitionMap, generateCallToolHandler, generateListToolsHandler, } from '../utils/code-gen.js'; import { generateExecuteApiToolFunction } from '../utils/security.js'; /** * Generates the TypeScript code for the MCP server * * @param api OpenAPI document * @param options CLI options * @param serverName Server name * @param serverVersion Server version * @returns Generated TypeScript code */ export function generateMcpServerCode(api, options, serverName, serverVersion) { // Extract tools from API const tools = extractToolsFromApi(api, options.defaultInclude ?? true); // Determine base URL const determinedBaseUrl = determineBaseUrl(api, options.baseUrl); // Generate code for tool definition map const toolDefinitionMapCode = generateToolDefinitionMap(tools, api.components?.securitySchemes); // Generate code for API tool execution const executeApiToolFunctionCode = generateExecuteApiToolFunction(api.components?.securitySchemes); // Generate code for request handlers const callToolHandlerCode = generateCallToolHandler(); const listToolsHandlerCode = generateListToolsHandler(); // Determine which transport to include let transportImport = ''; let transportCode = ''; switch (options.transport) { case 'web': transportImport = `\nimport { setupWebServer } from "./web-server.js";`; transportCode = `// Set up Web Server transport try { await setupWebServer(server, ${options.port || 3000}); } catch (error) { console.error("Error setting up web server:", error); process.exit(1); }`; break; case 'streamable-http': transportImport = `\nimport { setupStreamableHttpServer } from "./streamable-http.js";`; transportCode = `// Set up StreamableHTTP transport try { await setupStreamableHttpServer(server, ${options.port || 3000}); } catch (error) { console.error("Error setting up StreamableHTTP server:", error); process.exit(1); }`; break; default: // stdio transportImport = ''; transportCode = `// Set up stdio transport try { const transport = new StdioServerTransport(); await server.connect(transport); console.error(\`\${SERVER_NAME} MCP Server (v\${SERVER_VERSION}) running on stdio\${API_BASE_URL ? \`, proxying API at \${API_BASE_URL}\` : ''}\`); } catch (error) { console.error("Error during server startup:", error); process.exit(1); }`; break; } // Generate the full server code return `#!/usr/bin/env node /** * MCP Server generated from OpenAPI spec for ${serverName} v${serverVersion} * Generated on: ${new Date().toISOString()} */ // Load environment variables from .env file import dotenv from 'dotenv'; dotenv.config(); import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, type Tool, type CallToolResult, type CallToolRequest } from "@modelcontextprotocol/sdk/types.js";${transportImport} import { z, ZodError } from 'zod'; import { jsonSchemaToZod } from 'json-schema-to-zod'; import axios, { type AxiosRequestConfig, type AxiosError } from 'axios'; /** * Type definition for JSON objects */ type JsonObject = Record<string, any>; /** * Interface for MCP Tool Definition */ interface McpToolDefinition { name: string; description: string; inputSchema: any; method: string; pathTemplate: string; executionParameters: { name: string, in: string }[]; requestBodyContentType?: string; securityRequirements: any[]; } /** * Server configuration */ export const SERVER_NAME = "${serverName}"; export const SERVER_VERSION = "${serverVersion}"; export const API_BASE_URL = "${determinedBaseUrl || ''}"; /** * MCP Server instance */ const server = new Server( { name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } } ); /** * Map of tool definitions by name */ const toolDefinitionMap: Map<string, McpToolDefinition> = new Map([ ${toolDefinitionMapCode} ]); /** * Security schemes from the OpenAPI spec */ const securitySchemes = ${JSON.stringify(api.components?.securitySchemes || {}, null, 2).replace(/^/gm, ' ')}; ${listToolsHandlerCode} ${callToolHandlerCode} ${executeApiToolFunctionCode} /** * Main function to start the server */ async function main() { ${transportCode} } /** * Cleanup function for graceful shutdown */ async function cleanup() { console.error("Shutting down MCP server..."); process.exit(0); } // Register signal handlers process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); // Start the server main().catch((error) => { console.error("Fatal error in main execution:", error); process.exit(1); }); /** * Formats API errors for better readability * * @param error Axios error * @returns Formatted error message */ function formatApiError(error: AxiosError): string { let message = 'API request failed.'; if (error.response) { message = \`API Error: Status \${error.response.status} (\${error.response.statusText || 'Status text not available'}). \`; const responseData = error.response.data; const MAX_LEN = 200; if (typeof responseData === 'string') { message += \`Response: \${responseData.substring(0, MAX_LEN)}\${responseData.length > MAX_LEN ? '...' : ''}\`; } else if (responseData) { try { const jsonString = JSON.stringify(responseData); message += \`Response: \${jsonString.substring(0, MAX_LEN)}\${jsonString.length > MAX_LEN ? '...' : ''}\`; } catch { message += 'Response: [Could not serialize data]'; } } else { message += 'No response body received.'; } } else if (error.request) { message = 'API Network Error: No response received from server.'; if (error.code) message += \` (Code: \${error.code})\`; } else { message += \`API Request Setup Error: \${error.message}\`; } return message; } /** * Converts a JSON Schema to a Zod schema for runtime validation * * @param jsonSchema JSON Schema * @param toolName Tool name for error reporting * @returns Zod schema */ function getZodSchemaFromJsonSchema(jsonSchema: any, toolName: string): z.ZodTypeAny { if (typeof jsonSchema !== 'object' || jsonSchema === null) { return z.object({}).passthrough(); } try { const zodSchemaString = jsonSchemaToZod(jsonSchema); const zodSchema = eval(zodSchemaString); if (typeof zodSchema?.parse !== 'function') { throw new Error('Eval did not produce a valid Zod schema.'); } return zodSchema as z.ZodTypeAny; } catch (err: any) { console.error(\`Failed to generate/evaluate Zod schema for '\${toolName}':\`, err); return z.object({}).passthrough(); } } `; } //# sourceMappingURL=server-code.js.map