#
tokens: 4452/50000 9/9 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
});

```