#
tokens: 6407/50000 9/9 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── .prettierignore
├── .prettierrc
├── example.json
├── example.yaml
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | .env*
4 | 
```

--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------

```
1 | node_modules
2 | build
3 | dist
4 | package-lock.json
5 | yarn.lock 
```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
1 | {
2 |   "semi": true,
3 |   "singleQuote": true,
4 |   "trailingComma": "all",
5 |   "printWidth": 100,
6 |   "tabWidth": 2,
7 |   "arrowParens": "always"
8 | }
9 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # OpenAPI MCP Server
  2 | 
  3 | A Quick Way to Create an MCP Server with `StreamableHTTP` transport implementation from OpenAPI docs.
  4 | 
  5 | ## Overview
  6 | 
  7 | 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.**
  8 | 
  9 | It implements the Model Context Protocol (MCP) specification and supports OpenAPI documentation.
 10 | 
 11 | ## Features
 12 | 
 13 | - OpenAPI 3.0.0 compliant API documentation
 14 | - Model service API documentation retrieval
 15 | - Model service invocation with parameter handling
 16 | - TypeScript implementation for type safety
 17 | 
 18 | ## Prerequisites
 19 | 
 20 | - Node.js (v20 or higher)
 21 | - npm (v6 or higher)
 22 | 
 23 | ## Quick Start
 24 | 
 25 | 1.  **Clone the repository:**
 26 | 
 27 |     ```bash
 28 |     git clone https://github.com/oneWalker/openapi-mcp-server.git
 29 |     cd openapi-mcp-server
 30 |     ```
 31 | 
 32 | 2.  **Install dependencies:**
 33 | 
 34 |     ```bash
 35 |     npm install
 36 |     ```
 37 | 
 38 | 3.  **Configure your environment:**
 39 |     Create a `.env` file in the project root and add your configuration. See the [Configuration](#configuration) section for details.
 40 | 
 41 | 4.  **Run the server:**
 42 |     ```bash
 43 |     npm run build
 44 |     npm run start
 45 |     ```
 46 | 
 47 | ## Installation
 48 | 
 49 | 1. Clone the repository
 50 | 2. Install dependencies:
 51 | 
 52 | ```bash
 53 | npm install
 54 | ```
 55 | 
 56 | ## Development
 57 | 
 58 | ### Building the Project
 59 | 
 60 | ```bash
 61 | npm run build
 62 | ```
 63 | 
 64 | ### Running in Development Mode
 65 | 
 66 | ```bash
 67 | npm run watch
 68 | ```
 69 | 
 70 | ### Starting the Server
 71 | 
 72 | ```bash
 73 | npm  run start
 74 | ```
 75 | 
 76 | ## Configuration
 77 | 
 78 | Create a `.env` file in the root of the project to configure the server.
 79 | 
 80 | ```
 81 | # The base URL for the original API server
 82 | BASE_SERVER_URL= https://api.example.com
 83 | 
 84 | # The path to the OpenAPI specification file (can be a local file or a URL).
 85 | OPENAPI_PATH=./example.yaml or ./example.json # example.yaml is just for demo
 86 | 
 87 | # The port for the MCP server to run on
 88 | PORT=8000
 89 | ```
 90 | 
 91 | ## API Endpoints
 92 | 
 93 | ### Get Model Service API Documentation
 94 | 
 95 | ```
 96 | GET /api/model/services/{ID}
 97 | ```
 98 | 
 99 | Retrieves the API documentation for a specific model service.
100 | 
101 | **Parameters:**
102 | 
103 | - `ID` (path, required): Model service ID
104 | - `authorization` (header, required): Bearer token for authentication
105 | 
106 | ### Call Model Service
107 | 
108 | ```
109 | POST /api/model/services/
110 | ```
111 | 
112 | Invokes a specific model service with provided parameters.
113 | 
114 | **Parameters:**
115 | 
116 | - `id` (path, required): Model service ID
117 | 
118 | **Request Body:**
119 | 
120 | ```json
121 | {
122 |   "id": "123"
123 | }
124 | ```
125 | 
126 | ## Project Structure
127 | 
128 | ```
129 | openapi-mcp-server/
130 | ├── src/              # Source code
131 | ├── build/            # Compiled JavaScript files
132 | ├── example.yaml # OpenAPI specification
133 | ├── package.json      # Project configuration
134 | └── tsconfig.json     # TypeScript configuration
135 | ```
136 | 
137 | ## Contributing
138 | 
139 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
140 | 
141 | Please see the `CONTRIBUTING.md` file for details on our code of conduct, and the process for submitting pull requests to us.
142 | 
143 | ## Reporting Issues
144 | 
145 | 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!
146 | 
147 | ## Dependencies
148 | 
149 | - `openapi-mcp-generator`: OpenAPI specification generator
150 |   - **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).
151 | - `@modelcontextprotocol/sdk`: MCP SDK for protocol implementation
152 | - `express`: Web framework
153 | - `dotenv`: Environment variable management
154 | - `got`: HTTP client
155 | 
156 | ## Development Dependencies
157 | 
158 | - `TypeScript`
159 | - `@types/express`
160 | - `@types/node`
161 | 
162 | ## License
163 | 
164 | MIT License - See LICENSE file for details
165 | 
166 | ## Authors
167 | 
168 | - [Brian,Kun Liu](https://github.com/oneWalker)
169 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "openapi-mcp-server",
 3 |   "version": "0.1.4",
 4 |   "description": "MCP server for OpenAPI",
 5 |   "type": "module",
 6 |   "bin": {
 7 |     "openapi-mcp": "./build/index.js"
 8 |   },
 9 |   "files": [
10 |     "build"
11 |   ],
12 |   "scripts": {
13 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
14 |     "prepare": "npm run build",
15 |     "watch": "tsc --watch",
16 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js",
17 |     "prepublishOnly": "npm run build",
18 |     "start": "node build/index.js",
19 |     "format": "pretty-quick --staged",
20 |     "format.check": "prettier --check .",
21 |     "format.write": "prettier --write ."
22 |   },
23 |   "keywords": [
24 |     "openapi-mcp",
25 |     "openapi",
26 |     "mcp",
27 |     "model-context-protocol",
28 |     "ai-tools"
29 |   ],
30 |   "author": "Brian, Kun Liu",
31 |   "license": "MIT",
32 |   "dependencies": {
33 |     "@modelcontextprotocol/sdk": "^1.12.1",
34 |     "dotenv": "^16.4.5",
35 |     "express": "^5.1.0",
36 |     "got": "^14.4.7",
37 |     "openapi-mcp-generator": "^3.1.3",
38 |     "raw-body": "^3.0.0"
39 |   },
40 |   "devDependencies": {
41 |     "@types/express": "^5.0.1",
42 |     "@types/node": "^20.11.24",
43 |     "prettier": "^3.6.2",
44 |     "pretty-quick": "^4.2.2",
45 |     "typescript": "^5.3.3"
46 |   }
47 | }
48 | 
```

--------------------------------------------------------------------------------
/example.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | openapi: 3.0.0
  2 | info:
  3 |   title: Sample Service API Documentation
  4 |   version: 1.0.0
  5 | servers:
  6 |   - url: example.com
  7 |     description: ModelService API Server
  8 | 
  9 | paths:
 10 |   /api/model/services/{ID}:
 11 |     get:
 12 |       summary: Get model service API documentation
 13 |       description: Retrieves the API documentation for a specific model service
 14 |       parameters:
 15 |         - name: ID
 16 |           in: path
 17 |           required: true
 18 |           schema:
 19 |             type: string
 20 |           description: Model service ID
 21 |         - name: authorization
 22 |           in: header
 23 |           required: true
 24 |           schema:
 25 |             type: string
 26 |             pattern: '^Bearer\s[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$'
 27 |           description: Authorization token
 28 |         # - name: model_id
 29 |         #   in: query
 30 |         #   required: true
 31 |         #   schema:
 32 |         #     type: string
 33 |         #   description: Model ID
 34 |       # requestBody:
 35 |       #   required: true
 36 |       #   content:
 37 |       #     application/json:
 38 |       #       schema:
 39 |       #         type: object
 40 |       #         properties:
 41 |       #           model_version:
 42 |       #             type: string
 43 |       #             description: Model version
 44 |       responses:
 45 |         '200':
 46 |           description: Successful response
 47 |           content:
 48 |             application/json:
 49 |               schema:
 50 |                 type: object
 51 |                 properties:
 52 |                   Descriptions:
 53 |                     type: array
 54 |                     items:
 55 |                       type: object
 56 |                       properties:
 57 |                         Title:
 58 |                           type: string
 59 |                           description: Title of the API documentation section
 60 |                         Content:
 61 |                           type: string
 62 |                           description: Content of the API documentation section
 63 |   /api/model/services/:
 64 |     post:
 65 |       summary: Call model service
 66 |       description: Invokes a specific model service with provided parameters
 67 |       parameters:
 68 |         - name: id
 69 |           in: path
 70 |           required: true
 71 |           schema:
 72 |             type: string
 73 |           description: Model service ID
 74 |       requestBody:
 75 |         required: true
 76 |         content:
 77 |           application/json:
 78 |             schema:
 79 |               type: object
 80 |               properties:
 81 |                 content:
 82 |                   type: object
 83 |                   properties:
 84 |                     input:
 85 |                       type: object
 86 |                       description: Input parameters for the model service
 87 |       responses:
 88 |         '200':
 89 |           description: Successful response
 90 |           content:
 91 |             application/json:
 92 |               schema:
 93 |                 type: object
 94 |                 properties:
 95 |                   output:
 96 |                     type: string
 97 |                     description: Output from the model service
 98 |                   time:
 99 |                     type: number
100 |                     description: Processing time in milliseconds
101 | 
```

--------------------------------------------------------------------------------
/example.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "openapi": "3.0.0",
  3 |   "info": {
  4 |     "title": "Sample Service API Documentation",
  5 |     "version": "1.0.0"
  6 |   },
  7 |   "servers": [
  8 |     {
  9 |       "url": "example.com",
 10 |       "description": "ModelService API Server"
 11 |     }
 12 |   ],
 13 |   "paths": {
 14 |     "/api/model/services/{ID}": {
 15 |       "get": {
 16 |         "summary": "Get model service API documentation",
 17 |         "description": "Retrieves the API documentation for a specific model service",
 18 |         "parameters": [
 19 |           {
 20 |             "name": "ID",
 21 |             "in": "path",
 22 |             "required": true,
 23 |             "schema": {
 24 |               "type": "string"
 25 |             },
 26 |             "description": "Model service ID"
 27 |           }
 28 |         ],
 29 |         "responses": {
 30 |           "200": {
 31 |             "description": "Successful response",
 32 |             "content": {
 33 |               "application/json": {
 34 |                 "schema": {
 35 |                   "type": "object",
 36 |                   "properties": {
 37 |                     "Descriptions": {
 38 |                       "type": "array",
 39 |                       "items": {
 40 |                         "type": "object",
 41 |                         "properties": {
 42 |                           "Title": {
 43 |                             "type": "string",
 44 |                             "description": "Title of the API documentation section"
 45 |                           },
 46 |                           "Content": {
 47 |                             "type": "string",
 48 |                             "description": "Content of the API documentation section"
 49 |                           }
 50 |                         }
 51 |                       }
 52 |                     }
 53 |                   }
 54 |                 }
 55 |               }
 56 |             }
 57 |           }
 58 |         }
 59 |       }
 60 |     },
 61 |     "/api/model/services/{id}": {
 62 |       "post": {
 63 |         "summary": "Call model service",
 64 |         "description": "Invokes a specific model service with provided parameters",
 65 |         "parameters": [
 66 |           {
 67 |             "name": "id",
 68 |             "in": "path",
 69 |             "required": true,
 70 |             "schema": {
 71 |               "type": "string"
 72 |             },
 73 |             "description": "Model service ID"
 74 |           }
 75 |         ],
 76 |         "requestBody": {
 77 |           "required": true,
 78 |           "content": {
 79 |             "application/json": {
 80 |               "schema": {
 81 |                 "type": "object",
 82 |                 "properties": {
 83 |                   "content": {
 84 |                     "type": "object",
 85 |                     "properties": {
 86 |                       "input": {
 87 |                         "type": "object",
 88 |                         "description": "Input parameters for the model service"
 89 |                       }
 90 |                     }
 91 |                   }
 92 |                 }
 93 |               }
 94 |             }
 95 |           }
 96 |         },
 97 |         "responses": {
 98 |           "200": {
 99 |             "description": "Successful response",
100 |             "content": {
101 |               "application/json": {
102 |                 "schema": {
103 |                   "type": "object",
104 |                   "properties": {
105 |                     "output": {
106 |                       "type": "string",
107 |                       "description": "Output from the model service"
108 |                     },
109 |                     "time": {
110 |                       "type": "number",
111 |                       "description": "Processing time in milliseconds"
112 |                     }
113 |                   }
114 |                 }
115 |               }
116 |             }
117 |           }
118 |         }
119 |       }
120 |     }
121 |   }
122 | }
123 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import express, { Request, Response } from 'express';
  4 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ListToolsRequestSchema,
  8 |   Tool,
  9 |   McpError,
 10 |   ErrorCode,
 11 | } from '@modelcontextprotocol/sdk/types.js';
 12 | import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
 13 | import { getToolsFromOpenApi, McpToolDefinition, GetToolsOptions } from 'openapi-mcp-generator';
 14 | import dotenv from 'dotenv';
 15 | import got, { OptionsInit as GotOptionsInit, Method } from 'got';
 16 | 
 17 | dotenv.config();
 18 | 
 19 | const baseServerUrl = process.env.BASE_SERVER_URL;
 20 | const openapiPath = process.env.OPENAPI_PATH;
 21 | 
 22 | if (!baseServerUrl || !openapiPath) {
 23 |   console.error('BASE_SERVER_URL and OPENAPI_PATH must be set');
 24 |   process.exit(1);
 25 | }
 26 | 
 27 | interface OpenAPITool extends McpToolDefinition {
 28 |   function?: (args: any) => Promise<any>;
 29 | }
 30 | 
 31 | class OpenAPIClient {
 32 |   private server: Server;
 33 |   private tools: OpenAPITool[] = [];
 34 | 
 35 |   constructor() {
 36 |     this.server = new Server(
 37 |       {
 38 |         name: 'openapi-mcp',
 39 |         version: '0.1.0',
 40 |       },
 41 |       {
 42 |         capabilities: {
 43 |           resources: {},
 44 |           tools: {},
 45 |           prompts: {},
 46 |         },
 47 |       },
 48 |     );
 49 | 
 50 |     this.setupHandlers();
 51 |     this.setupErrorHandling();
 52 |   }
 53 | 
 54 |   private setupErrorHandling(): void {
 55 |     this.server.onerror = (error) => {
 56 |       console.error('[MCP Error]', error);
 57 |     };
 58 | 
 59 |     process.on('SIGINT', async () => {
 60 |       await this.server.close();
 61 |       process.exit(0);
 62 |     });
 63 |   }
 64 | 
 65 |   private setupHandlers(): void {
 66 |     this.setupToolHandlers();
 67 |   }
 68 | 
 69 |   private setupToolHandlers(): void {
 70 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => {
 71 |       return { tools: this.tools };
 72 |     });
 73 | 
 74 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
 75 |       try {
 76 |         const tool = this.tools.find((t) => t.name === request.params.name);
 77 |         if (!tool) {
 78 |           throw new McpError(ErrorCode.InvalidRequest, `Tool ${request.params.name} not found`);
 79 |         }
 80 | 
 81 |         const args = request.params.arguments ?? {};
 82 | 
 83 |         const jsonBody = args.requestBody;
 84 |         const headers: Record<string, string> = {};
 85 |         const searchParams: Record<string, string> = {};
 86 |         let path = tool.pathTemplate as string;
 87 |         (tool.parameters as any[]).forEach((param: any) => {
 88 |           switch (param.in) {
 89 |             case 'path':
 90 |               //replace the path with the args, matched `{param.name}` or ':id'
 91 |               path = path.replace(`{${param.name}}`, args[param.name] as string);
 92 |               path = path.replace(`:${param.name}`, args[param.name] as string);
 93 |               break;
 94 |             case 'query':
 95 |               searchParams[param.name] = args[param.name] as string;
 96 |               break;
 97 |             case 'header':
 98 |               headers[param.name] = args[param.name] as string;
 99 |               break;
100 |             default:
101 |               console.error('Unknown parameter type:', param.in);
102 |               break;
103 |           }
104 |         });
105 | 
106 |         // Call the tool function with the provided arguments
107 |         const result = await tool.function?.({
108 |           path,
109 |           headers,
110 |           searchParams: new URLSearchParams(searchParams),
111 |           jsonBody,
112 |         });
113 | 
114 |         const resultText = typeof result === 'object' ? JSON.stringify(result, null, 2) : result;
115 | 
116 |         return {
117 |           content: [
118 |             {
119 |               type: 'text',
120 |               text: resultText,
121 |             },
122 |           ],
123 |         };
124 |       } catch (error: any) {
125 |         console.error(`Error executing tool ${request.params.name}:`, error);
126 |         throw new McpError(ErrorCode.InternalError, `Failed to execute tool: ${error.message}`);
127 |       }
128 |     });
129 |   }
130 | 
131 |   private async loadTools(): Promise<void> {
132 |     try {
133 |       const config: GetToolsOptions = {
134 |         baseUrl: baseServerUrl,
135 |         dereference: true,
136 |         excludeOperationIds: [],
137 |         filterFn: (tool: McpToolDefinition) => true,
138 |       };
139 | 
140 |       const rawTools = await getToolsFromOpenApi(openapiPath as string, config);
141 | 
142 |       // Transform the tools to include HTTP request functionality
143 |       this.tools = rawTools.map((tool: OpenAPITool) => ({
144 |         ...tool,
145 |         function: async (args: {
146 |           path: string;
147 |           headers: Record<string, string>;
148 |           searchParams: Record<string, string>;
149 |           jsonBody: Record<string, any>;
150 |         }) => {
151 |           const { path, headers, searchParams, jsonBody } = args;
152 |           const method = tool.method.toLowerCase() as Method;
153 | 
154 |           const url = new URL(path, config.baseUrl);
155 | 
156 |           try {
157 |             const gotOptions: GotOptionsInit = {
158 |               method,
159 |               headers,
160 |               searchParams,
161 |             };
162 |             if (method !== 'get') {
163 |               gotOptions.json = jsonBody;
164 |             }
165 |             const response = await got(url, gotOptions);
166 | 
167 |             return (response as any).body;
168 |           } catch (error: any) {
169 |             console.error(`HTTP request failed for ${tool.name}:`, error);
170 |             throw new McpError(ErrorCode.InternalError, `HTTP request failed: ${error.message}`);
171 |           }
172 |         },
173 |       })) as OpenAPITool[];
174 |     } catch (error) {
175 |       console.error('Error loading tools from OpenAPI:', error);
176 |       throw error;
177 |     }
178 |   }
179 | 
180 |   async run(): Promise<void> {
181 |     await this.loadTools();
182 |     const app = express();
183 |     app.use(express.json());
184 | 
185 |     app.post('/mcp', async (req: Request, res: Response) => {
186 |       // In stateless mode, create a new instance of transport and server for each request
187 |       // to ensure complete isolation. A single instance would cause request ID collisions
188 |       // when multiple clients connect concurrently.
189 | 
190 |       try {
191 |         const client = new OpenAPIClient();
192 |         const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
193 |           sessionIdGenerator: undefined,
194 |         });
195 |         res.on('close', () => {
196 |           console.log('Request closed');
197 |           transport.close();
198 |           client.server.close();
199 |         });
200 |         await client.server.connect(transport);
201 | 
202 |         const authHeader = req.headers.authorization;
203 |         const token = authHeader?.split(' ')[1];
204 |         if (!req.body.params) {
205 |           req.body.params = {};
206 |         }
207 |         req.body.params.context = { token };
208 |         await transport.handleRequest(req, res, req.body);
209 |       } catch (error) {
210 |         console.error('Error handling MCP request:', error);
211 |         if (!res.headersSent) {
212 |           res.status(500).json({
213 |             jsonrpc: '2.0',
214 |             error: {
215 |               code: -32603,
216 |               message: 'Internal server error',
217 |             },
218 |             id: null,
219 |           });
220 |         }
221 |       }
222 |     });
223 | 
224 |     const port = process.env.PORT || 8000;
225 |     console.error('OpenAPI MCP server running on http, port:', port);
226 |     app.listen(port);
227 |     // const transport = new StdioServerTransport();
228 |     // await this.server.connect(transport);
229 |     // console.error("Tavily MCP server running on stdio");
230 |   }
231 | }
232 | 
233 | // Start the server
234 | const client = new OpenAPIClient();
235 | client.run().catch((error) => {
236 |   console.error('Failed to run server:', error);
237 |   process.exit(1);
238 | });
239 | 
```