Merge 04a2c9c7bfe7e52cab261d790e707af5802b7357 into 8ee9fc383dedff93043ed8a67107ee6691f19642
This commit is contained in:
commit
d113e4086e
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- CLI option `--passthrough-auth` to forward authentication headers from MCP requests to the downstream API, per the OpenAPI security scheme. Supports http (bearer/basic), apiKey (header/query/cookie), and OpenID Connect bearer tokens. Works for SSE and StreamableHTTP transports. Requires `@modelcontextprotocol/sdk` ^1.17.4.
|
||||
|
||||
|
||||
## [3.2.0] - 2025-08-24
|
||||
|
||||
### Added
|
||||
|
||||
53
README.md
53
README.md
@ -48,17 +48,18 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir -
|
||||
|
||||
### CLI Options
|
||||
|
||||
| Option | Alias | Description | Default |
|
||||
| ------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
||||
| `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** |
|
||||
| `--output` | `-o` | Directory to output the generated MCP project | **Required** |
|
||||
| `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` |
|
||||
| `--server-version` | `-v` | Version of the MCP server (`package.json:version`) | OpenAPI version or `1.0.0` |
|
||||
| `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible |
|
||||
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
|
||||
| `--port` | `-p` | Port for web-based transports | `3000` |
|
||||
| `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` |
|
||||
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
|
||||
| Option | Alias | Description | Default |
|
||||
| -------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
||||
| `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** |
|
||||
| `--output` | `-o` | Directory to output the generated MCP project | **Required** |
|
||||
| `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` |
|
||||
| `--server-version` | `-v` | Version of the MCP server (`package.json:version`) | OpenAPI version or `1.0.0` |
|
||||
| `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible |
|
||||
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
|
||||
| `--port` | `-p` | Port for web-based transports | `3000` |
|
||||
| `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` |
|
||||
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
|
||||
| `--passthrough-auth` | | Forward auth headers in MCP requests to the downstream API, as specified by the OpenAPI spec. | `false` |
|
||||
|
||||
## 📦 Programmatic API
|
||||
|
||||
@ -125,6 +126,7 @@ Launches a fully functional HTTP server with:
|
||||
- In-browser test client UI
|
||||
- Multi-connection support
|
||||
- Built with lightweight Hono framework
|
||||
- Optional pass-through auth headers
|
||||
|
||||
### StreamableHTTP
|
||||
|
||||
@ -137,6 +139,7 @@ Implements the MCP StreamableHTTP transport which offers:
|
||||
- Compatibility with MCP StreamableHTTPClientTransport
|
||||
- In-browser test client UI
|
||||
- Built with lightweight Hono framework
|
||||
- Optional pass-through auth headers
|
||||
|
||||
### Transport Comparison
|
||||
|
||||
@ -151,6 +154,7 @@ Implements the MCP StreamableHTTP transport which offers:
|
||||
| Load balancing | No | Limited | Yes |
|
||||
| Status codes | No | Limited | Full HTTP codes |
|
||||
| Headers | No | Limited | Full HTTP headers |
|
||||
| Pass-through Auth | No | Optional | Optional |
|
||||
| Test client | No | Yes | Yes |
|
||||
|
||||
---
|
||||
@ -168,6 +172,33 @@ Configure auth credentials in your environment:
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Pass-through Headers for Authentication
|
||||
|
||||
Use the CLI option `--passthrough-auth` to have the server pass-through client auth headers to the downstream API. The headers forwarded are for the auth schemes defined in the OpenAPI spec. Scheme types http (bearer or basic), apiKey (header, query param, or cookie), and openIdConnect bearer tokens are supported.
|
||||
|
||||
The client should configure the auth credentials to be sent, for example:
|
||||
|
||||
```
|
||||
"mcpServers": {
|
||||
"my-api": {
|
||||
"transport": "HTTP",
|
||||
"url": "http://localhost:3000/sse",
|
||||
"headers": {
|
||||
"Authorization": "Bearer MY_TOKEN"
|
||||
}
|
||||
},
|
||||
"my-other-api": {
|
||||
"transport": "Streamable-HTTP",
|
||||
"url": "http://localhost:4000/mcp",
|
||||
"headers": {
|
||||
"X-API-Key": "MY_API_KEY"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔎 Filtering Endpoints with OpenAPI Extensions
|
||||
|
||||
You can control which operations are exposed as MCP tools using a vendor extension flag `x-mcp`. This extension is supported at the root, path, and operation levels. By default, endpoints are included unless explicitly excluded.
|
||||
|
||||
59
examples/pet-store-sse/package-lock.json
generated
59
examples/pet-store-sse/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "1.0.26",
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.1",
|
||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"axios": "^1.9.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"hono": "^4.7.7",
|
||||
@ -39,15 +39,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
|
||||
"integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
|
||||
"license": "MIT",
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.4.tgz",
|
||||
"integrity": "sha512-zq24hfuAmmlNZvik0FLI58uE5sriN0WWsQzIlYnzSuKDAHFqJtBFrl/LfB1NLgJT5Y7dEBzaX4yAKqOPrcetaw==",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"cross-spawn": "^7.0.5",
|
||||
"eventsource": "^3.0.2",
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
@ -89,6 +90,21 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@ -456,6 +472,16 @@
|
||||
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
||||
@ -717,6 +743,11 @@
|
||||
"json-schema-to-zod": "dist/cjs/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -880,6 +911,14 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
@ -1159,6 +1198,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"axios": "^1.9.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"zod": "^3.24.3",
|
||||
|
||||
59
examples/pet-store-streamable-http/package-lock.json
generated
59
examples/pet-store-streamable-http/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "1.0.26",
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.14.1",
|
||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"axios": "^1.9.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"fetch-to-node": "^2.1.0",
|
||||
@ -40,15 +40,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
|
||||
"integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
|
||||
"license": "MIT",
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.4.tgz",
|
||||
"integrity": "sha512-zq24hfuAmmlNZvik0FLI58uE5sriN0WWsQzIlYnzSuKDAHFqJtBFrl/LfB1NLgJT5Y7dEBzaX4yAKqOPrcetaw==",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"cross-spawn": "^7.0.5",
|
||||
"eventsource": "^3.0.2",
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
@ -90,6 +91,21 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@ -457,6 +473,16 @@
|
||||
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
|
||||
},
|
||||
"node_modules/fetch-to-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-to-node/-/fetch-to-node-2.1.0.tgz",
|
||||
@ -724,6 +750,11 @@
|
||||
"json-schema-to-zod": "dist/cjs/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -887,6 +918,14 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
@ -1166,6 +1205,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"axios": "^1.9.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"zod": "^3.24.3",
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"axios": "^1.8.4",
|
||||
"json-schema-to-zod": "^2.6.1",
|
||||
"zod": "^3.24.2"
|
||||
|
||||
38
package-lock.json
generated
38
package-lock.json
generated
@ -29,7 +29,7 @@
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"json-schema-to-zod": "^2.6.1",
|
||||
"zod": "^3.24.3"
|
||||
}
|
||||
@ -385,16 +385,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
|
||||
"integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
|
||||
"license": "MIT",
|
||||
"version": "1.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.4.tgz",
|
||||
"integrity": "sha512-zq24hfuAmmlNZvik0FLI58uE5sriN0WWsQzIlYnzSuKDAHFqJtBFrl/LfB1NLgJT5Y7dEBzaX4yAKqOPrcetaw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"cross-spawn": "^7.0.5",
|
||||
"eventsource": "^3.0.2",
|
||||
"eventsource-parser": "^3.0.0",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"pkce-challenge": "^5.0.0",
|
||||
@ -406,6 +407,28 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1540,7 +1563,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
@ -2495,7 +2517,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@ -3091,7 +3112,6 @@
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.10.2",
|
||||
"@modelcontextprotocol/sdk": "^1.17.4",
|
||||
"json-schema-to-zod": "^2.6.1",
|
||||
"zod": "^3.24.3"
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ export function generatePackageJson(
|
||||
node: '>=20.0.0',
|
||||
},
|
||||
dependencies: {
|
||||
'@modelcontextprotocol/sdk': '^1.10.0',
|
||||
'@modelcontextprotocol/sdk': '^1.17.4',
|
||||
axios: '^1.9.0',
|
||||
dotenv: '^16.4.5',
|
||||
zod: '^3.24.3',
|
||||
|
||||
@ -39,7 +39,7 @@ export function generateMcpServerCode(
|
||||
);
|
||||
|
||||
// Generate code for request handlers
|
||||
const callToolHandlerCode = generateCallToolHandler();
|
||||
const callToolHandlerCode = generateCallToolHandler(options.passthroughAuth);
|
||||
const listToolsHandlerCode = generateListToolsHandler();
|
||||
|
||||
// Determine which transport to include
|
||||
@ -99,8 +99,12 @@ import {
|
||||
ListToolsRequestSchema,
|
||||
type Tool,
|
||||
type CallToolResult,
|
||||
type CallToolRequest
|
||||
type CallToolRequest,
|
||||
ServerRequest,
|
||||
ServerNotification,
|
||||
IsomorphicHeaders
|
||||
} from "@modelcontextprotocol/sdk/types.js";${transportImport}
|
||||
import { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
||||
|
||||
import { z, ZodError } from 'zod';
|
||||
import { jsonSchemaToZod } from 'json-schema-to-zod';
|
||||
|
||||
@ -19,7 +19,7 @@ import { serve } from '@hono/node-server';
|
||||
import { streamSSE } from 'hono/streaming';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { JSONRPCMessage, JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { JSONRPCMessage, JSONRPCMessageSchema, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { Context } from 'hono';
|
||||
import type { SSEStreamingApi } from 'hono/streaming';
|
||||
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
@ -37,7 +37,7 @@ private messageUrl: string;
|
||||
|
||||
onclose?: () => void;
|
||||
onerror?: (error: Error) => void;
|
||||
onmessage?: (message: JSONRPCMessage) => void;
|
||||
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
|
||||
|
||||
constructor(messageUrl: string, stream: SSEStreamingApi) {
|
||||
this._sessionId = uuid();
|
||||
@ -105,7 +105,7 @@ async handlePostMessage(c: Context): Promise<Response> {
|
||||
|
||||
// Forward to the message handler
|
||||
if (this.onmessage) {
|
||||
this.onmessage(parsedMessage);
|
||||
this.onmessage(parsedMessage, {requestInfo: {headers: c.req.header()}});
|
||||
return c.text('Accepted', 202);
|
||||
} else {
|
||||
return c.text('No message handler defined', 500);
|
||||
|
||||
@ -86,6 +86,7 @@ program
|
||||
},
|
||||
true
|
||||
)
|
||||
.option('--passthrough-auth', 'Pass through authentication headers to the API')
|
||||
.option('--force', 'Overwrite existing files without prompting')
|
||||
.version(pkg.version) // Match package.json version
|
||||
.action((options) => {
|
||||
|
||||
@ -35,6 +35,8 @@ export interface CliOptions {
|
||||
* false = exclude by default unless x-mcp explicitly enables.
|
||||
*/
|
||||
defaultInclude?: boolean;
|
||||
/** Whether to pass through authentication headers to the API. Defaults to false. */
|
||||
passthroughAuth?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -87,16 +87,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
*
|
||||
* @returns Generated code for the call tool handler
|
||||
*/
|
||||
export function generateCallToolHandler(): string {
|
||||
export function generateCallToolHandler(passthroughAuth: boolean | undefined): string {
|
||||
return `
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest): Promise<CallToolResult> => {
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest, extra: RequestHandlerExtra<ServerRequest, ServerNotification>): Promise<CallToolResult> => {
|
||||
const { name: toolName, arguments: toolArgs } = request.params;
|
||||
const toolDefinition = toolDefinitionMap.get(toolName);
|
||||
if (!toolDefinition) {
|
||||
console.error(\`Error: Unknown tool requested: \${toolName}\`);
|
||||
return { content: [{ type: "text", text: \`Error: Unknown tool requested: \${toolName}\` }] };
|
||||
}
|
||||
return await executeApiTool(toolName, toolDefinition, toolArgs ?? {}, securitySchemes);
|
||||
let sessionHeaders = ${passthroughAuth ? 'extra.requestInfo?.headers' : 'undefined'};
|
||||
return await executeApiTool(toolName, toolDefinition, toolArgs ?? {}, securitySchemes, sessionHeaders);
|
||||
});
|
||||
`;
|
||||
}
|
||||
|
||||
@ -208,6 +208,11 @@ export function generateExecuteApiToolFunction(
|
||||
|
||||
// Generate security handling code for checking, applying security
|
||||
const securityCode = `
|
||||
function getHeaderValue(headers: IsomorphicHeaders | undefined, key: string): string | undefined {
|
||||
const value = headers?.[key];
|
||||
return Array.isArray(value) ? value[0] : value;
|
||||
}
|
||||
|
||||
// Apply security requirements if available
|
||||
// Security requirements use OR between array items and AND within each object
|
||||
const appliedSecurity = definition.securityRequirements?.find(req => {
|
||||
@ -218,17 +223,18 @@ export function generateExecuteApiToolFunction(
|
||||
|
||||
// API Key security (header, query, cookie)
|
||||
if (scheme.type === 'apiKey') {
|
||||
return !!process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
return !!(getHeaderValue(sessionHeaders,scheme.name.toLowerCase()) || process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`]);
|
||||
}
|
||||
|
||||
// HTTP security (basic, bearer)
|
||||
if (scheme.type === 'http') {
|
||||
if (scheme.scheme?.toLowerCase() === 'bearer') {
|
||||
return !!process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
return !!(getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ') || process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`]);
|
||||
}
|
||||
else if (scheme.scheme?.toLowerCase() === 'basic') {
|
||||
return !!process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] &&
|
||||
!!process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
return (!!(getHeaderValue(sessionHeaders,'authorization')?.startsWith('Basic ') ||
|
||||
!!process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`] &&
|
||||
!!process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +259,7 @@ export function generateExecuteApiToolFunction(
|
||||
|
||||
// OpenID Connect
|
||||
if (scheme.type === 'openIdConnect') {
|
||||
return !!process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
return !!(getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ') || process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`]);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -268,7 +274,7 @@ export function generateExecuteApiToolFunction(
|
||||
|
||||
// API Key security
|
||||
if (scheme?.type === 'apiKey') {
|
||||
const apiKey = process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
const apiKey = getHeaderValue(sessionHeaders,scheme.name.toLowerCase()) ?? process.env[\`API_KEY_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (apiKey) {
|
||||
if (scheme.in === 'header') {
|
||||
headers[scheme.name.toLowerCase()] = apiKey;
|
||||
@ -288,18 +294,28 @@ export function generateExecuteApiToolFunction(
|
||||
// HTTP security (Bearer or Basic)
|
||||
else if (scheme?.type === 'http') {
|
||||
if (scheme.scheme?.toLowerCase() === 'bearer') {
|
||||
const token = process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (token) {
|
||||
headers['authorization'] = \`Bearer \${token}\`;
|
||||
console.error(\`Applied Bearer token for '\${schemeName}'\`);
|
||||
if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ')) {
|
||||
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
|
||||
console.error(\`Applied Bearer token for '\${schemeName}' from session headers\`);
|
||||
} else {
|
||||
const token = process.env[\`BEARER_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (token) {
|
||||
headers['authorization'] = \`Bearer \${token}\`;
|
||||
console.error(\`Applied Bearer token for '\${schemeName}'\`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (scheme.scheme?.toLowerCase() === 'basic') {
|
||||
const username = process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
const password = process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (username && password) {
|
||||
headers['authorization'] = \`Basic \${Buffer.from(\`\${username}:\${password}\`).toString('base64')}\`;
|
||||
console.error(\`Applied Basic authentication for '\${schemeName}'\`);
|
||||
if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Basic ')) {
|
||||
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
|
||||
console.error(\`Applied Basic authentication for '\${schemeName}' from session headers\`);
|
||||
} else {
|
||||
const username = process.env[\`BASIC_USERNAME_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
const password = process.env[\`BASIC_PASSWORD_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (username && password) {
|
||||
headers['authorization'] = \`Basic \${Buffer.from(\`\${username}:\${password}\`).toString('base64')}\`;
|
||||
console.error(\`Applied Basic authentication for '\${schemeName}'\`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -328,15 +344,19 @@ export function generateExecuteApiToolFunction(
|
||||
}
|
||||
// OpenID Connect
|
||||
else if (scheme?.type === 'openIdConnect') {
|
||||
const token = process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (token) {
|
||||
headers['authorization'] = \`Bearer \${token}\`;
|
||||
console.error(\`Applied OpenID Connect token for '\${schemeName}'\`);
|
||||
|
||||
// List the scopes that were requested, if any
|
||||
const scopes = scopesArray as string[];
|
||||
if (scopes && scopes.length > 0) {
|
||||
console.error(\`Requested scopes: \${scopes.join(', ')}\`);
|
||||
if (getHeaderValue(sessionHeaders,'authorization')?.startsWith('Bearer ')) {
|
||||
headers['authorization'] = getHeaderValue(sessionHeaders,'authorization')!;
|
||||
console.error(\`Applied OpenID Connect token for '\${schemeName}' from session headers\`);
|
||||
} else {
|
||||
const token = process.env[\`OPENID_TOKEN_\${schemeName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}\`];
|
||||
if (token) {
|
||||
headers['authorization'] = \`Bearer \${token}\`;
|
||||
console.error(\`Applied OpenID Connect token for '\${schemeName}'\`);
|
||||
// List the scopes that were requested, if any
|
||||
const scopes = scopesArray as string[];
|
||||
if (scopes && scopes.length > 0) {
|
||||
console.error(\`Requested scopes: \${scopes.join(', ')}\`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,7 +399,8 @@ async function executeApiTool(
|
||||
toolName: string,
|
||||
definition: McpToolDefinition,
|
||||
toolArgs: JsonObject,
|
||||
allSecuritySchemes: Record<string, any>
|
||||
allSecuritySchemes: Record<string, any>,
|
||||
sessionHeaders: IsomorphicHeaders | undefined
|
||||
): Promise<CallToolResult> {
|
||||
try {
|
||||
// Validate arguments against the input schema
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user