# Directory Structure ``` ├── .gitignore ├── .prettierignore ├── .prettierrc ├── example.json ├── example.yaml ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ build/ .env* ``` -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- ``` node_modules build dist package-lock.json yarn.lock ``` -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- ``` { "semi": true, "singleQuote": true, "trailingComma": "all", "printWidth": 100, "tabWidth": 2, "arrowParens": "always" } ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # OpenAPI MCP Server A Quick Way to Create an MCP Server with `StreamableHTTP` transport implementation from OpenAPI docs. ## Overview This server provides a standardized way to interact with model services through a RESTful API interface. It implements the Model Context Protocol (MCP) and is designed to be easily configurable. **Simply set up your `.env` file, and the server is ready to run.** It implements the Model Context Protocol (MCP) specification and supports OpenAPI documentation. ## Features - OpenAPI 3.0.0 compliant API documentation - Model service API documentation retrieval - Model service invocation with parameter handling - TypeScript implementation for type safety ## Prerequisites - Node.js (v20 or higher) - npm (v6 or higher) ## Quick Start 1. **Clone the repository:** ```bash git clone https://github.com/oneWalker/openapi-mcp-server.git cd openapi-mcp-server ``` 2. **Install dependencies:** ```bash npm install ``` 3. **Configure your environment:** Create a `.env` file in the project root and add your configuration. See the [Configuration](#configuration) section for details. 4. **Run the server:** ```bash npm run build npm run start ``` ## Installation 1. Clone the repository 2. Install dependencies: ```bash npm install ``` ## Development ### Building the Project ```bash npm run build ``` ### Running in Development Mode ```bash npm run watch ``` ### Starting the Server ```bash npm run start ``` ## Configuration Create a `.env` file in the root of the project to configure the server. ``` # The base URL for the original API server BASE_SERVER_URL= https://api.example.com # The path to the OpenAPI specification file (can be a local file or a URL). OPENAPI_PATH=./example.yaml or ./example.json # example.yaml is just for demo # The port for the MCP server to run on PORT=8000 ``` ## API Endpoints ### Get Model Service API Documentation ``` GET /api/model/services/{ID} ``` Retrieves the API documentation for a specific model service. **Parameters:** - `ID` (path, required): Model service ID - `authorization` (header, required): Bearer token for authentication ### Call Model Service ``` POST /api/model/services/ ``` Invokes a specific model service with provided parameters. **Parameters:** - `id` (path, required): Model service ID **Request Body:** ```json { "id": "123" } ``` ## Project Structure ``` openapi-mcp-server/ ├── src/ # Source code ├── build/ # Compiled JavaScript files ├── example.yaml # OpenAPI specification ├── package.json # Project configuration └── tsconfig.json # TypeScript configuration ``` ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. Please see the `CONTRIBUTING.md` file for details on our code of conduct, and the process for submitting pull requests to us. ## Reporting Issues We use GitHub Issues to track public bugs. Report a bug by [opening a new issue](https://github.com/oneWalker/openapi-mcp-server/issues); it's that easy! ## Dependencies - `openapi-mcp-generator`: OpenAPI specification generator - **Note:** This project requires a pending fix from the `openapi-mcp-generator` library. See this [pull request](https://github.com/harsha-iiiv/openapi-mcp-generator/pull/27). - `@modelcontextprotocol/sdk`: MCP SDK for protocol implementation - `express`: Web framework - `dotenv`: Environment variable management - `got`: HTTP client ## Development Dependencies - `TypeScript` - `@types/express` - `@types/node` ## License MIT License - See LICENSE file for details ## Authors - [Brian,Kun Liu](https://github.com/oneWalker) ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "openapi-mcp-server", "version": "0.1.4", "description": "MCP server for OpenAPI", "type": "module", "bin": { "openapi-mcp": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js", "prepublishOnly": "npm run build", "start": "node build/index.js", "format": "pretty-quick --staged", "format.check": "prettier --check .", "format.write": "prettier --write ." }, "keywords": [ "openapi-mcp", "openapi", "mcp", "model-context-protocol", "ai-tools" ], "author": "Brian, Kun Liu", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "dotenv": "^16.4.5", "express": "^5.1.0", "got": "^14.4.7", "openapi-mcp-generator": "^3.1.3", "raw-body": "^3.0.0" }, "devDependencies": { "@types/express": "^5.0.1", "@types/node": "^20.11.24", "prettier": "^3.6.2", "pretty-quick": "^4.2.2", "typescript": "^5.3.3" } } ``` -------------------------------------------------------------------------------- /example.yaml: -------------------------------------------------------------------------------- ```yaml openapi: 3.0.0 info: title: Sample Service API Documentation version: 1.0.0 servers: - url: example.com description: ModelService API Server paths: /api/model/services/{ID}: get: summary: Get model service API documentation description: Retrieves the API documentation for a specific model service parameters: - name: ID in: path required: true schema: type: string description: Model service ID - name: authorization in: header required: true schema: type: string pattern: '^Bearer\s[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$' description: Authorization token # - name: model_id # in: query # required: true # schema: # type: string # description: Model ID # requestBody: # required: true # content: # application/json: # schema: # type: object # properties: # model_version: # type: string # description: Model version responses: '200': description: Successful response content: application/json: schema: type: object properties: Descriptions: type: array items: type: object properties: Title: type: string description: Title of the API documentation section Content: type: string description: Content of the API documentation section /api/model/services/: post: summary: Call model service description: Invokes a specific model service with provided parameters parameters: - name: id in: path required: true schema: type: string description: Model service ID requestBody: required: true content: application/json: schema: type: object properties: content: type: object properties: input: type: object description: Input parameters for the model service responses: '200': description: Successful response content: application/json: schema: type: object properties: output: type: string description: Output from the model service time: type: number description: Processing time in milliseconds ``` -------------------------------------------------------------------------------- /example.json: -------------------------------------------------------------------------------- ```json { "openapi": "3.0.0", "info": { "title": "Sample Service API Documentation", "version": "1.0.0" }, "servers": [ { "url": "example.com", "description": "ModelService API Server" } ], "paths": { "/api/model/services/{ID}": { "get": { "summary": "Get model service API documentation", "description": "Retrieves the API documentation for a specific model service", "parameters": [ { "name": "ID", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Model service ID" } ], "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "Descriptions": { "type": "array", "items": { "type": "object", "properties": { "Title": { "type": "string", "description": "Title of the API documentation section" }, "Content": { "type": "string", "description": "Content of the API documentation section" } } } } } } } } } } } }, "/api/model/services/{id}": { "post": { "summary": "Call model service", "description": "Invokes a specific model service with provided parameters", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Model service ID" } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "content": { "type": "object", "properties": { "input": { "type": "object", "description": "Input parameters for the model service" } } } } } } } }, "responses": { "200": { "description": "Successful response", "content": { "application/json": { "schema": { "type": "object", "properties": { "output": { "type": "string", "description": "Output from the model service" }, "time": { "type": "number", "description": "Processing time in milliseconds" } } } } } } } } } } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import express, { Request, Response } from 'express'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { getToolsFromOpenApi, McpToolDefinition, GetToolsOptions } from 'openapi-mcp-generator'; import dotenv from 'dotenv'; import got, { OptionsInit as GotOptionsInit, Method } from 'got'; dotenv.config(); const baseServerUrl = process.env.BASE_SERVER_URL; const openapiPath = process.env.OPENAPI_PATH; if (!baseServerUrl || !openapiPath) { console.error('BASE_SERVER_URL and OPENAPI_PATH must be set'); process.exit(1); } interface OpenAPITool extends McpToolDefinition { function?: (args: any) => Promise<any>; } class OpenAPIClient { private server: Server; private tools: OpenAPITool[] = []; constructor() { this.server = new Server( { name: 'openapi-mcp', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, }, ); this.setupHandlers(); this.setupErrorHandling(); } private setupErrorHandling(): void { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupHandlers(): void { this.setupToolHandlers(); } private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.tools }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const tool = this.tools.find((t) => t.name === request.params.name); if (!tool) { throw new McpError(ErrorCode.InvalidRequest, `Tool ${request.params.name} not found`); } const args = request.params.arguments ?? {}; const jsonBody = args.requestBody; const headers: Record<string, string> = {}; const searchParams: Record<string, string> = {}; let path = tool.pathTemplate as string; (tool.parameters as any[]).forEach((param: any) => { switch (param.in) { case 'path': //replace the path with the args, matched `{param.name}` or ':id' path = path.replace(`{${param.name}}`, args[param.name] as string); path = path.replace(`:${param.name}`, args[param.name] as string); break; case 'query': searchParams[param.name] = args[param.name] as string; break; case 'header': headers[param.name] = args[param.name] as string; break; default: console.error('Unknown parameter type:', param.in); break; } }); // Call the tool function with the provided arguments const result = await tool.function?.({ path, headers, searchParams: new URLSearchParams(searchParams), jsonBody, }); const resultText = typeof result === 'object' ? JSON.stringify(result, null, 2) : result; return { content: [ { type: 'text', text: resultText, }, ], }; } catch (error: any) { console.error(`Error executing tool ${request.params.name}:`, error); throw new McpError(ErrorCode.InternalError, `Failed to execute tool: ${error.message}`); } }); } private async loadTools(): Promise<void> { try { const config: GetToolsOptions = { baseUrl: baseServerUrl, dereference: true, excludeOperationIds: [], filterFn: (tool: McpToolDefinition) => true, }; const rawTools = await getToolsFromOpenApi(openapiPath as string, config); // Transform the tools to include HTTP request functionality this.tools = rawTools.map((tool: OpenAPITool) => ({ ...tool, function: async (args: { path: string; headers: Record<string, string>; searchParams: Record<string, string>; jsonBody: Record<string, any>; }) => { const { path, headers, searchParams, jsonBody } = args; const method = tool.method.toLowerCase() as Method; const url = new URL(path, config.baseUrl); try { const gotOptions: GotOptionsInit = { method, headers, searchParams, }; if (method !== 'get') { gotOptions.json = jsonBody; } const response = await got(url, gotOptions); return (response as any).body; } catch (error: any) { console.error(`HTTP request failed for ${tool.name}:`, error); throw new McpError(ErrorCode.InternalError, `HTTP request failed: ${error.message}`); } }, })) as OpenAPITool[]; } catch (error) { console.error('Error loading tools from OpenAPI:', error); throw error; } } async run(): Promise<void> { await this.loadTools(); const app = express(); app.use(express.json()); app.post('/mcp', async (req: Request, res: Response) => { // In stateless mode, create a new instance of transport and server for each request // to ensure complete isolation. A single instance would cause request ID collisions // when multiple clients connect concurrently. try { const client = new OpenAPIClient(); const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); res.on('close', () => { console.log('Request closed'); transport.close(); client.server.close(); }); await client.server.connect(transport); const authHeader = req.headers.authorization; const token = authHeader?.split(' ')[1]; if (!req.body.params) { req.body.params = {}; } req.body.params.context = { token }; await transport.handleRequest(req, res, req.body); } catch (error) { console.error('Error handling MCP request:', error); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', error: { code: -32603, message: 'Internal server error', }, id: null, }); } } }); const port = process.env.PORT || 8000; console.error('OpenAPI MCP server running on http, port:', port); app.listen(port); // const transport = new StdioServerTransport(); // await this.server.connect(transport); // console.error("Tavily MCP server running on stdio"); } } // Start the server const client = new OpenAPIClient(); client.run().catch((error) => { console.error('Failed to run server:', error); process.exit(1); }); ```