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

```
├── .env.example
├── .gitignore
├── config.json
├── examples
│   └── swagger-pet-store.json
├── jest.config.js
├── LICENSE
├── package.json
├── README.md
├── src
│   ├── config.ts
│   ├── mcp-server.ts
│   ├── server.ts
│   ├── types
│   │   └── index.ts
│   └── types.ts
├── tests
│   └── test-pets.js
├── tsconfig.json
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
 1 | # Server Configuration
 2 | PORT=3000
 3 | 
 4 | # API Authentication
 5 | API_USERNAME=
 6 | API_PASSWORD=
 7 | API_TOKEN=
 8 | 
 9 | # Default API Configuration
10 | DEFAULT_API_BASE_URL=
11 | DEFAULT_SWAGGER_URL= 
```

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

```
 1 | # Dependencies
 2 | node_modules/
 3 | yarn-debug.log*
 4 | yarn-error.log*
 5 | 
 6 | # Build output
 7 | dist/
 8 | build/
 9 | 
10 | # Environment variables
11 | .env
12 | .env.local
13 | .env.*.local
14 | 
15 | # IDE and editor files
16 | .idea/
17 | .vscode/
18 | *.swp
19 | *.swo
20 | .DS_Store
21 | 
22 | # Test coverage
23 | coverage/
24 | 
25 | # Logs
26 | logs/
27 | *.log
28 | npm-debug.log* 
```

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

```markdown
  1 | # Swagger MCP Server
  2 | 
  3 | A server that ingests and serves Swagger/OpenAPI specifications through the Model Context Protocol (MCP).
  4 | 
  5 | ## Features
  6 | 
  7 | - Loads Swagger/OpenAPI specifications
  8 | - Supports multiple authentication methods:
  9 |   - Basic Auth
 10 |   - Bearer Token
 11 |   - API Key (header or query)
 12 |   - OAuth2
 13 | - Automatically generates MCP tools from API endpoints
 14 | - Server-Sent Events (SSE) support for real-time communication
 15 | - TypeScript support
 16 | 
 17 | ## Security
 18 | 
 19 | This is a personal server!! Do not expose it to the public internet.
 20 | If the underlying API requires authentication, you should not expose the MCP server to the public internet.
 21 | 
 22 | ## TODO
 23 | 
 24 | - secrets - the MCP server should be able to use secrets from the user to authenticate requests to the API
 25 | - Comprehensive test suite
 26 | 
 27 | ## Prerequisites
 28 | 
 29 | - Node.js (v18 or higher)
 30 | - Yarn package manager
 31 | - TypeScript
 32 | 
 33 | ## Installation
 34 | 
 35 | 1. Clone the repository:
 36 | ```bash
 37 | git clone https://github.com/dcolley/swagger-mcp.git
 38 | cd swagger-mcp
 39 | ```
 40 | 
 41 | 2. Install dependencies:
 42 | ```bash
 43 | yarn install
 44 | ```
 45 | 
 46 | 3. Create a `.env` file based on the example:
 47 | ```bash
 48 | cp .env.example .env
 49 | ```
 50 | 
 51 | 4. Configure your Swagger/OpenAPI specification:
 52 |    - Place your Swagger file in the project (e.g., `swagger.json`)
 53 |    - Or provide a URL to your Swagger specification
 54 | 
 55 | 5. Update the configuration in `config.json` with your server settings:
 56 | ```json
 57 | {
 58 |   "server": {
 59 |     "host": "localhost",
 60 |     "port": 3000
 61 |   },
 62 |   "swagger": {
 63 |     "url": "url-or-path/to/your/swagger.json",
 64 |     "apiBaseUrl": "https://api.example.com",  // Fallback if not specified in Swagger
 65 |     "defaultAuth": {  // Fallback if not specified in Swagger
 66 |       "type": "apiKey",
 67 |       "apiKey": "your-api-key",
 68 |       "apiKeyName": "api_key",
 69 |       "apiKeyIn": "header"
 70 |     }
 71 |   }
 72 | }
 73 | ```
 74 | 
 75 | Note: The server prioritizes settings from the Swagger specification over the config file:
 76 | - If the Swagger file contains a `servers` array, the first server URL will be used as the base URL
 77 | - If the Swagger file defines security schemes, they will be used for authentication
 78 | - The config file settings serve as fallbacks when not specified in the Swagger file
 79 | 
 80 | ## Usage
 81 | 
 82 | 1. Start the development server:
 83 | ```bash
 84 | yarn dev
 85 | ```
 86 | 
 87 | 2. Build for production:
 88 | ```bash
 89 | yarn build
 90 | ```
 91 | 
 92 | 3. Start the production server:
 93 | ```bash
 94 | yarn start
 95 | ```
 96 | 
 97 | ## API Endpoints
 98 | 
 99 | - `GET /health` - Check server health status
100 | - `GET /sse` - Establish Server-Sent Events connection
101 | - `POST /messages` - Send messages to the MCP server
102 | 
103 | ## Testing
104 | 
105 | Run the test suite:
106 | ```bash
107 | # Run tests once
108 | yarn test
109 | 
110 | # Run tests in watch mode
111 | yarn test:watch
112 | 
113 | # Run tests with coverage report
114 | yarn test:coverage
115 | ```
116 | 
117 | ## Authentication
118 | 
119 | The server supports various authentication methods. Configure them in the `config.json` file as fallbacks when not specified in the Swagger file:
120 | 
121 | ### Basic Auth
122 | ```json
123 | {
124 |   "defaultAuth": {
125 |     "type": "basic",
126 |     "username": "your-username",
127 |     "password": "your-password"
128 |   }
129 | }
130 | ```
131 | 
132 | ### Bearer Token
133 | ```json
134 | {
135 |   "defaultAuth": {
136 |     "type": "bearer",
137 |     "token": "your-bearer-token"
138 |   }
139 | }
140 | ```
141 | 
142 | ### API Key
143 | ```json
144 | {
145 |   "defaultAuth": {
146 |     "type": "apiKey",
147 |     "apiKey": "your-api-key",
148 |     "apiKeyName": "X-API-Key",
149 |     "apiKeyIn": "header"
150 |   }
151 | }
152 | ```
153 | 
154 | ### OAuth2
155 | ```json
156 | {
157 |   "defaultAuth": {
158 |     "type": "oauth2",
159 |     "token": "your-oauth-token"
160 |   }
161 | }
162 | ```
163 | 
164 | ## Development
165 | 
166 | 1. Start the development server:
167 | ```bash
168 | yarn dev
169 | ```
170 | 
171 | <!-- 2. Make changes to the code
172 | 
173 | 3. Run tests to ensure everything works:
174 | ```bash
175 | yarn test
176 | ```
177 | 
178 | 4. Build the project:
179 | ```bash
180 | yarn build
181 | ``` -->
182 | 
183 | <!-- ## Contributing
184 | 
185 | 1. Fork the repository
186 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
187 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
188 | 4. Push to the branch (`git push origin feature/amazing-feature`)
189 | 5. Open a Pull Request -->
190 | 
191 | ## License
192 | 
193 | This project is licensed under the Apache 2.0 License.
194 | 
195 | ## Environment Variables
196 | 
197 | - `PORT`: Server port (default: 3000)
198 | - `API_USERNAME`: Username for API authentication (fallback)
199 | - `API_PASSWORD`: Password for API authentication (fallback)
200 | - `API_TOKEN`: API token for authentication (fallback)
201 | - `DEFAULT_API_BASE_URL`: Default base URL for API endpoints (fallback)
202 | - `DEFAULT_SWAGGER_URL`: Default Swagger specification URL
203 | 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | module.exports = {
 2 |   preset: 'ts-jest',
 3 |   testEnvironment: 'node',
 4 |   roots: ['<rootDir>/tests'],
 5 |   transform: {
 6 |     '^.+\\.tsx?$': 'ts-jest',
 7 |   },
 8 |   testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
 9 |   moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
10 | }; 
```

--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "swagger": {
 3 |     "url": "https://petstore.swagger.io/v2/swagger.json",
 4 |     "apiBaseUrl": "https://petstore.swagger.io/v2",
 5 |     "defaultAuth": {
 6 |       "type": "apiKey",
 7 |       "apiKey": "special-key",
 8 |       "apiKeyName": "api_key",
 9 |       "apiKeyIn": "header"
10 |     }
11 |   },
12 |   "log": {
13 |     "level": "info"
14 |   },
15 |   "server": {
16 |     "port": 3000,
17 |     "host": "0.0.0.0"
18 |   }
19 | }
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export interface SwaggerConfig {
 2 |   swaggerUrl?: string;
 3 |   swaggerFile?: string;
 4 |   apiBaseUrl: string;
 5 |   auth?: AuthConfig;
 6 | }
 7 | 
 8 | export interface AuthConfig {
 9 |   type: 'basic' | 'bearer' | 'apiKey' | 'oauth2';
10 |   username?: string;
11 |   password?: string;
12 |   token?: string;
13 |   apiKey?: string;
14 |   apiKeyName?: string;
15 |   apiKeyIn?: 'header' | 'query';
16 | }
17 | 
18 | export interface ToolInput {
19 |   auth?: AuthConfig;
20 |   [key: string]: any;
21 | }
22 | 
23 | export interface SecurityScheme {
24 |   type: string;
25 |   description?: string;
26 |   name?: string;
27 |   in?: string;
28 |   scheme?: string;
29 |   flows?: {
30 |     implicit?: {
31 |       authorizationUrl: string;
32 |       scopes: Record<string, string>;
33 |     };
34 |     [key: string]: any;
35 |   };
36 | } 
```

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

```typescript
 1 | export interface SwaggerConfig {
 2 |   swaggerFile?: string;
 3 |   swaggerUrl?: string;
 4 |   apiBaseUrl: string;
 5 |   auth?: AuthConfig;
 6 | }
 7 | 
 8 | export interface ServerConfig {
 9 |   port: number;
10 |   username?: string;
11 |   password?: string;
12 |   token?: string;
13 | }
14 | 
15 | export interface AuthConfig {
16 |   type: 'basic' | 'bearer' | 'apiKey' | 'oauth2';
17 |   username?: string;
18 |   password?: string;
19 |   token?: string;
20 |   apiKey?: string;
21 |   apiKeyIn?: 'header' | 'query';
22 |   apiKeyName?: string;
23 | }
24 | 
25 | export interface SecurityScheme {
26 |   type: string;
27 |   description?: string;
28 |   name?: string;
29 |   in?: string;
30 |   scheme?: string;
31 |   bearerFormat?: string;
32 |   flows?: any;
33 | }
34 | 
35 | export interface ToolInput {
36 |   auth?: AuthConfig;
37 |   [key: string]: any;
38 | } 
```

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

```json
 1 | {
 2 |   "name": "swagger-mcp",
 3 |   "version": "1.0.0",
 4 |   "description": "Server that ingests and serves Swagger/OpenAPI specifications",
 5 |   "main": "dist/server.js",
 6 |   "scripts": {
 7 |     "start": "node dist/server.js",
 8 |     "dev": "NODE_OPTIONS='--loader ts-node/esm' ts-node src/server.ts",
 9 |     "build": "tsc",
10 |     "watch": "tsc -w",
11 |     "test": "jest",
12 |     "test:watch": "jest --watch",
13 |     "test:coverage": "jest --coverage"
14 |   },
15 |   "keywords": [
16 |     "swagger",
17 |     "openapi",
18 |     "api",
19 |     "documentation"
20 |   ],
21 |   "author": "",
22 |   "license": "ISC",
23 |   "dependencies": {
24 |     "@apidevtools/swagger-parser": "^10.1.0",
25 |     "@modelcontextprotocol/sdk": "^1.7.0",
26 |     "@types/cors": "^2.8.17",
27 |     "@types/express": "^5.0.0",
28 |     "@types/swagger-parser": "^7.0.1",
29 |     "axios": "^1.8.3",
30 |     "cors": "^2.8.5",
31 |     "dotenv": "^16.4.5",
32 |     "eventsource": "^3.0.5",
33 |     "express": "^4.18.3",
34 |     "node-fetch": "^3.3.2",
35 |     "openapi-types": "^12.1.3",
36 |     "ts-node": "^10.9.2",
37 |     "tslib": "^2.8.1",
38 |     "typescript": "^5.4.2",
39 |     "zod": "^3.22.4"
40 |   },
41 |   "devDependencies": {
42 |     "@types/jest": "^29.5.14",
43 |     "@types/supertest": "^6.0.2",
44 |     "jest": "^29.7.0",
45 |     "supertest": "^7.0.0",
46 |     "ts-jest": "^29.2.6"
47 |   },
48 |   "packageManager": "[email protected]+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
49 | }
50 | 
```

--------------------------------------------------------------------------------
/tests/test-pets.js:
--------------------------------------------------------------------------------

```javascript
 1 | import fetch from 'node-fetch';
 2 | import { EventSource } from 'eventsource';
 3 | 
 4 | async function findPetsBySold() {
 5 |   try {
 6 |     // First check if server is ready
 7 |     const healthResponse = await fetch('http://localhost:3000/health');
 8 |     const health = await healthResponse.json();
 9 |     
10 |     if (health.mcpServer !== 'initialized') {
11 |       console.error('Server is not ready. Please wait for it to initialize.');
12 |       return;
13 |     }
14 | 
15 |     // Create SSE connection
16 |     const eventSource = new EventSource('http://localhost:3000/sse');
17 |     
18 |     eventSource.onopen = async () => {
19 |       console.log('SSE connection opened');
20 |       
21 |       // Send the request
22 |       try {
23 |         const response = await fetch('http://localhost:3000/messages', {
24 |           method: 'POST',
25 |           headers: {
26 |             'Content-Type': 'application/json'
27 |           },
28 |           body: JSON.stringify({
29 |             type: 'invoke',
30 |             tool: 'findPetsByStatus',
31 |             input: {
32 |               status: ['sold']
33 |             }
34 |           })
35 |         });
36 |         
37 |         const data = await response.json();
38 |         console.log('Response:', data);
39 |         eventSource.close();
40 |       } catch (error) {
41 |         console.error('Error sending message:', error.message);
42 |         eventSource.close();
43 |       }
44 |     };
45 |     
46 |     eventSource.onerror = (error) => {
47 |       console.error('SSE error:', error);
48 |       eventSource.close();
49 |     };
50 |     
51 |     eventSource.onmessage = (event) => {
52 |       console.log('Received:', event.data);
53 |     };
54 |   } catch (error) {
55 |     console.error('Error:', error.message);
56 |     if (error.message.includes('ECONNREFUSED')) {
57 |       console.log('Make sure the server is running on port 3000');
58 |     }
59 |   }
60 | }
61 | 
62 | // Run the test
63 | findPetsBySold(); 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import fs from 'fs/promises';
 3 | import path from 'path';
 4 | 
 5 | // Define the configuration schema
 6 | export const ConfigSchema = z.object({
 7 |   swagger: z.object({
 8 |     url: z.string().url(),
 9 |     apiBaseUrl: z.string().url(),
10 |     defaultAuth: z.object({
11 |       type: z.enum(['basic', 'bearer', 'apiKey', 'oauth2']),
12 |       token: z.string().optional(),
13 |       username: z.string().optional(),
14 |       password: z.string().optional(),
15 |       apiKey: z.string().optional(),
16 |       apiKeyName: z.string().optional(),
17 |       apiKeyIn: z.enum(['header', 'query']).optional(),
18 |     }).optional(),
19 |   }),
20 |   log: z.object({
21 |     level: z.enum(['debug', 'info', 'warn', 'error']),
22 |   }),
23 |   server: z.object({
24 |     port: z.number().default(3000),
25 |     host: z.string().default('0.0.0.0'),
26 |   }),
27 | });
28 | 
29 | export type Config = z.infer<typeof ConfigSchema>;
30 | 
31 | const defaultConfig: Config = {
32 |   swagger: {
33 |     url: 'https://petstore.swagger.io/v2/swagger.json',
34 |     apiBaseUrl: 'https://petstore.swagger.io/v2',
35 |     defaultAuth: {
36 |       type: 'apiKey',
37 |       apiKey: 'special-key',
38 |       apiKeyName: 'api_key',
39 |       apiKeyIn: 'header',
40 |     },
41 |   },
42 |   log: {
43 |     level: 'info',
44 |   },
45 |   server: {
46 |     port: 3000,
47 |     host: '0.0.0.0',
48 |   },
49 | };
50 | 
51 | export async function loadConfig(configPath?: string): Promise<Config> {
52 |   try {
53 |     // If no config path provided, create default config file
54 |     if (!configPath) {
55 |       configPath = path.join(process.cwd(), 'config.json');
56 |       // Check if config file exists, if not create it with default values
57 |       try {
58 |         await fs.access(configPath);
59 |       } catch {
60 |         await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
61 |         console.log(`Created default configuration file at ${configPath}`);
62 |       }
63 |     }
64 | 
65 |     const configFile = await fs.readFile(configPath, 'utf-8');
66 |     const config = JSON.parse(configFile);
67 |     return ConfigSchema.parse(config);
68 |   } catch (error) {
69 |     if (error instanceof z.ZodError) {
70 |       console.error('Invalid configuration:', error.errors);
71 |     } else {
72 |       console.error('Error loading configuration:', error);
73 |     }
74 |     console.log('Using default configuration');
75 |     return defaultConfig;
76 |   }
77 | } 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import express, { Request, Response, Router } from 'express';
  2 | import cors from 'cors';
  3 | import dotenv from 'dotenv';
  4 | import { SwaggerMcpServer } from './mcp-server';
  5 | import { loadConfig } from './config';
  6 | 
  7 | // Load environment variables
  8 | dotenv.config();
  9 | 
 10 | const app = express();
 11 | const router = Router();
 12 | let mcpServer: SwaggerMcpServer | null = null;
 13 | 
 14 | // Middleware
 15 | // app.use(cors());
 16 | // app.use(express.json());
 17 | 
 18 | // Routes
 19 | const handleSSE = async (req: Request, res: Response) => {
 20 |   console.debug('SSE connection request received');
 21 |   if (!mcpServer) {
 22 |     console.warn('MCP server not initialized - rejecting SSE connection');
 23 |     res.status(400).json({ error: 'MCP server not initialized' });
 24 |     return;
 25 |   }
 26 |   console.debug('Establishing SSE connection...');
 27 |   mcpServer.handleSSE(res);
 28 | };
 29 | 
 30 | const handleMessage = async (req: Request, res: Response) => {
 31 |   console.debug('Message received:', {
 32 |     method: req.method,
 33 |     path: req.path,
 34 |     body: req.body
 35 |   });
 36 |   if (!mcpServer) {
 37 |     console.warn('MCP server not initialized - rejecting message');
 38 |     res.status(400).json({ error: 'MCP server not initialized' });
 39 |     return;
 40 |   }
 41 |   mcpServer.handleMessage(req, res);
 42 | };
 43 | 
 44 | const handleHealth = (_req: Request, res: Response) => {
 45 |   console.debug('Health check request received');
 46 |   res.json({ 
 47 |     status: 'ok',
 48 |     mcpServer: mcpServer ? 'initialized' : 'not initialized'
 49 |   });
 50 | };
 51 | 
 52 | // // Register routes
 53 | // router.get('/sse', handleSSE);
 54 | // router.post('/messages', handleMessage);
 55 | // router.get('/health', handleHealth);
 56 | 
 57 | // Mount router
 58 | // app.use('/', router);
 59 | 
 60 | app.get('/sse', handleSSE);
 61 | app.post('/messages', handleMessage);
 62 | app.get('/health', handleHealth);
 63 | 
 64 | // Initialize server
 65 | async function initializeServer() {
 66 |   try {
 67 |     console.log('Starting server initialization...');
 68 |     
 69 |     // Load configuration
 70 |     const config = await loadConfig();
 71 |     // set app logging level
 72 |     process.env.LOG_LEVEL = config.log?.level || 'info';
 73 | 
 74 |     console.debug('Configuration loaded:', {
 75 |       swaggerUrl: config.swagger.url,
 76 |       apiBaseUrl: config.swagger.apiBaseUrl,
 77 |       hasDefaultAuth: !!config.swagger.defaultAuth
 78 |     });
 79 |     
 80 |     // Create and initialize MCP server
 81 |     console.log('Creating MCP server instance...');
 82 |     mcpServer = new SwaggerMcpServer(config.swagger.apiBaseUrl, config.swagger.defaultAuth);
 83 |     
 84 |     console.log('Loading Swagger specification...');
 85 |     await mcpServer.loadSwaggerSpec(config.swagger.url);
 86 |     console.debug('Swagger specification loaded successfully');
 87 |     
 88 |     // Start the server
 89 |     app.listen(config.server.port, config.server.host, () => {
 90 |       console.log('Server initialization complete');
 91 |       console.log(`Server is running on http://${config.server.host}:${config.server.port}`);
 92 |       console.log('Swagger specification loaded from:', config.swagger.url);
 93 |       console.log('API Base URL:', config.swagger.apiBaseUrl);
 94 |     });
 95 |   } catch (error) {
 96 |     console.error('Failed to initialize server:', error);
 97 |     process.exit(1);
 98 |   }
 99 | }
100 | 
101 | // Start the server
102 | initializeServer();
```

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

```json
  1 | {
  2 |   "compilerOptions": {
  3 |     /* Visit https://aka.ms/tsconfig to read more about this file */
  4 | 
  5 |     /* Projects */
  6 |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
  7 |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
  8 |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
  9 |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
 10 |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
 11 |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 12 | 
 13 |     /* Language and Environment */
 14 |     "target": "ES2020",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
 15 |     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
 16 |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
 17 |     // "libReplacement": true,                           /* Enable lib replacement. */
 18 |     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
 19 |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
 20 |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
 21 |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
 22 |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
 23 |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
 24 |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
 25 |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 26 |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 27 | 
 28 |     /* Modules */
 29 |     "module": "NodeNext",                                /* Specify what module code is generated. */
 30 |     "moduleResolution": "NodeNext",                     /* Specify how TypeScript looks up a file from a given module specifier. */
 31 |     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
 32 |     // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
 33 |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
 34 |     // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
 35 |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
 36 |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
 37 |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
 38 |     // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
 39 |     // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
 40 |     // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
 41 |     // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
 42 |     // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
 43 |     // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
 44 |     // "resolveJsonModule": true,                        /* Enable importing .json files. */
 45 |     // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
 46 |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 47 | 
 48 |     /* JavaScript Support */
 49 |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
 50 |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
 51 |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
 52 | 
 53 |     /* Emit */
 54 |     "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
 55 |     "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 56 |     "emitDeclarationOnly": false,                     /* Only output d.ts files and not JavaScript files. */
 57 |     "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
 58 |     "noEmit": false,                                  /* Disable emitting files from a compilation. */
 59 |     "outDir": "dist",                                 /* Specify an output folder for all emitted files. */
 60 |     "removeComments": true,                           /* Disable emitting comments. */
 61 |     "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 62 |     "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 63 |     "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 64 |     "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 65 |     "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 66 |     "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 67 |     "newLine": "crlf",                                /* Set the newline character for emitting files. */
 68 |     "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 69 |     "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 70 |     "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 71 |     "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 72 | 
 73 |     /* Interop Constraints */
 74 |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
 75 |     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
 76 |     // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
 77 |     // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
 78 |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
 79 |     "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
 80 |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
 81 |     "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
 82 | 
 83 |     /* Type Checking */
 84 |     "strict": true,                                      /* Enable all strict type-checking options. */
 85 |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
 86 |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
 87 |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
 88 |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
 89 |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
 90 |     // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
 91 |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
 92 |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
 93 |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
 94 |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
 95 |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
 96 |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
 97 |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
 98 |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
 99 |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
100 |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
101 |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
102 |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
103 |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
104 | 
105 |     /* Completeness */
106 |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
107 |     "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
108 |   },
109 |   "include": ["src/**/*"],
110 |   "exclude": ["node_modules", "dist"]
111 | }
112 | 
```

--------------------------------------------------------------------------------
/examples/swagger-pet-store.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "swagger":"2.0",
  3 |   "info":{
  4 |     "description":"This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.7","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"[email protected]"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}
  5 |   },
  6 |   "host":"petstore.swagger.io",
  7 |   "basePath":"/v2",
  8 |   "tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user",
  9 |   "externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],
 10 |   "schemes":["https","http"],
 11 |   "paths":{
 12 | 
 13 |     "/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},
 14 |     
 15 |     "/pet":{
 16 |       "post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}
 17 |     },
 18 |     
 19 |     "/pet/findByStatus":{
 20 |       "get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/json","application/xml"],
 21 |         "parameters":[
 22 |           {"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}
 23 |           ],
 24 |           "responses":{
 25 |             "200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},
 26 |             "400":{"description":"Invalid status value"}
 27 |           },
 28 |           "security":[{"petstore_auth":["write:pets","read:pets"]}
 29 |         ]
 30 |       }
 31 |     },
 32 |     
 33 |     "/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/json","application/xml"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}],"deprecated":true}},
 34 |     
 35 |     "/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/json","application/xml"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},
 36 |     
 37 |     "/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},
 38 |     
 39 |     "/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},
 40 |     
 41 |     "/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":10,"minimum":1,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","operationId":"deleteOrder","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"integer","minimum":1,"format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},
 42 |     
 43 |     "/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},
 44 |     
 45 |     "/user/{username}":{
 46 |       "get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},
 47 |       "put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},
 48 |       "delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}},
 49 |     
 50 |     "/user/login":{
 51 |       "get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","headers":{"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when token expires"},"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"}},"schema":{"type":"string"}},"400":{"description":"Invalid username/password supplied"}}}
 52 |     },
 53 |     
 54 |     "/user/logout":{
 55 |       "get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/json","application/xml"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}
 56 |     },
 57 |     
 58 |     "/user/createWithArray":{
 59 |       "post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}
 60 |     },
 61 |     
 62 |     "/user":{
 63 |       "post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}
 64 |     }
 65 |   },
 66 | 
 67 |   "securityDefinitions":{
 68 |     "api_key":{"type":"apiKey","name":"api_key","in":"header"},
 69 |     "petstore_auth":{
 70 |       "type":"oauth2",
 71 |       "authorizationUrl":"https://petstore.swagger.io/oauth/authorize",
 72 |       "flow":"implicit",
 73 |       "scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}
 74 |     }
 75 |   },
 76 | 
 77 |   "definitions":{
 78 |     "ApiResponse":{
 79 |       "type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}
 80 |     },
 81 |     "Category":{
 82 |       "type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}}
 83 |     ,
 84 |     "Pet":{
 85 |       "type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"wrapped":true},"items":{"type":"string","xml":{"name":"photoUrl"}}},"tags":{"type":"array","xml":{"wrapped":true},"items":{"xml":{"name":"tag"},"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}
 86 |     },
 87 |     "Tag":{
 88 |       "type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}
 89 |     },
 90 |     "Order":{
 91 |       "type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean"}},"xml":{"name":"Order"}
 92 |     },
 93 |     "User":{
 94 |       "type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone": {"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}
 95 |     }
 96 |   },
 97 | 
 98 |   "externalDocs":{
 99 |     "description":"Find out more about Swagger",
100 |     "url":"http://swagger.io"
101 |   }
102 | }
```

--------------------------------------------------------------------------------
/src/mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
  2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  3 | import { z } from "zod";
  4 | import axios from "axios";
  5 | import SwaggerParser from "@apidevtools/swagger-parser";
  6 | import { OpenAPI } from "openapi-types";
  7 | import { Request, Response } from 'express';
  8 | import { AuthConfig, ToolInput, SecurityScheme } from './types.js';
  9 | 
 10 | let transport: SSEServerTransport | null = null;
 11 | 
 12 | export class SwaggerMcpServer {
 13 |   private mcpServer: McpServer;
 14 |   private swaggerSpec: OpenAPI.Document | null = null;
 15 |   private apiBaseUrl: string;
 16 |   private defaultAuth: AuthConfig | undefined;
 17 |   private securitySchemes: Record<string, SecurityScheme> = {};
 18 | 
 19 |   constructor(apiBaseUrl: string, defaultAuth?: AuthConfig) {
 20 |     console.debug('constructor', apiBaseUrl, defaultAuth);
 21 |     this.apiBaseUrl = apiBaseUrl;
 22 |     this.defaultAuth = defaultAuth;
 23 |     this.mcpServer = new McpServer({
 24 |       name: "Swagger API MCP Server",
 25 |       version: "1.0.0",
 26 |     });
 27 |     this.mcpServer.tool('test', 'test', {
 28 |       input: z.object({
 29 |         test: z.string(),
 30 |       }),
 31 |     }, async ({ input }) => {
 32 |       return { content: [{ type: "text", text: "Hello, world!" }] };
 33 |     });
 34 |   }
 35 | 
 36 |   private getAuthHeaders(auth?: AuthConfig, operation?: OpenAPI.Operation): Record<string, string> {
 37 |     // Use provided auth or fall back to default auth
 38 |     const authConfig = auth || this.defaultAuth;
 39 |     if (!authConfig) return {};
 40 | 
 41 |     // Check if operation requires specific security
 42 |     const requiredSchemes = operation?.security || (this.swaggerSpec as any)?.security || [];
 43 |     if (requiredSchemes.length === 0) return {};
 44 | 
 45 |     switch (authConfig.type) {
 46 |       case 'basic':
 47 |         if (authConfig.username && authConfig.password) {
 48 |           const credentials = Buffer.from(`${authConfig.username}:${authConfig.password}`).toString('base64');
 49 |           return { 'Authorization': `Basic ${credentials}` };
 50 |         }
 51 |         break;
 52 |       case 'bearer':
 53 |         if (authConfig.token) {
 54 |           return { 'Authorization': `Bearer ${authConfig.token}` };
 55 |         }
 56 |         break;
 57 |       case 'apiKey':
 58 |         // For Petstore, we know the API key goes in header named 'api_key'
 59 |         if (authConfig.apiKey) {
 60 |           return { 'api_key': authConfig.apiKey };
 61 |         }
 62 |         break;
 63 |       case 'oauth2':
 64 |         if (authConfig.token) {
 65 |           return { 'Authorization': `Bearer ${authConfig.token}` };
 66 |         }
 67 |         break;
 68 |     }
 69 |     return {};
 70 |   }
 71 | 
 72 |   private getAuthQueryParams(auth?: AuthConfig): Record<string, string> {
 73 |     const authConfig = auth || this.defaultAuth;
 74 |     if (!authConfig) return {};
 75 | 
 76 |     if (authConfig.type === 'apiKey' && authConfig.apiKey && authConfig.apiKeyName && authConfig.apiKeyIn === 'query') {
 77 |       return { [authConfig.apiKeyName]: authConfig.apiKey };
 78 |     }
 79 | 
 80 |     return {};
 81 |   }
 82 | 
 83 |   private extractSecuritySchemes() {
 84 |     if (!this.swaggerSpec) return;
 85 | 
 86 |     // OpenAPI 3.x
 87 |     const components = (this.swaggerSpec as any).components;
 88 |     if (components && components.securitySchemes) {
 89 |       this.securitySchemes = components.securitySchemes;
 90 |       return;
 91 |     }
 92 | 
 93 |     // Swagger 2.0
 94 |     const securityDefinitions = (this.swaggerSpec as any).securityDefinitions;
 95 |     if (securityDefinitions) {
 96 |       this.securitySchemes = securityDefinitions;
 97 |     }
 98 |   }
 99 | 
100 |   private createAuthSchema(operation?: OpenAPI.Operation): z.ZodType<any> {
101 |     const authTypes: string[] = ['none'];  // Start with 'none' as default
102 |     const authSchema: any = {};
103 | 
104 |     // Check operation-specific security requirements
105 |     const requiredSchemes = operation?.security || (this.swaggerSpec as any)?.security || [];
106 |     const requiredSchemeNames = new Set(
107 |       requiredSchemes.flatMap((scheme: any) => Object.keys(scheme))
108 |     );
109 | 
110 |     for (const [key, scheme] of Object.entries(this.securitySchemes)) {
111 |       const securityScheme = scheme as SecurityScheme;
112 |       const isRequired = requiredSchemeNames.has(key);
113 | 
114 |       switch (securityScheme.type) {
115 |         case 'basic':
116 |           authTypes.push('basic');
117 |           if (isRequired || authTypes.length === 1) {
118 |             authSchema.username = z.string();
119 |             authSchema.password = z.string();
120 |           } else {
121 |             authSchema.username = z.string().optional();
122 |             authSchema.password = z.string().optional();
123 |           }
124 |           break;
125 |         case 'bearer':
126 |         case 'http':
127 |           if (securityScheme.scheme === 'bearer') {
128 |             authTypes.push('bearer');
129 |             authSchema.token = isRequired ? z.string() : z.string().optional();
130 |           }
131 |           break;
132 |         case 'apiKey':
133 |           authTypes.push('apiKey');
134 |           if (isRequired || authTypes.length === 1) {
135 |             authSchema.apiKey = z.string();
136 |             if (securityScheme.in && securityScheme.name) {
137 |               authSchema.apiKeyIn = z.enum(['header', 'query']).default(securityScheme.in as 'header' | 'query');
138 |               authSchema.apiKeyName = z.string().default(securityScheme.name);
139 |             }
140 |           } else {
141 |             authSchema.apiKey = z.string().optional();
142 |             if (securityScheme.in && securityScheme.name) {
143 |               authSchema.apiKeyIn = z.enum(['header', 'query']).optional().default(securityScheme.in as 'header' | 'query');
144 |               authSchema.apiKeyName = z.string().optional().default(securityScheme.name);
145 |             }
146 |           }
147 |           break;
148 |         case 'oauth2':
149 |           authTypes.push('oauth2');
150 |           // Make token optional if API Key auth is available
151 |           authSchema.token = isRequired && !authTypes.includes('apiKey') ? z.string() : z.string().optional();
152 |           break;
153 |       }
154 |     }
155 | 
156 |     // Add all auth types to the enum - ensure we have at least 'none'
157 |     authSchema.type = z.enum(authTypes as [string, ...string[]]);
158 | 
159 |     const description = `Authentication configuration. Available methods: ${authTypes.join(', ')}. ` +
160 |       Object.entries(this.securitySchemes)
161 |         .map(([key, scheme]) => {
162 |           const desc = (scheme as SecurityScheme).description || scheme.type;
163 |           const required = requiredSchemeNames.has(key) ? ' (Required)' : ' (Optional)';
164 |           return `${key}: ${desc}${required}`;
165 |         })
166 |         .join('. ');
167 | 
168 |     return z.object(authSchema).describe(description);
169 |   }
170 | 
171 |   async loadSwaggerSpec(specUrlOrFile: string) {
172 |     console.debug('Loading Swagger specification from:', specUrlOrFile);
173 |     try {
174 |       // Add auth headers for fetching the swagger spec if needed
175 |       const headers = this.getAuthHeaders();
176 |       this.swaggerSpec = await SwaggerParser.parse(specUrlOrFile, {
177 |         resolve: { http: { headers } }
178 |       }) as OpenAPI.Document;
179 |       
180 |       const info = this.swaggerSpec.info;
181 |       console.debug('Loaded Swagger spec:', {
182 |         title: info.title,
183 |         version: info.version,
184 |         description: info.description?.substring(0, 100) + '...'
185 |       });
186 |       
187 |       // Extract security schemes
188 |       this.extractSecuritySchemes();
189 |       console.debug('Security schemes found:', Object.keys(this.securitySchemes));
190 |       
191 |       // Update server name with API info
192 |       this.mcpServer = new McpServer({
193 |         name: info.title || "Swagger API Server",
194 |         version: info.version || "1.0.0",
195 |         description: info.description || undefined
196 |       });
197 | 
198 |       await this.registerTools();
199 |     } catch (error) {
200 |       console.error("Failed to load Swagger specification:", error);
201 |       throw error;
202 |     }
203 |   }
204 | 
205 |   private createZodSchema(parameter: OpenAPI.Parameter): z.ZodType<any> {
206 |     const schema = (parameter as any).schema || parameter;
207 |     
208 |     switch (schema.type) {
209 |       case 'string':
210 |         return z.string().describe(schema.description || '');
211 |       case 'number':
212 |         return z.number().describe(schema.description || '');
213 |       case 'integer':
214 |         return z.number().int().describe(schema.description || '');
215 |       case 'boolean':
216 |         return z.boolean().describe(schema.description || '');
217 |       case 'array':
218 |         return z.array(this.createZodSchema(schema.items)).describe(schema.description || '');
219 |       case 'object':
220 |         if (schema.properties) {
221 |           const shape: { [key: string]: z.ZodType<any> } = {};
222 |           Object.entries(schema.properties).forEach(([key, prop]) => {
223 |             shape[key] = this.createZodSchema(prop as OpenAPI.Parameter);
224 |           });
225 |           return z.object(shape).describe(schema.description || '');
226 |         }
227 |         return z.object({}).describe(schema.description || '');
228 |       default:
229 |         return z.any().describe(schema.description || '');
230 |     }
231 |   }
232 | 
233 |   private async registerTools() {
234 |     console.debug('Starting tool registration process');
235 |     if (!this.swaggerSpec || !this.swaggerSpec.paths) {
236 |       console.warn('No paths found in Swagger spec');
237 |       return;
238 |     }
239 | 
240 |     const totalPaths = Object.keys(this.swaggerSpec.paths).length;
241 |     console.debug(`Found ${totalPaths} paths to process`);
242 | 
243 |     for (const [path, pathItem] of Object.entries(this.swaggerSpec.paths)) {
244 |       if (!pathItem) continue;
245 |       for (const [method, operation] of Object.entries(pathItem)) {
246 |         if (method === '$ref' || !operation) continue;
247 | 
248 |         const op = operation as OpenAPI.Operation;
249 |         const operationId = op.operationId || `${method}-${path}`;
250 |         console.log(`Register endpoint: ${method.toUpperCase()} ${path} (${operationId})`);
251 | 
252 |         // Create input schema based on parameters
253 |         const inputShape: { [key: string]: z.ZodType<any> } = {};
254 |         const parameters = op.parameters || [];
255 | 
256 |         // Add auth parameters based on security schemes
257 |         inputShape['auth'] = this.createAuthSchema(op);
258 | 
259 |         // Add API parameters
260 |         parameters.forEach((param) => {
261 |           if (param && 'name' in param && param.name) {
262 |             inputShape[param.name] = this.createZodSchema(param);
263 |           }
264 |         });
265 | 
266 |         console.debug(`Registering tool: ${operationId}`, {
267 |           parameters: Object.keys(inputShape),
268 |           hasAuth: !!inputShape['auth']
269 |         });
270 | 
271 |         // Register the tool
272 |         this.mcpServer.tool(
273 |           operationId,
274 |           `${op.summary || `${method.toUpperCase()} ${path}`}\n\n${op.description || ''}`,
275 |           {
276 |             input: z.object(inputShape),
277 |           },
278 |           async ({ input }) => {
279 |             console.debug(`Tool called: ${operationId}`, {
280 |               params: Object.keys(input).filter(k => k !== 'auth'),
281 |               hasAuth: !!input.auth
282 |             });
283 |             try {
284 |               const { auth, ...params } = input as ToolInput;
285 |               console.debug('params', params);
286 |               let url = this.apiBaseUrl + path;
287 |               
288 |               // Separate path parameters from query parameters
289 |               const pathParams = new Set();
290 |               path.split('/').forEach(segment => {
291 |                 if (segment.startsWith('{') && segment.endsWith('}')) {
292 |                   pathParams.add(segment.slice(1, -1));
293 |                 }
294 |               });
295 | 
296 |               // Replace path parameters
297 |               Object.entries(params).forEach(([key, value]) => {
298 |                 if (pathParams.has(key)) {
299 |                   url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
300 |                 }
301 |               });
302 | 
303 |               // Build query parameters object for GET requests
304 |               const queryObject = method === 'get' ? 
305 |                 Object.entries(params)
306 |                   .filter(([key]) => !pathParams.has(key))
307 |                   .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
308 |                 : {};
309 | 
310 |               const headers = this.getAuthHeaders(auth, op);
311 |               const queryParams = this.getAuthQueryParams(auth);
312 | 
313 |               console.debug('url', url);
314 |               console.debug('method', method);
315 |               console.debug('headers', headers);
316 |               console.debug('params', params);
317 |               console.debug('queryParams', queryParams);
318 |               
319 |               const response = await axios({
320 |                 method: method as string,
321 |                 url: url,
322 |                 headers,
323 |                 data: method !== 'get' ? params : undefined,
324 |                 params: { ...queryObject, ...queryParams },
325 |                 paramsSerializer: (params) => {
326 |                   const searchParams = new URLSearchParams();
327 |                   Object.entries(params).forEach(([key, value]) => {
328 |                     if (Array.isArray(value)) {
329 |                       // Handle arrays by adding multiple entries with the same key
330 |                       value.forEach(v => searchParams.append(key, v));
331 |                     } else {
332 |                       searchParams.append(key, value as string);
333 |                     }
334 |                   });
335 |                   return searchParams.toString();
336 |                 }
337 |               });
338 |               console.debug('response.headers', response.headers);
339 |               console.debug('response.data', response.data);
340 | 
341 |               return {
342 |                 content: [
343 |                   { type: "text", text: JSON.stringify(response.data, null, 2) },
344 |                   // http status code
345 |                   { type: "text", text: `HTTP Status Code: ${response.status}` },
346 |                   // // http headers
347 |                   // { type: "text", text: JSON.stringify(response.headers, null, 2) },
348 |                 ],
349 |               };
350 |             } catch (error) {
351 |               console.error(`Error in ${operationId}:`, error);
352 |               if (axios.isAxiosError(error) && error.response) {
353 |                 return {
354 |                   content: [{ 
355 |                     type: "text", text: `Error ${error.response.status}: ${JSON.stringify(error.response.data, null, 2)}` 
356 |                   }],
357 |                 };
358 |               }
359 |               return {
360 |                 content: [{ type: "text", text: `Error: ${error}` }],
361 |               };
362 |             }
363 |           }
364 |         );
365 |       }
366 |     }
367 |   }
368 | 
369 |   getServer() {
370 |     return this.mcpServer;
371 |   }
372 | 
373 |   handleSSE(res: Response) {
374 |     console.debug('MCP handleSSE');
375 |     transport = new SSEServerTransport("/messages", res);
376 |     this.mcpServer.connect(transport);
377 |   }
378 | 
379 |   handleMessage(req: Request, res: Response) {
380 |     console.debug('MCP handleMessage', req.body);
381 |     if (transport) {
382 |       try {
383 |         transport.handlePostMessage(req, res);
384 |       } catch (error) {
385 |         console.error('Error handling message:', error);
386 |       }
387 |     } else {
388 |       console.warn('no transport');
389 |     }
390 |   }
391 | }
392 | 
```