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

```
├── .env.example
├── .gitignore
├── config.json
├── jest.config.js
├── package.json
├── README.md
├── src
│   ├── config.ts
│   ├── postman-mcp-simple.ts
│   ├── simple-server.ts
│   ├── simple-stdio.ts
│   ├── swagger-mcp-simple.ts
│   └── types.ts
├── start-mcp.sh
├── test-simple.ts
├── 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* 
29 | 
30 | # Local test files
31 | ns-openapi.json
```

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

```markdown
  1 | # Swagger/Postman MCP Server
  2 | 
  3 | Server that ingests and serves Swagger/OpenAPI specifications and Postman collections as MCP (Model Context Protocol) tools using a **simplified strategic approach**.
  4 | 
  5 | Instead of generating hundreds of individual tools for each API endpoint, this server provides **only 4 strategic tools** that allow AI agents to dynamically discover and interact with APIs:
  6 | 
  7 | ```bash
  8 | Example prompt:
  9 | Help me generate an axios call using our api mcp. I want to implement updating a user. Follow our same DDD pattern (tanstack hook -> axios service)
 10 | ```
 11 | 
 12 | ## Features
 13 | 
 14 | - **Strategic Tool Approach**: Only 4 tools instead of hundreds for better AI agent performance
 15 | - **OpenAPI/Swagger Support**: Load OpenAPI 2.0/3.0 specifications from URLs or local files
 16 | - **Postman Collection Support**: Load Postman collection JSON files from URLs or local files
 17 | - **Environment Variables**: Support for Postman environment files
 18 | - **Authentication**: Multiple authentication methods (Basic, Bearer, API Key, OAuth2)
 19 | - **Dynamic API Discovery**: Tools for listing, searching, and getting details about API endpoints
 20 | - **Request Execution**: Execute API requests with proper parameter handling and authentication
 21 | 
 22 | ## Security
 23 | 
 24 | This is a personal server!! Do not expose it to the public internet.
 25 | If the underlying API requires authentication, you should not expose the MCP server to the public internet.
 26 | 
 27 | ## TODO
 28 | 
 29 | - secrets - the MCP server should be able to use secrets from the user to authenticate requests to the API
 30 | - Comprehensive test suite
 31 | 
 32 | ## Prerequisites
 33 | 
 34 | - Node.js (v18 or higher)
 35 | - Yarn package manager
 36 | - TypeScript
 37 | 
 38 | ## Installation
 39 | 
 40 | ```bash
 41 | # Clone the repository
 42 | git clone <repository-url>
 43 | cd swag-mcp
 44 | 
 45 | # Install dependencies
 46 | npm install
 47 | # or
 48 | yarn install
 49 | 
 50 | # Build the project
 51 | npm run build
 52 | # or
 53 | yarn build
 54 | 
 55 | # Make the start script executable (Linux/macOS)
 56 | chmod +x start-mcp.sh
 57 | ```
 58 | 
 59 | ### Quick Setup for Cursor
 60 | 
 61 | 1. **Clone and build** (commands above)
 62 | 2. **Configure** your `config.json` with your API details
 63 | 3. **Update paths**: Edit `start-mcp.sh` and change the `cd` path to your installation directory
 64 | 4. **Add to Cursor**: Edit `~/.cursor/mcp.json` and add:
 65 |    ```json
 66 |    {
 67 |      "mcpServers": {
 68 |        "postman-swagger-api": {
 69 |          "command": "/full/path/to/your/swag-mcp/start-mcp.sh"
 70 |        }
 71 |      }
 72 |    }
 73 |    ```
 74 | 5. **Restart Cursor** and start using the 4 strategic MCP tools!
 75 | 
 76 | ## Configuration
 77 | 
 78 | The server uses a `config.json` file for configuration. You can specify either OpenAPI/Swagger specifications or Postman collections.
 79 | 
 80 | ### OpenAPI/Swagger Configuration
 81 | 
 82 | ```json
 83 | {
 84 |   "api": {
 85 |     "type": "openapi",
 86 |     "openapi": {
 87 |       "url": "https://petstore.swagger.io/v2/swagger.json",
 88 |       "apiBaseUrl": "https://petstore.swagger.io/v2",
 89 |       "defaultAuth": {
 90 |         "type": "apiKey",
 91 |         "apiKey": "special-key",
 92 |         "apiKeyName": "api_key",
 93 |         "apiKeyIn": "header"
 94 |       }
 95 |     }
 96 |   },
 97 |   "log": {
 98 |     "level": "info"
 99 |   }
100 | }
101 | ```
102 | 
103 | ### Postman Collection Configuration
104 | 
105 | ```json
106 | {
107 |   "api": {
108 |     "type": "postman",
109 |     "postman": {
110 |       "collectionUrl": "https://www.postman.com/collections/your-collection-id",
111 |       "collectionFile": "./examples/postman-collection.json",
112 |       "environmentUrl": "https://www.postman.com/environments/your-environment-id",
113 |       "environmentFile": "./examples/postman-environment.json",
114 |       "defaultAuth": {
115 |         "type": "bearer",
116 |         "token": "your-api-token-here"
117 |       }
118 |     }
119 |   },
120 |   "log": {
121 |     "level": "info"
122 |   }
123 | }
124 | ```
125 | 
126 | ### Configuration Options
127 | 
128 | #### API Configuration
129 | 
130 | - `api.type`: Either `"openapi"` or `"postman"`
131 | - `api.openapi`: OpenAPI/Swagger specific configuration
132 |   - `url`: URL to the OpenAPI specification
133 |   - `apiBaseUrl`: Base URL for API requests
134 |   - `defaultAuth`: Default authentication configuration
135 | - `api.postman`: Postman specific configuration
136 |   - `collectionUrl`: URL to the Postman collection (optional)
137 |   - `collectionFile`: Path to local Postman collection file (optional)
138 |   - `environmentUrl`: URL to the Postman environment (optional)
139 |   - `environmentFile`: Path to local Postman environment file (optional)
140 |   - `defaultAuth`: Default authentication configuration
141 | 
142 | #### Authentication Configuration
143 | 
144 | - `type`: Authentication type (`"basic"`, `"bearer"`, `"apiKey"`, `"oauth2"`)
145 | - `username`: Username (for basic auth)
146 | - `password`: Password (for basic auth)
147 | - `token`: Token (for bearer/oauth2 auth)
148 | - `apiKey`: API key value
149 | - `apiKeyName`: API key parameter name
150 | - `apiKeyIn`: Where to send API key (`"header"` or `"query"`)
151 | 
152 | #### Logging Configuration
153 | 
154 | - `log.level`: Logging level (`"debug"`, `"info"`, `"warn"`, `"error"`)
155 | 
156 | ## Usage
157 | 
158 | ### Starting the MCP Server
159 | 
160 | The server runs via stdio transport for MCP connections:
161 | 
162 | ```bash
163 | # Start the simplified MCP server via stdio
164 | ./start-mcp.sh
165 | 
166 | # Or directly with node
167 | node dist/simple-stdio.js
168 | 
169 | # For development with auto-reload
170 | npm run dev:simple
171 | # or
172 | yarn dev:simple
173 | ```
174 | 
175 | ### MCP Integration
176 | 
177 | This server uses stdio transport and is designed to be used with MCP clients like Claude Desktop or Cursor.
178 | 
179 | ## Installing in Cursor
180 | 
181 | To use this MCP server with Cursor, you need to add it to your Cursor MCP configuration:
182 | 
183 | ### 1. Locate your Cursor MCP configuration file
184 | 
185 | The configuration file is located at:
186 | 
187 | - **Linux/macOS**: `~/.cursor/mcp.json`
188 | - **Windows**: `%APPDATA%\.cursor\mcp.json`
189 | 
190 | ### 2. Add the MCP server configuration
191 | 
192 | Edit your `mcp.json` file to include this server:
193 | 
194 | ```json
195 | {
196 |   "mcpServers": {
197 |     "postman-swagger-api": {
198 |       "command": "/path/to/your/swag-mcp/start-mcp.sh"
199 |     }
200 |   }
201 | }
202 | ```
203 | 
204 | **⚠️ Important: Change the path!**
205 | 
206 | Replace `/path/to/your/swag-mcp/start-mcp.sh` with the actual path to your cloned repository. For example:
207 | 
208 | - **Linux/macOS**: `"/home/username/Documents/swag-mcp/start-mcp.sh"`
209 | - **Windows**: `"C:\\Users\\username\\Documents\\swag-mcp\\start-mcp.sh"`
210 | 
211 | ### 3. Example complete configuration
212 | 
213 | ```json
214 | {
215 |   "mcpServers": {
216 |     "supabase": {
217 |       "command": "npx",
218 |       "args": [
219 |         "-y",
220 |         "@supabase/mcp-server-supabase@latest",
221 |         "--access-token",
222 |         "your-supabase-token"
223 |       ]
224 |     },
225 |     "postman-swagger-api": {
226 |       "command": "/home/username/Documents/swag-mcp/start-mcp.sh"
227 |     }
228 |   }
229 | }
230 | ```
231 | 
232 | ### 4. Restart Cursor
233 | 
234 | After saving the configuration file, restart Cursor for the changes to take effect.
235 | 
236 | ### 5. Verify installation
237 | 
238 | In Cursor, you should now have access to the 4 strategic MCP tools:
239 | 
240 | - `list_requests` - List all available requests
241 | - `get_request_details` - Get detailed request information
242 | - `search_requests` - Search requests by keyword
243 | - `make_request` - Execute any API request
244 | 
245 | ### Troubleshooting
246 | 
247 | If the MCP server fails to start:
248 | 
249 | 1. **Update start-mcp.sh path**: Edit `start-mcp.sh` and change the `cd` path from `/path/to/your/swag-mcp` to your actual installation directory
250 | 2. **Check the path**: Ensure the path in `mcp.json` points to your actual `start-mcp.sh` file
251 | 3. **Check permissions**: Make sure `start-mcp.sh` is executable (`chmod +x start-mcp.sh`)
252 | 4. **Check build**: Ensure you've run `npm run build` to compile the TypeScript files
253 | 5. **Check logs**: Look in Cursor's MCP logs for error messages
254 | 
255 | ### Example Path Updates
256 | 
257 | If you cloned to `/home/username/Documents/swag-mcp/`, then:
258 | 
259 | **In `start-mcp.sh`:**
260 | 
261 | ```bash
262 | cd "/home/username/Documents/swag-mcp"
263 | ```
264 | 
265 | **In `~/.cursor/mcp.json`:**
266 | 
267 | ```json
268 | "command": "/home/username/Documents/swag-mcp/start-mcp.sh"
269 | ```
270 | 
271 | ## How It Works
272 | 
273 | ### Strategic Tool Approach
274 | 
275 | Instead of generating hundreds of individual tools for each API endpoint, this server provides **4 strategic tools** that enable dynamic API discovery and interaction:
276 | 
277 | ### OpenAPI/Swagger Mode
278 | 
279 | **4 Strategic Tools:**
280 | 
281 | 1. **`list_endpoints`** - List all available API endpoints
282 | 2. **`get_endpoint_details`** - Get detailed information about specific endpoints
283 | 3. **`search_endpoints`** - Search endpoints by keyword
284 | 4. **`make_api_call`** - Execute any API call with proper authentication
285 | 
286 | **Process:**
287 | 
288 | 1. Loads the OpenAPI specification from the configured URL or file
289 | 2. Parses the specification to extract API endpoints, parameters, and security schemes
290 | 3. Makes endpoint information available through the 4 strategic tools
291 | 4. Handles authentication and parameter validation dynamically
292 | 5. Executes API requests and returns responses
293 | 
294 | ### Postman Collection Mode
295 | 
296 | **4 Strategic Tools:**
297 | 
298 | 1. **`list_requests`** - List all available requests in the collection
299 | 2. **`get_request_details`** - Get detailed information about specific requests
300 | 3. **`search_requests`** - Search requests by keyword
301 | 4. **`make_request`** - Execute any request from the collection
302 | 
303 | **Process:**
304 | 
305 | 1. Loads the Postman collection JSON from the configured URL or file
306 | 2. Optionally loads a Postman environment file for variable substitution
307 | 3. Parses requests, folders, and nested items in the collection
308 | 4. Makes request information available through the 4 strategic tools
309 | 5. Handles variable substitution, authentication, and parameter mapping dynamically
310 | 6. Executes requests with proper headers, query parameters, and body data
311 | 
312 | ### Benefits of Strategic Tools
313 | 
314 | - **Better AI Performance**: 4 tools vs hundreds means faster decision making
315 | - **Dynamic Discovery**: AI agents can explore APIs without knowing endpoints beforehand
316 | - **Flexible Interaction**: Any endpoint can be called through `make_api_call`/`make_request`
317 | - **Reduced Overwhelm**: AI agents aren't flooded with tool options
318 | 
319 | ## Strategic Tools Reference
320 | 
321 | ### For OpenAPI/Swagger APIs
322 | 
323 | 1. **`list_endpoints`**
324 | 
325 |    - Lists all available API endpoints with methods and paths
326 |    - No parameters required
327 |    - Returns: Array of endpoint summaries
328 | 
329 | 2. **`get_endpoint_details`**
330 | 
331 |    - Get detailed information about a specific endpoint
332 |    - Parameters: `method` (GET/POST/etc), `path` (/users/{id}/etc)
333 |    - Returns: Full endpoint specification with parameters, body schema, responses
334 | 
335 | 3. **`search_endpoints`**
336 | 
337 |    - Search endpoints by keyword in path, summary, or description
338 |    - Parameters: `query` (search term)
339 |    - Returns: Filtered list of matching endpoints
340 | 
341 | 4. **`make_api_call`**
342 |    - Execute an API call to any endpoint
343 |    - Parameters: `method`, `path`, `pathParams`, `queryParams`, `headers`, `body`
344 |    - Returns: API response with status and data
345 | 
346 | ### For Postman Collections
347 | 
348 | 1. **`list_requests`**
349 | 
350 |    - Lists all available requests in the collection
351 |    - No parameters required
352 |    - Returns: Array of request summaries
353 | 
354 | 2. **`get_request_details`**
355 | 
356 |    - Get detailed information about a specific request
357 |    - Parameters: `requestId` or `requestName`
358 |    - Returns: Full request specification
359 | 
360 | 3. **`search_requests`**
361 | 
362 |    - Search requests by keyword
363 |    - Parameters: `query` (search term)
364 |    - Returns: Filtered list of matching requests
365 | 
366 | 4. **`make_request`**
367 |    - Execute any request from the collection
368 |    - Parameters: `requestId`, `variables` (for substitution)
369 |    - Returns: Request response
370 | 
371 | ### Authentication
372 | 
373 | The server supports multiple authentication methods:
374 | 
375 | - **Basic Authentication**: Username/password
376 | - **Bearer Token**: JWT or other bearer tokens
377 | - **API Key**: In headers or query parameters
378 | - **OAuth2**: Bearer token based
379 | 
380 | Authentication can be configured globally or overridden per request.
381 | 
382 | ## Example Configuration
383 | 
384 | Your `config.json` should specify either OpenAPI or Postman configuration as shown above.
385 | 
386 | ### Example Postman Collection Structure
387 | 
388 | ```json
389 | {
390 |   "info": {
391 |     "name": "Sample API Collection",
392 |     "description": "A sample Postman collection"
393 |   },
394 |   "item": [
395 |     {
396 |       "name": "Get Users",
397 |       "request": {
398 |         "method": "GET",
399 |         "header": [],
400 |         "url": {
401 |           "raw": "{{baseUrl}}/users",
402 |           "host": ["{{baseUrl}}"],
403 |           "path": ["users"]
404 |         }
405 |       }
406 |     }
407 |   ]
408 | }
409 | ```
410 | 
411 | ## Development
412 | 
413 | ```bash
414 | # Install dependencies
415 | npm install
416 | 
417 | # Run in development mode
418 | npm run dev
419 | 
420 | # Run tests
421 | npm test
422 | 
423 | # Build for production
424 | npm run build
425 | ```
426 | 
427 | ## License
428 | 
429 | ISC
430 | 
431 | ## Environment Variables
432 | 
433 | - `PORT`: Server port (default: 3000)
434 | - `API_USERNAME`: Username for API authentication (fallback)
435 | - `API_PASSWORD`: Password for API authentication (fallback)
436 | - `API_TOKEN`: API token for authentication (fallback)
437 | - `DEFAULT_API_BASE_URL`: Default base URL for API endpoints (fallback)
438 | - `DEFAULT_SWAGGER_URL`: Default Swagger specification URL
439 | 
```

--------------------------------------------------------------------------------
/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 |   "api": {
 3 |     "type": "postman",
 4 |     "postman": {
 5 |       "collectionFile": "./ns-openapi.json",
 6 |       "environmentFile": "./ns-openapi.json",
 7 |       "defaultAuth": {
 8 |         "type": "bearer",
 9 |         "token": "test-token"
10 |       }
11 |     }
12 |   },
13 |   "log": {
14 |     "level": "debug"
15 |   },
16 |   "server": {
17 |     "port": 9001,
18 |     "host": "0.0.0.0"
19 |   }
20 | }
```

--------------------------------------------------------------------------------
/start-mcp.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/bin/bash
 2 | 
 3 | # ⚠️ IMPORTANT: Update this path to match your installation directory!
 4 | # Change this to the full path where you cloned the swag-mcp repository
 5 | cd "/path/to/your/swag-mcp"
 6 | 
 7 | # Set environment variables
 8 | export NODE_ENV=production
 9 | 
10 | # Start the simplified MCP server (only 4 strategic tools instead of 300+)
11 | exec node dist/simple-stdio.js 
```

--------------------------------------------------------------------------------
/test-simple.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { SimpleSwaggerMcpServer } from "./src/swagger-mcp-simple.js";
 2 | 
 3 | async function test() {
 4 |   console.log("Testing simplified MCP server...");
 5 | 
 6 |   const server = new SimpleSwaggerMcpServer("https://api.example.com");
 7 | 
 8 |   // Test with a simple OpenAPI spec
 9 |   const simpleSpec = {
10 |     openapi: "3.0.0",
11 |     info: {
12 |       title: "Test API",
13 |       version: "1.0.0",
14 |     },
15 |     paths: {
16 |       "/users": {
17 |         get: {
18 |           operationId: "getUsers",
19 |           summary: "Get all users",
20 |           responses: {
21 |             "200": {
22 |               description: "Success",
23 |             },
24 |           },
25 |         },
26 |       },
27 |     },
28 |   };
29 | 
30 |   console.log("✅ SimpleSwaggerMcpServer created successfully");
31 |   console.log("This demonstrates the strategic tool approach works!");
32 | }
33 | 
34 | test().catch(console.error);
35 | 
```

--------------------------------------------------------------------------------
/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 PostmanConfig {
 9 |   collectionUrl?: string;
10 |   collectionFile?: string;
11 |   environmentUrl?: string;
12 |   environmentFile?: string;
13 |   auth?: AuthConfig;
14 | }
15 | 
16 | export interface ApiConfig {
17 |   type: "openapi" | "postman";
18 |   openapi?: SwaggerConfig;
19 |   postman?: PostmanConfig;
20 | }
21 | 
22 | export interface AuthConfig {
23 |   type: "basic" | "bearer" | "apiKey" | "oauth2";
24 |   username?: string;
25 |   password?: string;
26 |   token?: string;
27 |   apiKey?: string;
28 |   apiKeyName?: string;
29 |   apiKeyIn?: "header" | "query";
30 | }
31 | 
32 | export interface ToolInput {
33 |   auth?: AuthConfig;
34 |   [key: string]: any;
35 | }
36 | 
37 | export interface SecurityScheme {
38 |   type: string;
39 |   description?: string;
40 |   name?: string;
41 |   in?: string;
42 |   scheme?: string;
43 |   flows?: {
44 |     implicit?: {
45 |       authorizationUrl: string;
46 |       scopes: Record<string, string>;
47 |     };
48 |     [key: string]: any;
49 |   };
50 | }
51 | 
```

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

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

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

```typescript
 1 | import { SimpleSwaggerMcpServer } from "./swagger-mcp-simple.js";
 2 | import { loadConfig } from "./config.js";
 3 | 
 4 | async function main() {
 5 |   try {
 6 |     const configPath = process.argv[2] || undefined;
 7 |     const config = await loadConfig(configPath);
 8 | 
 9 |     // Use new config structure or fallback to legacy
10 |     const openApiConfig =
11 |       config.api.type === "openapi" ? config.api.openapi : config.swagger;
12 | 
13 |     if (!openApiConfig) {
14 |       throw new Error("No OpenAPI configuration found");
15 |     }
16 | 
17 |     console.log("Creating MCP server with config:", {
18 |       apiBaseUrl: openApiConfig.apiBaseUrl,
19 |       authType: openApiConfig.defaultAuth?.type,
20 |     });
21 | 
22 |     const server = new SimpleSwaggerMcpServer(
23 |       openApiConfig.apiBaseUrl,
24 |       openApiConfig.defaultAuth && openApiConfig.defaultAuth.type
25 |         ? (openApiConfig.defaultAuth as any)
26 |         : undefined
27 |     );
28 | 
29 |     // Load the swagger spec
30 |     await server.loadSwaggerSpec(openApiConfig.url);
31 | 
32 |     console.log(
33 |       "✅ Simple MCP Server successfully initialized with strategic tools!"
34 |     );
35 |     console.log("Now you have only 4 tools instead of hundreds:");
36 |     console.log("  1. list_endpoints - List all available API endpoints");
37 |     console.log(
38 |       "  2. get_endpoint_details - Get detailed info about specific endpoints"
39 |     );
40 |     console.log("  3. search_endpoints - Search endpoints by keyword");
41 |     console.log("  4. make_api_call - Make actual API calls");
42 |     console.log("");
43 |     console.log("This approach allows AI agents to:");
44 |     console.log("  - Discover APIs dynamically");
45 |     console.log("  - Get required parameter info");
46 |     console.log("  - Make informed API calls");
47 |     console.log("  - Search for relevant endpoints");
48 | 
49 |     return server.getServer();
50 |   } catch (error) {
51 |     console.error("Failed to initialize MCP server:", error);
52 |     process.exit(1);
53 |   }
54 | }
55 | 
56 | main().catch(console.error);
57 | 
```

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

```typescript
  1 | import { z } from "zod";
  2 | import fs from "fs/promises";
  3 | import path from "path";
  4 | 
  5 | // Define auth configuration schema
  6 | const AuthConfigSchema = z.object({
  7 |   type: z.enum(["basic", "bearer", "apiKey", "oauth2"]),
  8 |   token: z.string().optional(),
  9 |   username: z.string().optional(),
 10 |   password: z.string().optional(),
 11 |   apiKey: z.string().optional(),
 12 |   apiKeyName: z.string().optional(),
 13 |   apiKeyIn: z.enum(["header", "query"]).optional(),
 14 | });
 15 | 
 16 | // Define the configuration schema
 17 | export const ConfigSchema = z
 18 |   .object({
 19 |     api: z.object({
 20 |       type: z.enum(["openapi", "postman"]),
 21 |       openapi: z
 22 |         .object({
 23 |           url: z.string().url(),
 24 |           apiBaseUrl: z.string().url(),
 25 |           defaultAuth: AuthConfigSchema.optional(),
 26 |         })
 27 |         .optional(),
 28 |       postman: z
 29 |         .object({
 30 |           collectionUrl: z.string().url().optional(),
 31 |           collectionFile: z.string().optional(),
 32 |           environmentUrl: z.string().url().optional(),
 33 |           environmentFile: z.string().optional(),
 34 |           defaultAuth: AuthConfigSchema.optional(),
 35 |         })
 36 |         .optional(),
 37 |     }),
 38 |     // Keep legacy swagger config for backward compatibility
 39 |     swagger: z
 40 |       .object({
 41 |         url: z.string().url(),
 42 |         apiBaseUrl: z.string().url(),
 43 |         defaultAuth: AuthConfigSchema.optional(),
 44 |       })
 45 |       .optional(),
 46 |     log: z.object({
 47 |       level: z.enum(["debug", "info", "warn", "error"]),
 48 |     }),
 49 |     server: z.object({
 50 |       port: z.number().default(3000),
 51 |       host: z.string().default("0.0.0.0"),
 52 |     }),
 53 |   })
 54 |   .refine(
 55 |     (data) => {
 56 |       // Ensure we have either the new api config or legacy swagger config
 57 |       if (data.api.type === "openapi" && !data.api.openapi && !data.swagger) {
 58 |         return false;
 59 |       }
 60 |       if (data.api.type === "postman" && !data.api.postman) {
 61 |         return false;
 62 |       }
 63 |       return true;
 64 |     },
 65 |     {
 66 |       message:
 67 |         "Configuration must include appropriate API settings based on type",
 68 |     }
 69 |   );
 70 | 
 71 | export type Config = z.infer<typeof ConfigSchema>;
 72 | 
 73 | const defaultConfig: Config = {
 74 |   api: {
 75 |     type: "openapi",
 76 |     openapi: {
 77 |       url: "https://petstore.swagger.io/v2/swagger.json",
 78 |       apiBaseUrl: "https://petstore.swagger.io/v2",
 79 |       defaultAuth: {
 80 |         type: "apiKey",
 81 |         apiKey: "special-key",
 82 |         apiKeyName: "api_key",
 83 |         apiKeyIn: "header",
 84 |       },
 85 |     },
 86 |   },
 87 |   swagger: {
 88 |     url: "https://petstore.swagger.io/v2/swagger.json",
 89 |     apiBaseUrl: "https://petstore.swagger.io/v2",
 90 |     defaultAuth: {
 91 |       type: "apiKey",
 92 |       apiKey: "special-key",
 93 |       apiKeyName: "api_key",
 94 |       apiKeyIn: "header",
 95 |     },
 96 |   },
 97 |   log: {
 98 |     level: "info",
 99 |   },
100 |   server: {
101 |     port: 3000,
102 |     host: "0.0.0.0",
103 |   },
104 | };
105 | 
106 | export async function loadConfig(configPath?: string): Promise<Config> {
107 |   try {
108 |     // If no config path provided, create default config file
109 |     if (!configPath) {
110 |       configPath = path.join(process.cwd(), "config.json");
111 |       // Check if config file exists, if not create it with default values
112 |       try {
113 |         await fs.access(configPath);
114 |       } catch {
115 |         await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
116 |         console.log(`Created default configuration file at ${configPath}`);
117 |       }
118 |     }
119 | 
120 |     const configFile = await fs.readFile(configPath, "utf-8");
121 |     const config = JSON.parse(configFile);
122 | 
123 |     // Handle legacy config migration
124 |     if (config.swagger && !config.api) {
125 |       config.api = {
126 |         type: "openapi",
127 |         openapi: config.swagger,
128 |       };
129 |     }
130 | 
131 |     return ConfigSchema.parse(config);
132 |   } catch (error) {
133 |     if (error instanceof z.ZodError) {
134 |       console.error("Invalid configuration:", error.errors);
135 |     } else {
136 |       console.error("Error loading configuration:", error);
137 |     }
138 |     console.log("Using default configuration");
139 |     return defaultConfig;
140 |   }
141 | }
142 | 
```

--------------------------------------------------------------------------------
/src/simple-stdio.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  2 | import { loadConfig } from "./config.js";
  3 | import { SimpleSwaggerMcpServer } from "./swagger-mcp-simple.js";
  4 | import { SimplePostmanMcpServer } from "./postman-mcp-simple.js";
  5 | 
  6 | async function main() {
  7 |   try {
  8 |     console.error(`[SIMPLE-STDIO] Starting simplified MCP server via stdio...`);
  9 |     console.error(`[SIMPLE-STDIO] Process ID: ${process.pid}`);
 10 |     console.error(`[SIMPLE-STDIO] Working directory: ${process.cwd()}`);
 11 | 
 12 |     // Load configuration
 13 |     console.error(`[SIMPLE-STDIO] Loading configuration...`);
 14 |     const config = await loadConfig();
 15 | 
 16 |     let mcpServer: SimpleSwaggerMcpServer | SimplePostmanMcpServer;
 17 | 
 18 |     // Create and initialize MCP server based on configuration
 19 |     if (config.api.type === "postman") {
 20 |       if (!config.api.postman) {
 21 |         throw new Error(
 22 |           'Postman configuration is required when api.type is "postman"'
 23 |         );
 24 |       }
 25 | 
 26 |       console.error(
 27 |         "[SIMPLE-STDIO] Creating simplified Postman MCP server instance..."
 28 |       );
 29 |       console.error(
 30 |         "✅ Using simplified Postman explorer with only 4 strategic tools!"
 31 |       );
 32 | 
 33 |       mcpServer = new SimplePostmanMcpServer(config.api.postman.defaultAuth);
 34 | 
 35 |       // Load collection
 36 |       const collectionSource =
 37 |         config.api.postman.collectionUrl || config.api.postman.collectionFile;
 38 |       if (!collectionSource) {
 39 |         throw new Error(
 40 |           "Either collectionUrl or collectionFile must be specified for Postman configuration"
 41 |         );
 42 |       }
 43 | 
 44 |       console.error("[SIMPLE-STDIO] Loading Postman collection...");
 45 |       await (mcpServer as SimplePostmanMcpServer).loadCollection(
 46 |         collectionSource
 47 |       );
 48 | 
 49 |       // Load environment if specified
 50 |       const environmentSource =
 51 |         config.api.postman.environmentUrl || config.api.postman.environmentFile;
 52 |       if (environmentSource) {
 53 |         console.error("[SIMPLE-STDIO] Loading Postman environment...");
 54 |         await (mcpServer as SimplePostmanMcpServer).loadEnvironment(
 55 |           environmentSource
 56 |         );
 57 |       }
 58 | 
 59 |       console.error("[SIMPLE-STDIO] Postman collection loaded successfully");
 60 | 
 61 |       console.error(
 62 |         "✅ Simple MCP Server successfully initialized with strategic tools!"
 63 |       );
 64 |       console.error("Now you have only 4 tools instead of hundreds:");
 65 |       console.error(
 66 |         "  1. list_requests - List all available requests in the collection"
 67 |       );
 68 |       console.error(
 69 |         "  2. get_request_details - Get detailed info about specific requests"
 70 |       );
 71 |       console.error("  3. search_requests - Search requests by keyword");
 72 |       console.error(
 73 |         "  4. make_request - Execute any request from the collection"
 74 |       );
 75 |     } else {
 76 |       // Default to OpenAPI/Swagger with simplified tools
 77 |       const openApiConfig = config.api.openapi || config.swagger;
 78 |       if (!openApiConfig) {
 79 |         throw new Error(
 80 |           'OpenAPI configuration is required when api.type is "openapi" or for legacy swagger config'
 81 |         );
 82 |       }
 83 | 
 84 |       console.error(
 85 |         "[SIMPLE-STDIO] Creating simplified OpenAPI MCP server instance..."
 86 |       );
 87 |       mcpServer = new SimpleSwaggerMcpServer(
 88 |         openApiConfig.apiBaseUrl,
 89 |         openApiConfig.defaultAuth && openApiConfig.defaultAuth.type
 90 |           ? (openApiConfig.defaultAuth as any)
 91 |           : undefined
 92 |       );
 93 | 
 94 |       console.error("[SIMPLE-STDIO] Loading OpenAPI specification...");
 95 |       await mcpServer.loadSwaggerSpec(openApiConfig.url);
 96 |       console.error("[SIMPLE-STDIO] OpenAPI specification loaded successfully");
 97 | 
 98 |       console.error(
 99 |         "✅ Simple MCP Server successfully initialized with strategic tools!"
100 |       );
101 |       console.error("Now you have only 4 tools instead of hundreds:");
102 |       console.error("  1. list_endpoints - List all available API endpoints");
103 |       console.error(
104 |         "  2. get_endpoint_details - Get detailed info about specific endpoints"
105 |       );
106 |       console.error("  3. search_endpoints - Search endpoints by keyword");
107 |       console.error("  4. make_api_call - Make actual API calls");
108 |     }
109 | 
110 |     // Get the MCP server instance
111 |     const server = mcpServer.getServer();
112 | 
113 |     // Create stdio transport
114 |     console.error(`[SIMPLE-STDIO] Creating stdio transport...`);
115 |     const transport = new StdioServerTransport();
116 | 
117 |     // Connect the MCP server to stdio transport
118 |     console.error(`[SIMPLE-STDIO] Connecting MCP server to stdio transport...`);
119 |     await server.connect(transport);
120 | 
121 |     console.error(
122 |       "[SIMPLE-STDIO] Simplified MCP server connected via stdio successfully!"
123 |     );
124 |     console.error(
125 |       `[SIMPLE-STDIO] Server is ready and listening for requests...`
126 |     );
127 | 
128 |     // Handle process termination gracefully
129 |     process.on("SIGINT", () => {
130 |       console.error(
131 |         "[SIMPLE-STDIO] Received SIGINT, shutting down gracefully..."
132 |       );
133 |       process.exit(0);
134 |     });
135 | 
136 |     process.on("SIGTERM", () => {
137 |       console.error(
138 |         "[SIMPLE-STDIO] Received SIGTERM, shutting down gracefully..."
139 |       );
140 |       process.exit(0);
141 |     });
142 |   } catch (error) {
143 |     console.error(
144 |       "[SIMPLE-STDIO] Failed to start simplified MCP server:",
145 |       error
146 |     );
147 |     process.exit(1);
148 |   }
149 | }
150 | 
151 | // Handle uncaught exceptions and rejections
152 | process.on("uncaughtException", (error) => {
153 |   console.error("[SIMPLE-STDIO] Uncaught Exception:", error);
154 |   process.exit(1);
155 | });
156 | 
157 | process.on("unhandledRejection", (reason, promise) => {
158 |   console.error(
159 |     "[SIMPLE-STDIO] Unhandled Rejection at:",
160 |     promise,
161 |     "reason:",
162 |     reason
163 |   );
164 |   process.exit(1);
165 | });
166 | 
167 | main().catch((error) => {
168 |   console.error("[SIMPLE-STDIO] Unhandled error:", error);
169 |   process.exit(1);
170 | });
171 | 
```

--------------------------------------------------------------------------------
/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 | 
```

--------------------------------------------------------------------------------
/src/swagger-mcp-simple.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 { Request, Response } from "express";
  7 | import { AuthConfig } from "./types.js";
  8 | 
  9 | export class SimpleSwaggerMcpServer {
 10 |   private mcpServer: McpServer;
 11 |   private swaggerSpec: any = null;
 12 |   private apiBaseUrl: string;
 13 |   private defaultAuth: AuthConfig | undefined;
 14 | 
 15 |   constructor(apiBaseUrl: string, defaultAuth?: AuthConfig) {
 16 |     this.apiBaseUrl = apiBaseUrl;
 17 |     this.defaultAuth = defaultAuth;
 18 |     this.mcpServer = new McpServer({
 19 |       name: "Simple Swagger API MCP Server",
 20 |       version: "1.0.0",
 21 |     });
 22 |   }
 23 | 
 24 |   async loadSwaggerSpec(specUrlOrFile: string) {
 25 |     console.debug("Loading Swagger specification from:", specUrlOrFile);
 26 |     try {
 27 |       this.swaggerSpec = (await SwaggerParser.parse(specUrlOrFile)) as any;
 28 | 
 29 |       const info = this.swaggerSpec.info;
 30 |       console.debug("Loaded Swagger spec:", {
 31 |         title: info.title,
 32 |         version: info.version,
 33 |       });
 34 | 
 35 |       this.mcpServer = new McpServer({
 36 |         name: info.title || "Swagger API Server",
 37 |         version: info.version || "1.0.0",
 38 |         description: info.description || undefined,
 39 |       });
 40 | 
 41 |       await this.registerTools();
 42 |     } catch (error) {
 43 |       console.error("Failed to load Swagger specification:", error);
 44 |       throw error;
 45 |     }
 46 |   }
 47 | 
 48 |   private getAuthHeaders(auth?: AuthConfig): Record<string, string> {
 49 |     const authConfig = auth || this.defaultAuth;
 50 |     if (!authConfig) return {};
 51 | 
 52 |     switch (authConfig.type) {
 53 |       case "basic":
 54 |         if (authConfig.username && authConfig.password) {
 55 |           const credentials = Buffer.from(
 56 |             `${authConfig.username}:${authConfig.password}`
 57 |           ).toString("base64");
 58 |           return { Authorization: `Basic ${credentials}` };
 59 |         }
 60 |         break;
 61 |       case "bearer":
 62 |         if (authConfig.token) {
 63 |           return { Authorization: `Bearer ${authConfig.token}` };
 64 |         }
 65 |         break;
 66 |       case "apiKey":
 67 |         if (authConfig.apiKey && authConfig.apiKeyName) {
 68 |           if (authConfig.apiKeyIn === "header") {
 69 |             return { [authConfig.apiKeyName]: authConfig.apiKey };
 70 |           }
 71 |         }
 72 |         break;
 73 |       case "oauth2":
 74 |         if (authConfig.token) {
 75 |           return { Authorization: `Bearer ${authConfig.token}` };
 76 |         }
 77 |         break;
 78 |     }
 79 |     return {};
 80 |   }
 81 | 
 82 |   private getAuthQueryParams(auth?: AuthConfig): Record<string, string> {
 83 |     const authConfig = auth || this.defaultAuth;
 84 |     if (!authConfig) return {};
 85 | 
 86 |     if (
 87 |       authConfig.type === "apiKey" &&
 88 |       authConfig.apiKey &&
 89 |       authConfig.apiKeyName &&
 90 |       authConfig.apiKeyIn === "query"
 91 |     ) {
 92 |       return { [authConfig.apiKeyName]: authConfig.apiKey };
 93 |     }
 94 | 
 95 |     return {};
 96 |   }
 97 | 
 98 |   private async registerTools() {
 99 |     console.debug("Starting tool registration process");
100 |     if (!this.swaggerSpec || !this.swaggerSpec.paths) {
101 |       console.warn("No paths found in Swagger spec");
102 |       return;
103 |     }
104 | 
105 |     const paths = this.swaggerSpec.paths;
106 |     const totalPaths = Object.keys(paths).length;
107 |     console.debug(`Found ${totalPaths} paths to process`);
108 | 
109 |     // Tool 1: List all available endpoints
110 |     this.mcpServer.tool(
111 |       "list_endpoints",
112 |       "List all available API endpoints with basic information including path, method, summary, and tags",
113 |       {
114 |         input: z.object({
115 |           method: z
116 |             .string()
117 |             .optional()
118 |             .describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
119 |           tag: z.string().optional().describe("Filter by OpenAPI tag"),
120 |           limit: z
121 |             .number()
122 |             .optional()
123 |             .default(50)
124 |             .describe("Maximum number of endpoints to return"),
125 |         }),
126 |       },
127 |       async ({ input }) => {
128 |         const endpoints = [];
129 | 
130 |         for (const [path, pathItem] of Object.entries(paths)) {
131 |           if (!pathItem) continue;
132 | 
133 |           for (const [method, operation] of Object.entries(pathItem as any)) {
134 |             if (method === "$ref" || !operation) continue;
135 | 
136 |             const op = operation as any;
137 |             const operationId = op.operationId || `${method}-${path}`;
138 | 
139 |             // Apply filters
140 |             if (
141 |               input.method &&
142 |               method.toLowerCase() !== input.method.toLowerCase()
143 |             )
144 |               continue;
145 |             if (input.tag && (!op.tags || !op.tags.includes(input.tag)))
146 |               continue;
147 | 
148 |             endpoints.push({
149 |               operationId,
150 |               method: method.toUpperCase(),
151 |               path,
152 |               summary: op.summary || "",
153 |               description: op.description || "",
154 |               tags: op.tags || [],
155 |               deprecated: op.deprecated || false,
156 |             });
157 | 
158 |             if (endpoints.length >= input.limit) break;
159 |           }
160 |           if (endpoints.length >= input.limit) break;
161 |         }
162 | 
163 |         return {
164 |           content: [
165 |             {
166 |               type: "text",
167 |               text: JSON.stringify(
168 |                 {
169 |                   total: endpoints.length,
170 |                   endpoints,
171 |                 },
172 |                 null,
173 |                 2
174 |               ),
175 |             },
176 |           ],
177 |         };
178 |       }
179 |     );
180 | 
181 |     // Tool 2: Get detailed information about a specific endpoint
182 |     this.mcpServer.tool(
183 |       "get_endpoint_details",
184 |       "Get detailed information about a specific API endpoint including parameters, request/response schemas, and authentication requirements",
185 |       {
186 |         input: z.object({
187 |           operationId: z.string().describe("The operation ID of the endpoint"),
188 |           path: z
189 |             .string()
190 |             .optional()
191 |             .describe("The API path (alternative to operationId)"),
192 |           method: z
193 |             .string()
194 |             .optional()
195 |             .describe("The HTTP method (required if using path)"),
196 |         }),
197 |       },
198 |       async ({ input }) => {
199 |         let targetOperation = null;
200 |         let targetPath = "";
201 |         let targetMethod = "";
202 | 
203 |         // Find the operation by operationId or path+method
204 |         for (const [path, pathItem] of Object.entries(paths)) {
205 |           if (!pathItem) continue;
206 | 
207 |           for (const [method, operation] of Object.entries(pathItem as any)) {
208 |             if (method === "$ref" || !operation) continue;
209 | 
210 |             const op = operation as any;
211 |             const operationId = op.operationId || `${method}-${path}`;
212 | 
213 |             if (
214 |               input.operationId === operationId ||
215 |               (input.path === path &&
216 |                 input.method?.toLowerCase() === method.toLowerCase())
217 |             ) {
218 |               targetOperation = op;
219 |               targetPath = path;
220 |               targetMethod = method;
221 |               break;
222 |             }
223 |           }
224 |           if (targetOperation) break;
225 |         }
226 | 
227 |         if (!targetOperation) {
228 |           return {
229 |             content: [
230 |               {
231 |                 type: "text",
232 |                 text: `Endpoint not found. Use list_endpoints to see available endpoints.`,
233 |               },
234 |             ],
235 |           };
236 |         }
237 | 
238 |         // Extract parameter information
239 |         const parameters = (targetOperation.parameters || []).map(
240 |           (param: any) => ({
241 |             name: param.name,
242 |             in: param.in,
243 |             required: param.required || false,
244 |             type: param.schema?.type || param.type,
245 |             description: param.description || "",
246 |             example: param.example || param.schema?.example,
247 |           })
248 |         );
249 | 
250 |         // Extract request body schema
251 |         let requestBody = null;
252 |         if (targetOperation.requestBody) {
253 |           const rb = targetOperation.requestBody;
254 |           const content = rb.content;
255 |           if (content) {
256 |             requestBody = Object.keys(content).map((mediaType) => ({
257 |               mediaType,
258 |               schema: content[mediaType].schema,
259 |               required: rb.required || false,
260 |             }));
261 |           }
262 |         }
263 | 
264 |         // Extract response schemas
265 |         const responses = Object.entries(targetOperation.responses || {}).map(
266 |           ([code, resp]: [string, any]) => ({
267 |             statusCode: code,
268 |             description: resp.description || "",
269 |             schema: resp.content
270 |               ? Object.keys(resp.content).map((mt) => ({
271 |                   mediaType: mt,
272 |                   schema: resp.content[mt].schema,
273 |                 }))
274 |               : null,
275 |           })
276 |         );
277 | 
278 |         return {
279 |           content: [
280 |             {
281 |               type: "text",
282 |               text: JSON.stringify(
283 |                 {
284 |                   operationId:
285 |                     targetOperation.operationId ||
286 |                     `${targetMethod}-${targetPath}`,
287 |                   method: targetMethod.toUpperCase(),
288 |                   path: targetPath,
289 |                   summary: targetOperation.summary || "",
290 |                   description: targetOperation.description || "",
291 |                   tags: targetOperation.tags || [],
292 |                   deprecated: targetOperation.deprecated || false,
293 |                   parameters,
294 |                   requestBody,
295 |                   responses,
296 |                 },
297 |                 null,
298 |                 2
299 |               ),
300 |             },
301 |           ],
302 |         };
303 |       }
304 |     );
305 | 
306 |     // Tool 3: Search endpoints by keyword
307 |     this.mcpServer.tool(
308 |       "search_endpoints",
309 |       "Search API endpoints by keyword in path, summary, description, or tags",
310 |       {
311 |         input: z.object({
312 |           query: z
313 |             .string()
314 |             .describe(
315 |               "Search term to look for in endpoint paths, summaries, descriptions, or tags"
316 |             ),
317 |           limit: z
318 |             .number()
319 |             .optional()
320 |             .default(20)
321 |             .describe("Maximum number of results to return"),
322 |         }),
323 |       },
324 |       async ({ input }) => {
325 |         const results = [];
326 |         const query = input.query.toLowerCase();
327 | 
328 |         for (const [path, pathItem] of Object.entries(paths)) {
329 |           if (!pathItem) continue;
330 | 
331 |           for (const [method, operation] of Object.entries(pathItem as any)) {
332 |             if (method === "$ref" || !operation) continue;
333 | 
334 |             const op = operation as any;
335 |             const operationId = op.operationId || `${method}-${path}`;
336 | 
337 |             // Search in various fields
338 |             const searchText = [
339 |               path,
340 |               op.summary || "",
341 |               op.description || "",
342 |               ...(op.tags || []),
343 |               operationId,
344 |             ]
345 |               .join(" ")
346 |               .toLowerCase();
347 | 
348 |             if (searchText.includes(query)) {
349 |               results.push({
350 |                 operationId,
351 |                 method: method.toUpperCase(),
352 |                 path,
353 |                 summary: op.summary || "",
354 |                 description: op.description || "",
355 |                 tags: op.tags || [],
356 |               });
357 |             }
358 | 
359 |             if (results.length >= input.limit) break;
360 |           }
361 |           if (results.length >= input.limit) break;
362 |         }
363 | 
364 |         return {
365 |           content: [
366 |             {
367 |               type: "text",
368 |               text: JSON.stringify(
369 |                 {
370 |                   query: input.query,
371 |                   total: results.length,
372 |                   results,
373 |                 },
374 |                 null,
375 |                 2
376 |               ),
377 |             },
378 |           ],
379 |         };
380 |       }
381 |     );
382 | 
383 |     // Tool 4: Make API call
384 |     this.mcpServer.tool(
385 |       "make_api_call",
386 |       "Make an API call to any endpoint with the specified parameters and authentication",
387 |       {
388 |         input: z.object({
389 |           operationId: z
390 |             .string()
391 |             .optional()
392 |             .describe("The operation ID of the endpoint"),
393 |           path: z
394 |             .string()
395 |             .optional()
396 |             .describe("The API path (alternative to operationId)"),
397 |           method: z
398 |             .string()
399 |             .optional()
400 |             .describe("The HTTP method (required if using path)"),
401 |           parameters: z
402 |             .record(z.any())
403 |             .optional()
404 |             .describe("Query parameters, path parameters, or form data"),
405 |           body: z
406 |             .any()
407 |             .optional()
408 |             .describe("Request body (for POST, PUT, PATCH requests)"),
409 |           auth: z
410 |             .object({
411 |               type: z
412 |                 .enum(["none", "basic", "bearer", "apiKey", "oauth2"])
413 |                 .default("none"),
414 |               username: z.string().optional(),
415 |               password: z.string().optional(),
416 |               token: z.string().optional(),
417 |               apiKey: z.string().optional(),
418 |               apiKeyName: z.string().optional(),
419 |               apiKeyIn: z.enum(["header", "query"]).optional(),
420 |             })
421 |             .optional()
422 |             .describe("Authentication configuration"),
423 |         }),
424 |       },
425 |       async ({ input }) => {
426 |         // Find the operation
427 |         let targetOperation = null;
428 |         let targetPath = "";
429 |         let targetMethod = "";
430 | 
431 |         for (const [path, pathItem] of Object.entries(paths)) {
432 |           if (!pathItem) continue;
433 | 
434 |           for (const [method, operation] of Object.entries(pathItem as any)) {
435 |             if (method === "$ref" || !operation) continue;
436 | 
437 |             const op = operation as any;
438 |             const operationId = op.operationId || `${method}-${path}`;
439 | 
440 |             if (
441 |               input.operationId === operationId ||
442 |               (input.path === path &&
443 |                 input.method?.toLowerCase() === method.toLowerCase())
444 |             ) {
445 |               targetOperation = op;
446 |               targetPath = path;
447 |               targetMethod = method;
448 |               break;
449 |             }
450 |           }
451 |           if (targetOperation) break;
452 |         }
453 | 
454 |         if (!targetOperation) {
455 |           return {
456 |             content: [
457 |               {
458 |                 type: "text",
459 |                 text: `Endpoint not found. Use list_endpoints to see available endpoints.`,
460 |               },
461 |             ],
462 |           };
463 |         }
464 | 
465 |         try {
466 |           const params = input.parameters || {};
467 |           let url = this.apiBaseUrl + targetPath;
468 | 
469 |           // Handle path parameters
470 |           const pathParams = new Set();
471 |           targetPath.split("/").forEach((segment) => {
472 |             if (segment.startsWith("{") && segment.endsWith("}")) {
473 |               pathParams.add(segment.slice(1, -1));
474 |             }
475 |           });
476 | 
477 |           Object.entries(params).forEach(([key, value]) => {
478 |             if (pathParams.has(key)) {
479 |               url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
480 |             }
481 |           });
482 | 
483 |           // Separate query parameters
484 |           const queryParams = Object.entries(params)
485 |             .filter(([key]) => !pathParams.has(key))
486 |             .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
487 | 
488 |           const headers = this.getAuthHeaders(
489 |             input.auth?.type !== "none" ? (input.auth as AuthConfig) : undefined
490 |           );
491 |           const authQueryParams = this.getAuthQueryParams(
492 |             input.auth?.type !== "none" ? (input.auth as AuthConfig) : undefined
493 |           );
494 | 
495 |           const response = await axios({
496 |             method: targetMethod as string,
497 |             url: url,
498 |             headers,
499 |             data: input.body,
500 |             params: { ...queryParams, ...authQueryParams },
501 |           });
502 | 
503 |           return {
504 |             content: [
505 |               { type: "text", text: JSON.stringify(response.data, null, 2) },
506 |               { type: "text", text: `HTTP Status Code: ${response.status}` },
507 |             ],
508 |           };
509 |         } catch (error) {
510 |           console.error(`Error in API call:`, error);
511 |           if (axios.isAxiosError(error) && error.response) {
512 |             return {
513 |               content: [
514 |                 {
515 |                   type: "text",
516 |                   text: `Error ${error.response.status}: ${JSON.stringify(
517 |                     error.response.data,
518 |                     null,
519 |                     2
520 |                   )}`,
521 |                 },
522 |               ],
523 |             };
524 |           }
525 |           return {
526 |             content: [{ type: "text", text: `Error: ${error}` }],
527 |           };
528 |         }
529 |       }
530 |     );
531 | 
532 |     console.debug(
533 |       "Successfully registered 4 strategic tools for API navigation"
534 |     );
535 |   }
536 | 
537 |   getServer() {
538 |     return this.mcpServer;
539 |   }
540 | }
541 | 
```

--------------------------------------------------------------------------------
/src/postman-mcp-simple.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import axios from "axios";
  4 | import {
  5 |   Collection,
  6 |   Request as PostmanRequest,
  7 |   Item,
  8 |   ItemGroup,
  9 | } from "postman-collection";
 10 | import { Request, Response } from "express";
 11 | import { AuthConfig, ToolInput } from "./types.js";
 12 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
 13 | 
 14 | let transport: SSEServerTransport | null = null;
 15 | 
 16 | export class SimplePostmanMcpServer {
 17 |   private mcpServer: McpServer;
 18 |   private collection: Collection | null = null;
 19 |   private environment: Record<string, any> = {};
 20 |   private defaultAuth: AuthConfig | undefined;
 21 |   private requests: Array<{
 22 |     id: string;
 23 |     name: string;
 24 |     method: string;
 25 |     url: string;
 26 |     description: string;
 27 |     folder: string;
 28 |     request: PostmanRequest;
 29 |   }> = [];
 30 | 
 31 |   constructor(defaultAuth?: AuthConfig) {
 32 |     if (process.env.NODE_ENV !== "production") {
 33 |       console.debug("SimplePostmanMcpServer constructor", defaultAuth);
 34 |     }
 35 |     this.defaultAuth = defaultAuth;
 36 |     this.mcpServer = new McpServer({
 37 |       name: "Simple Postman Collection MCP Server",
 38 |       version: "1.0.0",
 39 |     });
 40 |   }
 41 | 
 42 |   private getAuthHeaders(auth?: AuthConfig): Record<string, string> {
 43 |     const authConfig = auth || this.defaultAuth;
 44 |     if (!authConfig) return {};
 45 | 
 46 |     switch (authConfig.type) {
 47 |       case "basic":
 48 |         if (authConfig.username && authConfig.password) {
 49 |           const credentials = Buffer.from(
 50 |             `${authConfig.username}:${authConfig.password}`
 51 |           ).toString("base64");
 52 |           return { Authorization: `Basic ${credentials}` };
 53 |         }
 54 |         break;
 55 |       case "bearer":
 56 |         if (authConfig.token) {
 57 |           return { Authorization: `Bearer ${authConfig.token}` };
 58 |         }
 59 |         break;
 60 |       case "apiKey":
 61 |         if (
 62 |           authConfig.apiKey &&
 63 |           authConfig.apiKeyName &&
 64 |           authConfig.apiKeyIn === "header"
 65 |         ) {
 66 |           return { [authConfig.apiKeyName]: authConfig.apiKey };
 67 |         }
 68 |         break;
 69 |       case "oauth2":
 70 |         if (authConfig.token) {
 71 |           return { Authorization: `Bearer ${authConfig.token}` };
 72 |         }
 73 |         break;
 74 |     }
 75 |     return {};
 76 |   }
 77 | 
 78 |   private getAuthQueryParams(auth?: AuthConfig): Record<string, string> {
 79 |     const authConfig = auth || this.defaultAuth;
 80 |     if (!authConfig) return {};
 81 | 
 82 |     if (
 83 |       authConfig.type === "apiKey" &&
 84 |       authConfig.apiKey &&
 85 |       authConfig.apiKeyName &&
 86 |       authConfig.apiKeyIn === "query"
 87 |     ) {
 88 |       return { [authConfig.apiKeyName]: authConfig.apiKey };
 89 |     }
 90 | 
 91 |     return {};
 92 |   }
 93 | 
 94 |   private createAuthSchema(): z.ZodType<any> {
 95 |     return z
 96 |       .object({
 97 |         type: z
 98 |           .enum(["none", "basic", "bearer", "apiKey", "oauth2"])
 99 |           .default("none"),
100 |         username: z.string().optional(),
101 |         password: z.string().optional(),
102 |         token: z.string().optional(),
103 |         apiKey: z.string().optional(),
104 |         apiKeyName: z.string().optional(),
105 |         apiKeyIn: z.enum(["header", "query"]).optional(),
106 |       })
107 |       .describe("Authentication configuration for the request");
108 |   }
109 | 
110 |   async loadCollection(collectionUrlOrFile: string) {
111 |     if (process.env.NODE_ENV !== "production") {
112 |       console.debug("Loading Postman collection from:", collectionUrlOrFile);
113 |     }
114 |     try {
115 |       let collectionData: any;
116 | 
117 |       if (collectionUrlOrFile.startsWith("http")) {
118 |         const response = await axios.get(collectionUrlOrFile);
119 |         collectionData = response.data;
120 |       } else {
121 |         const fs = await import("fs/promises");
122 |         const fileContent = await fs.readFile(collectionUrlOrFile, "utf-8");
123 |         collectionData = JSON.parse(fileContent);
124 |       }
125 | 
126 |       this.collection = new Collection(collectionData);
127 | 
128 |       // Get collection info safely
129 |       const info = {
130 |         name:
131 |           (this.collection as any).name ||
132 |           collectionData.info?.name ||
133 |           "Postman Collection",
134 |         description:
135 |           (this.collection as any).description ||
136 |           collectionData.info?.description ||
137 |           "",
138 |         version:
139 |           (this.collection as any).version ||
140 |           collectionData.info?.version ||
141 |           "1.0.0",
142 |       };
143 | 
144 |       if (process.env.NODE_ENV !== "production") {
145 |         console.debug("Loaded Postman collection:", {
146 |           name: info.name,
147 |           description:
148 |             typeof info.description === "string"
149 |               ? info.description.substring(0, 100) + "..."
150 |               : "",
151 |         });
152 |       }
153 | 
154 |       // Update server name with collection info
155 |       this.mcpServer = new McpServer({
156 |         name:
157 |           `${info.name} - Simple Explorer` ||
158 |           "Simple Postman Collection Server",
159 |         version: info.version || "1.0.0",
160 |         description: `Simplified explorer for ${info.name}` || undefined,
161 |       });
162 | 
163 |       // Parse all requests for the strategic tools
164 |       this.parseAllRequests();
165 | 
166 |       await this.registerStrategicTools();
167 |     } catch (error) {
168 |       console.error("Failed to load Postman collection:", error);
169 |       throw error;
170 |     }
171 |   }
172 | 
173 |   async loadEnvironment(environmentUrlOrFile: string) {
174 |     if (process.env.NODE_ENV !== "production") {
175 |       console.debug("Loading Postman environment from:", environmentUrlOrFile);
176 |     }
177 |     try {
178 |       let environmentData: any;
179 | 
180 |       if (environmentUrlOrFile.startsWith("http")) {
181 |         const response = await axios.get(environmentUrlOrFile);
182 |         environmentData = response.data;
183 |       } else {
184 |         const fs = await import("fs/promises");
185 |         const fileContent = await fs.readFile(environmentUrlOrFile, "utf-8");
186 |         environmentData = JSON.parse(fileContent);
187 |       }
188 | 
189 |       // Parse environment variables
190 |       if (environmentData.values) {
191 |         for (const variable of environmentData.values) {
192 |           this.environment[variable.key] = variable.value;
193 |         }
194 |       }
195 | 
196 |       if (process.env.NODE_ENV !== "production") {
197 |         console.debug(
198 |           "Loaded environment variables:",
199 |           Object.keys(this.environment)
200 |         );
201 |       }
202 |     } catch (error) {
203 |       console.error("Failed to load Postman environment:", error);
204 |       throw error;
205 |     }
206 |   }
207 | 
208 |   private parseAllRequests() {
209 |     if (!this.collection) return;
210 | 
211 |     this.requests = [];
212 | 
213 |     const parseItem = (
214 |       item: Item | ItemGroup<Item>,
215 |       folderPath: string = ""
216 |     ) => {
217 |       if (item instanceof Item && item.request) {
218 |         const request = item.request;
219 | 
220 |         // Handle description safely
221 |         let description = "";
222 |         if (item.request.description) {
223 |           if (typeof item.request.description === "string") {
224 |             description = item.request.description;
225 |           } else if (
226 |             typeof item.request.description === "object" &&
227 |             "content" in item.request.description
228 |           ) {
229 |             description = (item.request.description as any).content;
230 |           }
231 |         }
232 | 
233 |         this.requests.push({
234 |           id: `${folderPath}${item.name}`
235 |             .replace(/[^a-zA-Z0-9_]/g, "_")
236 |             .toLowerCase(),
237 |           name: item.name || "Unnamed Request",
238 |           method: request.method || "GET",
239 |           url: request.url?.toString() || "",
240 |           description,
241 |           folder: folderPath,
242 |           request,
243 |         });
244 |       } else if (item instanceof ItemGroup) {
245 |         // Handle folders recursively
246 |         const newFolderPath = folderPath
247 |           ? `${folderPath}/${item.name}`
248 |           : item.name;
249 |         item.items.each((subItem: Item | ItemGroup<Item>) => {
250 |           parseItem(subItem, newFolderPath);
251 |         });
252 |       }
253 |     };
254 | 
255 |     // Parse all items
256 |     this.collection.items.each((item: Item | ItemGroup<Item>) => {
257 |       parseItem(item);
258 |     });
259 | 
260 |     console.log(
261 |       `✅ Parsed ${this.requests.length} requests from Postman collection`
262 |     );
263 |   }
264 | 
265 |   private resolveVariables(text: string): string {
266 |     if (!text) return text;
267 | 
268 |     // Replace {{variableName}} with actual values
269 |     return text.replace(/\{\{(\w+)\}\}/g, (match, variableName) => {
270 |       return this.environment[variableName] || match;
271 |     });
272 |   }
273 | 
274 |   private async registerStrategicTools() {
275 |     // Tool 1: List all requests
276 |     this.mcpServer.tool(
277 |       "list_requests",
278 |       "List all available requests in the Postman collection with basic information",
279 |       {
280 |         input: z.object({
281 |           method: z
282 |             .string()
283 |             .optional()
284 |             .describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
285 |           folder: z.string().optional().describe("Filter by folder/path"),
286 |           limit: z
287 |             .number()
288 |             .optional()
289 |             .default(50)
290 |             .describe("Maximum number of requests to return"),
291 |         }),
292 |       },
293 |       async ({ input }) => {
294 |         let filteredRequests = this.requests;
295 | 
296 |         // Apply filters
297 |         if (input.method) {
298 |           filteredRequests = filteredRequests.filter(
299 |             (req) => req.method.toLowerCase() === input.method!.toLowerCase()
300 |           );
301 |         }
302 | 
303 |         if (input.folder) {
304 |           filteredRequests = filteredRequests.filter((req) =>
305 |             req.folder.toLowerCase().includes(input.folder!.toLowerCase())
306 |           );
307 |         }
308 | 
309 |         // Limit results
310 |         const limitedRequests = filteredRequests.slice(0, input.limit);
311 | 
312 |         return {
313 |           content: [
314 |             {
315 |               type: "text",
316 |               text: JSON.stringify(
317 |                 {
318 |                   total: limitedRequests.length,
319 |                   requests: limitedRequests.map((req) => ({
320 |                     id: req.id,
321 |                     name: req.name,
322 |                     method: req.method,
323 |                     url: req.url,
324 |                     folder: req.folder,
325 |                     description:
326 |                       req.description.substring(0, 100) +
327 |                       (req.description.length > 100 ? "..." : ""),
328 |                   })),
329 |                 },
330 |                 null,
331 |                 2
332 |               ),
333 |             },
334 |           ],
335 |         };
336 |       }
337 |     );
338 | 
339 |     // Tool 2: Get detailed information about a specific request
340 |     this.mcpServer.tool(
341 |       "get_request_details",
342 |       "Get detailed information about a specific request including parameters, headers, and body structure",
343 |       {
344 |         input: z.object({
345 |           requestId: z.string().describe("The ID of the request"),
346 |           name: z
347 |             .string()
348 |             .optional()
349 |             .describe("The name of the request (alternative to ID)"),
350 |         }),
351 |       },
352 |       async ({ input }) => {
353 |         let targetRequest = null;
354 | 
355 |         // Find the request by ID or name
356 |         for (const req of this.requests) {
357 |           if (
358 |             req.id === input.requestId ||
359 |             req.name.toLowerCase() === input.name?.toLowerCase()
360 |           ) {
361 |             targetRequest = req;
362 |             break;
363 |           }
364 |         }
365 | 
366 |         if (!targetRequest) {
367 |           return {
368 |             content: [
369 |               {
370 |                 type: "text",
371 |                 text: `Request not found. Use list_requests to see available requests.`,
372 |               },
373 |             ],
374 |           };
375 |         }
376 | 
377 |         const request = targetRequest.request;
378 | 
379 |         // Extract parameters information
380 |         const parameters = {
381 |           query: [] as any[],
382 |           path: [] as any[],
383 |           headers: [] as any[],
384 |         };
385 | 
386 |         // Query parameters
387 |         if (request.url && request.url.query) {
388 |           request.url.query.each((param: any) => {
389 |             if (param.key && !param.disabled) {
390 |               parameters.query.push({
391 |                 name: param.key,
392 |                 value: param.value || "",
393 |                 description: param.description || "",
394 |               });
395 |             }
396 |           });
397 |         }
398 | 
399 |         // Path variables
400 |         if (request.url && request.url.variables) {
401 |           request.url.variables.each((variable: any) => {
402 |             if (variable.key) {
403 |               parameters.path.push({
404 |                 name: variable.key,
405 |                 value: variable.value || "",
406 |                 description: variable.description || "",
407 |               });
408 |             }
409 |           });
410 |         }
411 | 
412 |         // Headers
413 |         if (request.headers) {
414 |           request.headers.each((header: any) => {
415 |             if (header.key && !header.disabled) {
416 |               parameters.headers.push({
417 |                 name: header.key,
418 |                 value: header.value || "",
419 |                 description: header.description || "",
420 |               });
421 |             }
422 |           });
423 |         }
424 | 
425 |         // Request body info
426 |         let bodyInfo: any = null;
427 |         if (
428 |           request.body &&
429 |           ["POST", "PUT", "PATCH"].includes(request.method || "")
430 |         ) {
431 |           bodyInfo = {
432 |             mode: request.body.mode,
433 |             description: "Request body based on the collection definition",
434 |           } as any;
435 | 
436 |           if (request.body.mode === "raw") {
437 |             bodyInfo.example = request.body.raw || "";
438 |           } else if (request.body.mode === "formdata") {
439 |             bodyInfo.formFields = [];
440 |             if (request.body.formdata) {
441 |               request.body.formdata.each((field: any) => {
442 |                 if (field.key && !field.disabled) {
443 |                   bodyInfo.formFields.push({
444 |                     name: field.key,
445 |                     type: field.type || "text",
446 |                     value: field.value || "",
447 |                     description: field.description || "",
448 |                   });
449 |                 }
450 |               });
451 |             }
452 |           }
453 |         }
454 | 
455 |         return {
456 |           content: [
457 |             {
458 |               type: "text",
459 |               text: JSON.stringify(
460 |                 {
461 |                   id: targetRequest.id,
462 |                   name: targetRequest.name,
463 |                   method: targetRequest.method,
464 |                   url: targetRequest.url,
465 |                   folder: targetRequest.folder,
466 |                   description: targetRequest.description,
467 |                   parameters,
468 |                   body: bodyInfo,
469 |                   auth: "Use the auth parameter in make_request to provide authentication",
470 |                 },
471 |                 null,
472 |                 2
473 |               ),
474 |             },
475 |           ],
476 |         };
477 |       }
478 |     );
479 | 
480 |     // Tool 3: Search requests by keyword
481 |     this.mcpServer.tool(
482 |       "search_requests",
483 |       "Search requests by keyword in name, description, URL, or folder",
484 |       {
485 |         input: z.object({
486 |           query: z
487 |             .string()
488 |             .describe(
489 |               "Search term to look for in request names, descriptions, URLs, or folders"
490 |             ),
491 |           limit: z
492 |             .number()
493 |             .optional()
494 |             .default(20)
495 |             .describe("Maximum number of results to return"),
496 |         }),
497 |       },
498 |       async ({ input }) => {
499 |         const query = input.query.toLowerCase();
500 |         const results = [];
501 | 
502 |         for (const req of this.requests) {
503 |           const searchText = [
504 |             req.name,
505 |             req.description,
506 |             req.url,
507 |             req.folder,
508 |             req.method,
509 |           ]
510 |             .join(" ")
511 |             .toLowerCase();
512 | 
513 |           if (searchText.includes(query)) {
514 |             results.push({
515 |               id: req.id,
516 |               name: req.name,
517 |               method: req.method,
518 |               url: req.url,
519 |               folder: req.folder,
520 |               description:
521 |                 req.description.substring(0, 100) +
522 |                 (req.description.length > 100 ? "..." : ""),
523 |               relevance: this.calculateRelevance(query, searchText),
524 |             });
525 | 
526 |             if (results.length >= input.limit) break;
527 |           }
528 |         }
529 | 
530 |         // Sort by relevance
531 |         results.sort((a, b) => b.relevance - a.relevance);
532 | 
533 |         return {
534 |           content: [
535 |             {
536 |               type: "text",
537 |               text: JSON.stringify(
538 |                 {
539 |                   query: input.query,
540 |                   total: results.length,
541 |                   results: results.map((r) => ({ ...r, relevance: undefined })), // Remove relevance from output
542 |                 },
543 |                 null,
544 |                 2
545 |               ),
546 |             },
547 |           ],
548 |         };
549 |       }
550 |     );
551 | 
552 |     // Tool 4: Make request
553 |     this.mcpServer.tool(
554 |       "make_request",
555 |       "Execute any request from the Postman collection with the specified parameters and authentication",
556 |       {
557 |         input: z.object({
558 |           requestId: z.string().optional().describe("The ID of the request"),
559 |           name: z
560 |             .string()
561 |             .optional()
562 |             .describe("The name of the request (alternative to ID)"),
563 |           parameters: z
564 |             .record(z.any())
565 |             .optional()
566 |             .describe(
567 |               "Query parameters, path parameters, headers, or form data"
568 |             ),
569 |           body: z
570 |             .any()
571 |             .optional()
572 |             .describe("Request body (for POST, PUT, PATCH requests)"),
573 |           auth: this.createAuthSchema()
574 |             .optional()
575 |             .describe("Authentication configuration"),
576 |         }),
577 |       },
578 |       async ({ input }) => {
579 |         // Find the request
580 |         let targetRequest = null;
581 | 
582 |         for (const req of this.requests) {
583 |           if (
584 |             req.id === input.requestId ||
585 |             req.name.toLowerCase() === input.name?.toLowerCase()
586 |           ) {
587 |             targetRequest = req;
588 |             break;
589 |           }
590 |         }
591 | 
592 |         if (!targetRequest) {
593 |           return {
594 |             content: [
595 |               {
596 |                 type: "text",
597 |                 text: `Request not found. Use list_requests to see available requests.`,
598 |               },
599 |             ],
600 |           };
601 |         }
602 | 
603 |         try {
604 |           const result = await this.executeRequest(
605 |             targetRequest.request,
606 |             input.parameters || {},
607 |             input.body,
608 |             input.auth
609 |           );
610 |           return {
611 |             content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
612 |           };
613 |         } catch (error) {
614 |           return {
615 |             content: [
616 |               {
617 |                 type: "text",
618 |                 text: `Error: ${
619 |                   error instanceof Error ? error.message : String(error)
620 |                 }`,
621 |               },
622 |             ],
623 |           };
624 |         }
625 |       }
626 |     );
627 | 
628 |     console.log(
629 |       "✅ Successfully registered 4 strategic tools for Postman collection exploration"
630 |     );
631 |   }
632 | 
633 |   private calculateRelevance(query: string, text: string): number {
634 |     const queryWords = query.split(" ");
635 |     let score = 0;
636 | 
637 |     queryWords.forEach((word) => {
638 |       if (text.includes(word)) {
639 |         score += 1;
640 |         // Bonus for exact word matches
641 |         if (
642 |           text.includes(` ${word} `) ||
643 |           text.startsWith(word) ||
644 |           text.endsWith(word)
645 |         ) {
646 |           score += 0.5;
647 |         }
648 |       }
649 |     });
650 | 
651 |     return score;
652 |   }
653 | 
654 |   private async executeRequest(
655 |     request: PostmanRequest,
656 |     parameters: Record<string, any>,
657 |     body?: any,
658 |     auth?: AuthConfig
659 |   ): Promise<any> {
660 |     // Build URL
661 |     let url = request.url?.toString() || "";
662 |     url = this.resolveVariables(url);
663 | 
664 |     // Replace path variables
665 |     Object.keys(parameters).forEach((key) => {
666 |       const value = parameters[key];
667 |       if (value !== undefined) {
668 |         // Try different variable formats
669 |         url = url.replace(`:${key}`, encodeURIComponent(String(value)));
670 |         url = url.replace(`{{${key}}}`, encodeURIComponent(String(value)));
671 |         url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
672 |       }
673 |     });
674 | 
675 |     // Build query parameters
676 |     const queryParams = this.getAuthQueryParams(auth);
677 |     Object.keys(parameters).forEach((key) => {
678 |       const value = parameters[key];
679 |       if (
680 |         value !== undefined &&
681 |         !url.includes(`:${key}`) &&
682 |         !url.includes(`{{${key}}}`)
683 |       ) {
684 |         queryParams[key] = value;
685 |       }
686 |     });
687 | 
688 |     // Build headers
689 |     const headers = this.getAuthHeaders(auth);
690 | 
691 |     // Add any headers from parameters
692 |     Object.keys(parameters).forEach((key) => {
693 |       const value = parameters[key];
694 |       if (value !== undefined && key.toLowerCase().includes("header")) {
695 |         headers[key] = value;
696 |       }
697 |     });
698 | 
699 |     // Add default content type for requests with body
700 |     if (body && !headers["Content-Type"]) {
701 |       headers["Content-Type"] = "application/json";
702 |     }
703 | 
704 |     // Prepare request configuration
705 |     const config: any = {
706 |       method: request.method || "GET",
707 |       url,
708 |       headers,
709 |       params: queryParams,
710 |     };
711 | 
712 |     // Add body if present
713 |     if (body) {
714 |       if (typeof body === "string") {
715 |         config.data = body;
716 |       } else {
717 |         config.data = JSON.stringify(body);
718 |       }
719 |     }
720 | 
721 |     if (process.env.NODE_ENV !== "production") {
722 |       console.debug("Executing request:", {
723 |         method: config.method,
724 |         url: config.url,
725 |         headers: Object.keys(config.headers),
726 |         hasBody: !!config.data,
727 |       });
728 |     }
729 | 
730 |     try {
731 |       const response = await axios(config);
732 |       return {
733 |         status: response.status,
734 |         statusText: response.statusText,
735 |         headers: response.headers,
736 |         data: response.data,
737 |       };
738 |     } catch (error: any) {
739 |       if (error.response) {
740 |         return {
741 |           status: error.response.status,
742 |           statusText: error.response.statusText,
743 |           headers: error.response.headers,
744 |           data: error.response.data,
745 |           error: true,
746 |         };
747 |       }
748 |       throw error;
749 |     }
750 |   }
751 | 
752 |   getServer() {
753 |     return this.mcpServer;
754 |   }
755 | 
756 |   handleSSE(res: Response) {
757 |     if (!transport) {
758 |       transport = new SSEServerTransport("/messages", res);
759 |     }
760 |     this.mcpServer.connect(transport);
761 |   }
762 | 
763 |   handleMessage(req: Request, res: Response) {
764 |     this.mcpServer.connect(transport!);
765 |   }
766 | }
767 | 
```