├── .gitignore ├── experiments ├── dynamic-remote-mcp-server │ ├── static │ │ ├── img │ │ └── README.md │ ├── img │ │ ├── mcp-login.png │ │ ├── available-tools.png │ │ ├── mcp-inspector-sse-config.png │ │ ├── mcp-inspector-oauth-success.png │ │ ├── claude-does-math-the-fancy-way.png │ │ └── mcp-inspector-successful-tool-call.png │ ├── tsconfig.json │ ├── package.json │ ├── biome.json │ ├── wrangler.jsonc │ ├── src │ │ ├── index.ts │ │ ├── app.ts │ │ └── utils.ts │ ├── .gitignore │ └── README.md └── cloudflare-remote-mcp-demo-analysis │ └── openapi.yaml ├── v3.drawio.png ├── toolflare.drawio.png ├── openapi-to-mcp.drawio.png ├── wrangler.json ├── package.json ├── SPEC.md ├── README.md ├── main.ts └── mcp.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .wrangler 3 | package-lock.json -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/static/img: -------------------------------------------------------------------------------- 1 | ../img -------------------------------------------------------------------------------- /v3.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/v3.drawio.png -------------------------------------------------------------------------------- /toolflare.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/toolflare.drawio.png -------------------------------------------------------------------------------- /openapi-to-mcp.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/openapi-to-mcp.drawio.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/mcp-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/mcp-login.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/available-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/available-tools.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/mcp-inspector-sse-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/mcp-inspector-sse-config.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/mcp-inspector-oauth-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/mcp-inspector-oauth-success.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/claude-does-math-the-fancy-way.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/claude-does-math-the-fancy-way.png -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/img/mcp-inspector-successful-tool-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janwilmake/openapi-to-mcp/HEAD/experiments/dynamic-remote-mcp-server/img/mcp-inspector-successful-tool-call.png -------------------------------------------------------------------------------- /wrangler.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/wrangler@latest/config-schema.json", 3 | "name": "openapi-to-mcp-worker", 4 | "main": "main.ts", 5 | "compatibility_date": "2025-08-20", 6 | "route": { "custom_domain": true, "pattern": "mcp.openapisearch.com" } 7 | } 8 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "lib": ["es2021"], 5 | "jsx": "react-jsx", 6 | "module": "es2022", 7 | "moduleResolution": "Bundler", 8 | "resolveJsonModule": true, 9 | "allowJs": true, 10 | "checkJs": false, 11 | "noEmit": true, 12 | "isolatedModules": true, 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["worker-configuration.d.ts", "src/**/*.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openapi-to-mcp-worker", 3 | "version": "1.0.0", 4 | "main": "main.ts", 5 | "scripts": { 6 | "dev": "wrangler dev", 7 | "deploy": "wrangler deploy", 8 | "types": "curl -o mcp.schema.json https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/2025-03-26/schema.json && json2ts -i mcp.schema.json -o mcp.d.ts --unreachableDefinitions" 9 | }, 10 | "dependencies": { 11 | "yaml": "^2.3.4" 12 | }, 13 | "devDependencies": { 14 | "@cloudflare/workers-types": "^4.20250819.0", 15 | "wrangler": "^3.78.12" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remote-mcp-server", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "deploy": "wrangler deploy", 7 | "dev": "wrangler dev", 8 | "format": "biome format --write", 9 | "lint:fix": "biome lint --fix", 10 | "start": "wrangler dev", 11 | "cf-typegen": "wrangler types" 12 | }, 13 | "devDependencies": { 14 | "marked": "^15.0.7", 15 | "typescript": "^5.5.2", 16 | "workers-mcp": "^0.1.0-3", 17 | "wrangler": "^4.2.0" 18 | }, 19 | "dependencies": { 20 | "@cloudflare/workers-oauth-provider": "^0.0.2", 21 | "@modelcontextprotocol/sdk": "^1.7.0", 22 | "agents": "^0.0.53", 23 | "hono": "^4.7.4", 24 | "zod": "^3.24.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SPEC.md: -------------------------------------------------------------------------------- 1 | based on: https://uithub.com/janwilmake/with-mcp 2 | 3 | openapi-to-mcp-worker that exposes MCPs requirements for any hostname that exposes an openapi.json? 4 | 5 | https://openapimcp.com/{hostname}/mcp 6 | 7 | How it should work: 8 | 9 | - validate hostname to have a valid openapi 10 | - look for `tools/list`, `prompts/list`, and `resources/list` operations and proxy to these. If none available, all operations are deemed tools. 11 | - check if openapi has authorization. if so, find first operation that requires it, and call it and respond with its response incase we get a 401. incase that doesn't include www-authenticate, add it with `resource_metadata="${url.origin}/.well-known/oauth-protected-resource"` 12 | 13 | 14 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.6.2/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "files": { 7 | "ignore": ["worker-configuration.d.ts"] 8 | }, 9 | "vcs": { 10 | "enabled": true, 11 | "clientKind": "git", 12 | "useIgnoreFile": true 13 | }, 14 | "linter": { 15 | "enabled": true, 16 | "rules": { 17 | "recommended": true, 18 | "suspicious": { 19 | "noExplicitAny": "off", 20 | "noDebugger": "off", 21 | "noConsoleLog": "off", 22 | "noConfusingVoidType": "off" 23 | }, 24 | "style": { 25 | "noNonNullAssertion": "off" 26 | } 27 | } 28 | }, 29 | "formatter": { 30 | "enabled": true, 31 | "indentWidth": 4, 32 | "lineWidth": 100 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/wrangler.jsonc: -------------------------------------------------------------------------------- 1 | /** 2 | * For more details on how to configure Wrangler, refer to: 3 | * https://developers.cloudflare.com/workers/wrangler/configuration/ 4 | */ 5 | { 6 | "$schema": "node_modules/wrangler/config-schema.json", 7 | "name": "remote-mcp-server", 8 | "main": "src/index.ts", 9 | "compatibility_date": "2025-03-10", 10 | "compatibility_flags": ["nodejs_compat"], 11 | "migrations": [ 12 | { 13 | "new_sqlite_classes": ["MyMCP"], 14 | "tag": "v1" 15 | } 16 | ], 17 | "durable_objects": { 18 | "bindings": [ 19 | { 20 | "class_name": "MyMCP", 21 | "name": "MCP_OBJECT" 22 | } 23 | ] 24 | }, 25 | "kv_namespaces": [ 26 | { 27 | "binding": "OAUTH_KV", 28 | "id": "" 29 | } 30 | ], 31 | "observability": { 32 | "enabled": true 33 | }, 34 | "assets": { "directory": "./static/", "binding": "ASSETS" } 35 | } 36 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/src/index.ts: -------------------------------------------------------------------------------- 1 | import app from "./app"; 2 | import { McpAgent } from "agents/mcp"; 3 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 4 | import { z } from "zod"; 5 | import OAuthProvider from "@cloudflare/workers-oauth-provider"; 6 | 7 | /** NB: This is the exported durable object!!! */ 8 | export class MyMCP extends McpAgent { 9 | // I cannot easily define the McpServer dynamically as this is a static value in the McpAgent. Sunil? 10 | server: McpServer | undefined = undefined; 11 | 12 | async init() { 13 | // Fetch the name asynchronously 14 | const response = await fetch("https://your-api.com/openapi.json"); 15 | const { 16 | info: { name, version }, 17 | } = await response.json(); 18 | 19 | this.server = new McpServer( 20 | { name, version }, 21 | // The idea would be to add this in dynamically based on the OpenAPI spec 22 | //{ capabilities, enforceStrictCapabilities, instructions }, 23 | ); 24 | 25 | // tool executions are initialized here 26 | this.server.tool( 27 | "add", 28 | { a: z.number(), b: z.number() }, 29 | async ({ a, b }) => ({ 30 | content: [{ type: "text", text: String(a + b) }], 31 | }), 32 | ); 33 | } 34 | } 35 | 36 | export default { 37 | fetch: (request: Request, env: any, ctx: ExecutionContext) => { 38 | const provider = new OAuthProvider({ 39 | apiRoute: "/sse", 40 | // We could change the routes here. 41 | // TODO: fix these types 42 | // @ts-ignore 43 | apiHandler: MyMCP.mount("/sse"), 44 | // @ts-ignore 45 | defaultHandler: app, 46 | authorizeEndpoint: "/authorize", 47 | tokenEndpoint: "/token", 48 | clientRegistrationEndpoint: "/register", 49 | }); 50 | 51 | return provider.fetch(request, env, ctx); 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .nx 4 | .idea 5 | .vscode 6 | .zed 7 | # Logs 8 | 9 | logs 10 | _.log 11 | npm-debug.log_ 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .pnpm-debug.log* 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | \*.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | \*.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | \*.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | \*.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .cache 112 | .parcel-cache 113 | 114 | # Next.js build output 115 | 116 | .next 117 | out 118 | 119 | # Nuxt.js build / generate output 120 | 121 | .nuxt 122 | dist 123 | 124 | # Gatsby files 125 | 126 | .cache/ 127 | 128 | # Comment in the public line in if your project uses Gatsby and not Next.js 129 | 130 | # https://nextjs.org/blog/next-9-1#public-directory-support 131 | 132 | # public 133 | 134 | # vuepress build output 135 | 136 | .vuepress/dist 137 | 138 | # vuepress v2.x temp and cache directory 139 | 140 | .temp 141 | .cache 142 | 143 | # Docusaurus cache and generated files 144 | 145 | .docusaurus 146 | 147 | # Serverless directories 148 | 149 | .serverless/ 150 | 151 | # FuseBox cache 152 | 153 | .fusebox/ 154 | 155 | # DynamoDB Local files 156 | 157 | .dynamodb/ 158 | 159 | # TernJS port file 160 | 161 | .tern-port 162 | 163 | # Stores VSCode versions used for testing VSCode extensions 164 | 165 | .vscode-test 166 | 167 | # yarn v2 168 | 169 | .yarn/cache 170 | .yarn/unplugged 171 | .yarn/build-state.yml 172 | .yarn/install-state.gz 173 | .pnp.\* 174 | 175 | # wrangler project 176 | 177 | .dev.vars 178 | .wrangler/ 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI to MCP 2 | 3 | [Thread](https://x.com/janwilmake/status/1965060951444508938) 4 | 5 | ![](v3.drawio.png) 6 | 7 | OpenAPI-to-MCP automatically discovers OpenAPI specs, builds proper MCP configurations, and proxies requests with full parameter handling and authentication support! 8 | 9 | To test, run `npx @modelcontextprotocol/inspector` and use any API as MCP by passing url https://mcp.openapisearch.com/{hostname}/mcp 10 | 11 | Please note, the openapi can provide a filter to the tools used for MCP by providing `info.x-mcp` with: 12 | 13 | ```ts 14 | type MCPConfig = { 15 | /** defaults to 2025-03-26 */ 16 | protocolVersion?: string; 17 | /** GET endpoint that returns MCP-compatible 401 if authentication isn't valid. 18 | * 19 | * e.g. '/me' 20 | * 21 | * Will be used before responding with "initialize" and '.../list' methods 22 | */ 23 | authEndpoint?: string; 24 | serverInfo?: { 25 | name: string; 26 | version: string; 27 | }; 28 | promptOperationIds?: string[]; 29 | toolOperationIds?: string[]; 30 | resourceOperationIds?: string[]; 31 | }; 32 | ``` 33 | 34 | For example: 35 | 36 | ```json 37 | { 38 | "openapi": "3.0.3", 39 | "info": { 40 | "x-mcp": { 41 | "protocolVersion": "2025-03-26", 42 | // this would only enable the myOperation operation, not the other ones 43 | "toolOperationIds": ["myOperation"] 44 | } 45 | //... other info 46 | } 47 | "paths": { 48 | "/some/operation":{ 49 | "get": { 50 | "operationId": "myOperation", 51 | //...config 52 | } 53 | } 54 | ///..... 55 | }, 56 | //..... 57 | } 58 | ``` 59 | 60 | Also, please note that it is expected that IF the OpenAPI server exposes OAUTH, it follows the MCP recommenation/specification: https://modelcontextprotocol.io/specification/draft/basic/authorization. 61 | 62 | Example: https://mcp.openapisearch.com/curlmcp.com/mcp 63 | 64 | Should be the same as `https://curlmcp.com/mcp` since it's based on https://curlmcp.com/openapi.json 65 | 66 | ## Earlier attempt: 67 | 68 | ![](openapi-to-mcp.drawio.png) 69 | 70 | https://x.com/janwilmake/status/1913196601679523922 71 | 72 | ![](toolflare.drawio.png) 73 | 74 | https://x.com/janwilmake/status/1913660585356501195 75 | 76 | SPECS 77 | 78 | - Swagger (openapi 2) - https://docs.swagger.io/spec.html 79 | - Openapi 3 https://raw.githubusercontent.com/OAI/OpenAPI-Specification/refs/heads/main/versions/3.1.1.md 80 | - Moonwalk (openapi 4) https://github.com/OAI/sig-moonwalk 81 | - Arazzo specification - https://raw.githubusercontent.com/OAI/Arazzo-Specification/refs/heads/main/schemas/v1.0/schema.json 82 | - A2A (Google) https://github.com/google/A2A/blob/main/specification/json/a2a.json 83 | - Agents.json - https://raw.githubusercontent.com/wild-card-ai/agents-json/refs/heads/master/agents_json/agentsJson.schema.json 84 | - MCP (Anthropic) - https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/2025-03-26/schema.json 85 | - OAuth2 Server Metadata https://datatracker.ietf.org/doc/html/rfc8414 86 | 87 | Idea: 88 | 89 | - Improve openapisearch such that it tracks used openapi specs found through the MCP (see https://github.com/janwilmake/openapisearch) 90 | - Create remote MCP-server for each OpenAPI (this repo) 91 | - Create remote A2A-server for each OpenAPI (not started) 92 | - Set this up in a modular way such that others can easily contribute other specs (not started) 93 | 94 | [Experiments](experiments): 95 | 96 | - `cloudflare-remote-mcp-demo-analysis`: took the code from https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-server and some dependencies to prompt its spec 97 | - `dynamic-remote-mcp-server` adaptation of https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-server trying to make that dynamic 98 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import { 3 | layout, 4 | homeContent, 5 | parseApproveFormBody, 6 | renderAuthorizationRejectedContent, 7 | renderAuthorizationApprovedContent, 8 | renderLoggedInAuthorizeScreen, 9 | renderLoggedOutAuthorizeScreen, 10 | } from "./utils"; 11 | import type { OAuthHelpers } from "@cloudflare/workers-oauth-provider"; 12 | 13 | export type Bindings = Env & { 14 | OAUTH_PROVIDER: OAuthHelpers; 15 | }; 16 | 17 | const app = new Hono<{ 18 | Bindings: Bindings; 19 | }>(); 20 | 21 | // Render a basic homepage placeholder to make sure the app is up 22 | app.get("/", async (c) => { 23 | const content = await homeContent(c.req.raw); 24 | return c.html(layout(content, "MCP Remote Auth Demo - Home")); 25 | }); 26 | 27 | // Render an authorization page 28 | // If the user is logged in, we'll show a form to approve the appropriate scopes 29 | // If the user is not logged in, we'll show a form to both login and approve the scopes 30 | app.get("/authorize", async (c) => { 31 | // We don't have an actual auth system, so to demonstrate both paths, you can 32 | // hard-code whether the user is logged in or not. We'll default to true 33 | // const isLoggedIn = false; 34 | const isLoggedIn = true; 35 | 36 | const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw); 37 | 38 | const oauthScopes = [ 39 | { 40 | name: "read_profile", 41 | description: "Read your basic profile information", 42 | }, 43 | { name: "read_data", description: "Access your stored data" }, 44 | { name: "write_data", description: "Create and modify your data" }, 45 | ]; 46 | 47 | if (isLoggedIn) { 48 | const content = await renderLoggedInAuthorizeScreen(oauthScopes, oauthReqInfo); 49 | return c.html(layout(content, "MCP Remote Auth Demo - Authorization")); 50 | } 51 | 52 | const content = await renderLoggedOutAuthorizeScreen(oauthScopes, oauthReqInfo); 53 | return c.html(layout(content, "MCP Remote Auth Demo - Authorization")); 54 | }); 55 | 56 | // The /authorize page has a form that will POST to /approve 57 | // This endpoint is responsible for validating any login information and 58 | // then completing the authorization request with the OAUTH_PROVIDER 59 | app.post("/approve", async (c) => { 60 | const { action, oauthReqInfo, email, password } = await parseApproveFormBody( 61 | await c.req.parseBody(), 62 | ); 63 | 64 | if (!oauthReqInfo) { 65 | return c.html("INVALID LOGIN", 401); 66 | } 67 | 68 | // If the user needs to both login and approve, we should validate the login first 69 | if (action === "login_approve") { 70 | // We'll allow any values for email and password for this demo 71 | // but you could validate them here 72 | // Ex: 73 | // if (email !== "user@example.com" || password !== "password") { 74 | // biome-ignore lint/correctness/noConstantCondition: This is a demo 75 | if (false) { 76 | return c.html( 77 | layout( 78 | await renderAuthorizationRejectedContent("/"), 79 | "MCP Remote Auth Demo - Authorization Status", 80 | ), 81 | ); 82 | } 83 | } 84 | 85 | // The user must be successfully logged in and have approved the scopes, so we 86 | // can complete the authorization request 87 | const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ 88 | request: oauthReqInfo, 89 | userId: email, 90 | metadata: { 91 | label: "Test User", 92 | }, 93 | scope: oauthReqInfo.scope, 94 | props: { 95 | userEmail: email, 96 | }, 97 | }); 98 | 99 | return c.html( 100 | layout( 101 | await renderAuthorizationApprovedContent(redirectTo), 102 | "MCP Remote Auth Demo - Authorization Status", 103 | ), 104 | ); 105 | }); 106 | 107 | export default app; 108 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/README.md: -------------------------------------------------------------------------------- 1 | # Remote MCP Server on Cloudflare 2 | 3 | Let's get a remote MCP server up-and-running on Cloudflare Workers complete with OAuth login! 4 | 5 | ## Develop locally 6 | 7 | ```bash 8 | # clone the repository 9 | git clone git@github.com:cloudflare/ai.git 10 | 11 | # install dependencies 12 | cd ai 13 | npm install 14 | 15 | # run locally 16 | npx nx dev remote-mcp-server 17 | ``` 18 | 19 | You should be able to open [`http://localhost:8787/`](http://localhost:8787/) in your browser 20 | 21 | ## Connect the MCP inspector to your server 22 | 23 | To explore your new MCP api, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector). 24 | 25 | - Start it with `npx @modelcontextprotocol/inspector` 26 | - [Within the inspector](http://localhost:5173), switch the Transport Type to `SSE` and enter `http://localhost:8787/sse` as the URL of the MCP server to connect to, and click "Connect" 27 | - You will navigate to a (mock) user/password login screen. Input any email and pass to login. 28 | - You should be redirected back to the MCP Inspector and you can now list and call any defined tools! 29 | 30 |
31 | MCP Inspector with the above config 32 |
33 | 34 |
35 | MCP Inspector with after a tool call 36 |
37 | 38 | ## Connect Claude Desktop to your local MCP server 39 | 40 | The MCP inspector is great, but we really want to connect this to Claude! Follow [Anthropic's Quickstart](https://modelcontextprotocol.io/quickstart/user) and within Claude Desktop go to Settings > Developer > Edit Config to find your configuration file. 41 | 42 | Open the file in your text editor and replace it with this configuration: 43 | 44 | ```json 45 | { 46 | "mcpServers": { 47 | "math": { 48 | "command": "npx", 49 | "args": [ 50 | "mcp-remote", 51 | "http://localhost:8787/sse" 52 | ] 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | This will run a local proxy and let Claude talk to your MCP server over HTTP 59 | 60 | When you open Claude a browser window should open and allow you to login. You should see the tools available in the bottom right. Given the right prompt Claude should ask to call the tool. 61 | 62 |
63 | Clicking on the hammer icon shows a list of available tools 64 |
65 | 66 |
67 | Claude answers the prompt 'I seem to have lost my calculator and have run out of fingers. Could you use the math tool to add 23 and 19?' by invoking the MCP add tool 68 |
69 | 70 | ## Deploy to Cloudflare 71 | 72 | 1. `npx wrangler kv namespace create OAUTH_KV` 73 | 2. Follow the guidance to add the kv namespace ID to `wrangler.jsonc` 74 | 3. `npm run deploy` 75 | 76 | ## Call your newly deployed remote MCP server from a remote MCP client 77 | 78 | Just like you did above in "Develop locally", run the MCP inspector: 79 | 80 | `npx @modelcontextprotocol/inspector@latest` 81 | 82 | Then enter the `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) of your Worker in the inspector as the URL of the MCP server to connect to, and click "Connect". 83 | 84 | You've now connected to your MCP server from a remote MCP client. 85 | 86 | ## Connect Claude Desktop to your remote MCP server 87 | 88 | Update the Claude configuration file to point to your `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) and restart Claude 89 | 90 | ```json 91 | { 92 | "mcpServers": { 93 | "math": { 94 | "command": "npx", 95 | "args": [ 96 | "mcp-remote", 97 | "https://worker-name.account-name.workers.dev/sse" 98 | ] 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ## Debugging 105 | 106 | Should anything go wrong it can be helpful to restart Claude, or to try connecting directly to your 107 | MCP server on the command line with the following command. 108 | 109 | ```bash 110 | npx mcp-remote http://localhost:8787/sse 111 | ``` 112 | 113 | In some rare cases it may help to clear the files added to `~/.mcp-auth` 114 | 115 | ```bash 116 | rm -rf ~/.mcp-auth 117 | ``` 118 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/static/README.md: -------------------------------------------------------------------------------- 1 | # Remote MCP Server on Cloudflare 2 | 3 | Let's get a remote MCP server up-and-running on Cloudflare Workers complete with OAuth login! 4 | 5 | ## Develop locally 6 | 7 | ```bash 8 | # clone the repository 9 | git clone git@github.com:cloudflare/ai.git 10 | 11 | # install dependencies 12 | cd ai 13 | npm install 14 | 15 | # run locally 16 | npx nx dev remote-mcp-server 17 | ``` 18 | 19 | You should be able to open [`http://localhost:8787/`](http://localhost:8787/) in your browser 20 | 21 | ## Connect the MCP inspector to your server 22 | 23 | To explore your new MCP api, you can use the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector). 24 | 25 | - Start it with `npx @modelcontextprotocol/inspector` 26 | - [Within the inspector](http://localhost:5173), switch the Transport Type to `SSE` and enter `http://localhost:8787/sse` as the URL of the MCP server to connect to, and click "Connect" 27 | - You will navigate to a (mock) user/password login screen. Input any email and pass to login. 28 | - You should be redirected back to the MCP Inspector and you can now list and call any defined tools! 29 | 30 |
31 | MCP Inspector with the above config 32 |
33 | 34 |
35 | MCP Inspector with after a tool call 36 |
37 | 38 | ## Connect Claude Desktop to your local MCP server 39 | 40 | The MCP inspector is great, but we really want to connect this to Claude! Follow [Anthropic's Quickstart](https://modelcontextprotocol.io/quickstart/user) and within Claude Desktop go to Settings > Developer > Edit Config to find your configuration file. 41 | 42 | Open the file in your text editor and replace it with this configuration: 43 | 44 | ```json 45 | { 46 | "mcpServers": { 47 | "math": { 48 | "command": "npx", 49 | "args": [ 50 | "mcp-remote", 51 | "http://localhost:8787/sse" 52 | ] 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | This will run a local proxy and let Claude talk to your MCP server over HTTP 59 | 60 | When you open Claude a browser window should open and allow you to login. You should see the tools available in the bottom right. Given the right prompt Claude should ask to call the tool. 61 | 62 |
63 | Clicking on the hammer icon shows a list of available tools 64 |
65 | 66 |
67 | Claude answers the prompt 'I seem to have lost my calculator and have run out of fingers. Could you use the math tool to add 23 and 19?' by invoking the MCP add tool 68 |
69 | 70 | ## Deploy to Cloudflare 71 | 72 | 1. `npx wrangler@latest kv namespace create remote-mcp-server-oauth-kv` 73 | 2. Follow the guidance to add the kv namespace ID to `wrangler.jsonc` 74 | 3. `npm run deploy` 75 | 76 | ## Call your newly deployed remote MCP server from a remote MCP client 77 | 78 | Just like you did above in "Develop locally", run the MCP inspector: 79 | 80 | `npx @modelcontextprotocol/inspector@latest` 81 | 82 | Then enter the `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) of your Worker in the inspector as the URL of the MCP server to connect to, and click "Connect". 83 | 84 | You've now connected to your MCP server from a remote MCP client. 85 | 86 | ## Connect Claude Desktop to your remote MCP server 87 | 88 | Update the Claude configuration file to point to your `workers.dev` URL (ex: `worker-name.account-name.workers.dev/sse`) and restart Claude 89 | 90 | ```json 91 | { 92 | "mcpServers": { 93 | "math": { 94 | "command": "npx", 95 | "args": [ 96 | "mcp-remote", 97 | "https://worker-name.account-name.workers.dev/sse" 98 | ] 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ## Debugging 105 | 106 | Should anything go wrong it can be helpful to restart Claude, or to try connecting directly to your 107 | MCP server on the command line with the following command. 108 | 109 | ```bash 110 | npx mcp-remote http://localhost:8787/sse 111 | ``` 112 | 113 | In some rare cases it may help to clear the files added to `~/.mcp-auth` 114 | 115 | ```bash 116 | rm -rf ~/.mcp-auth 117 | ``` 118 | -------------------------------------------------------------------------------- /experiments/dynamic-remote-mcp-server/src/utils.ts: -------------------------------------------------------------------------------- 1 | // Helper to generate the layout 2 | import { html, raw } from "hono/html"; 3 | import type { HtmlEscapedString } from "hono/utils/html"; 4 | import { marked } from "marked"; 5 | import type { AuthRequest } from "@cloudflare/workers-oauth-provider"; 6 | import { env } from "cloudflare:workers"; 7 | 8 | // This file mainly exists as a dumping ground for uninteresting html and CSS 9 | // to remove clutter and noise from the auth logic. You likely do not need 10 | // anything from this file. 11 | 12 | export const layout = (content: HtmlEscapedString | string, title: string) => html` 13 | 14 | 15 | 16 | 17 | 21 | ${title} 22 | 23 | 40 | 147 | 148 | 151 |
152 |
155 | MCP Remote Auth Demo 160 |
161 |
162 |
163 | ${content} 164 |
165 | 173 | 174 | 175 | `; 176 | 177 | export const homeContent = async (req: Request): Promise => { 178 | // We have the README symlinked into the static directory, so we can fetch it 179 | // and render it into HTML 180 | const origin = new URL(req.url).origin; 181 | const res = await env.ASSETS.fetch(`${origin}/README.md`); 182 | const markdown = await res.text(); 183 | const content = await marked(markdown); 184 | return html` 185 |
${raw(content)}
186 | `; 187 | }; 188 | 189 | export const renderLoggedInAuthorizeScreen = async ( 190 | oauthScopes: { name: string; description: string }[], 191 | oauthReqInfo: AuthRequest, 192 | ) => { 193 | return html` 194 |
195 |

196 | Authorization Request 197 |

198 | 199 |
200 |

201 | MCP Remote Auth Demo would like permission to: 202 |

203 |
    204 | ${oauthScopes.map( 205 | (scope) => html` 206 |
  • 207 | 211 |
    212 |

    ${scope.name}

    213 |

    214 | ${scope.description} 215 |

    216 |
    217 |
  • 218 | `, 219 | )} 220 |
221 |
222 |
223 | 228 | 229 | 237 | 245 |
246 |
247 | `; 248 | }; 249 | 250 | export const renderLoggedOutAuthorizeScreen = async ( 251 | oauthScopes: { name: string; description: string }[], 252 | oauthReqInfo: AuthRequest, 253 | ) => { 254 | return html` 255 |
256 |

257 | Authorization Request 258 |

259 | 260 |
261 |

262 | MCP Remote Auth Demo would like permission to: 263 |

264 |
    265 | ${oauthScopes.map( 266 | (scope) => html` 267 |
  • 268 | 272 |
    273 |

    ${scope.name}

    274 |

    275 | ${scope.description} 276 |

    277 |
    278 |
  • 279 | `, 280 | )} 281 |
282 |
283 |
284 | 289 |
290 |
291 | 296 | 303 |
304 |
305 | 310 | 317 |
318 |
319 | 327 | 335 |
336 |
337 | `; 338 | }; 339 | 340 | export const renderApproveContent = async ( 341 | message: string, 342 | status: string, 343 | redirectUrl: string, 344 | ) => { 345 | return html` 346 |
349 |
350 | 357 | ${status === "success" ? "✓" : "✗"} 358 | 359 |
360 |

361 | ${message} 362 |

363 |

364 | You will be redirected back to the application shortly. 365 |

366 | 370 | Return to Home 371 | 372 | ${raw(` 373 | 378 | `)} 379 |
380 | `; 381 | }; 382 | 383 | export const renderAuthorizationApprovedContent = async (redirectUrl: string) => { 384 | return renderApproveContent("Authorization approved!", "success", redirectUrl); 385 | }; 386 | 387 | export const renderAuthorizationRejectedContent = async (redirectUrl: string) => { 388 | return renderApproveContent("Authorization rejected.", "error", redirectUrl); 389 | }; 390 | 391 | export const parseApproveFormBody = async (body: { 392 | [x: string]: string | File; 393 | }) => { 394 | const action = body.action as string; 395 | const email = body.email as string; 396 | const password = body.password as string; 397 | let oauthReqInfo: AuthRequest | null = null; 398 | try { 399 | oauthReqInfo = JSON.parse(body.oauthReqInfo as string) as AuthRequest; 400 | } catch (e) { 401 | oauthReqInfo = null; 402 | } 403 | 404 | return { action, oauthReqInfo, email, password }; 405 | }; 406 | -------------------------------------------------------------------------------- /experiments/cloudflare-remote-mcp-demo-analysis/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | title: "OAuth Provider with MCP Integration" 4 | version: "1.0.0" 5 | description: "API specification for an OAuth 2.0 provider with Model Context Protocol (MCP) integration" 6 | 7 | servers: 8 | - url: "https://api.example.com" 9 | description: "API Server" 10 | 11 | paths: 12 | /: 13 | get: 14 | summary: "Homepage" 15 | description: "Renders the README content as the homepage" 16 | responses: 17 | "200": 18 | description: "Successful response" 19 | content: 20 | text/html: 21 | schema: 22 | type: "string" 23 | 24 | /authorize: 25 | get: 26 | summary: "OAuth Authorization Endpoint" 27 | description: "Initiates OAuth authorization flow and displays the authorization screen" 28 | parameters: 29 | - name: "response_type" 30 | in: "query" 31 | required: true 32 | schema: 33 | type: "string" 34 | enum: ["code", "token"] 35 | description: "OAuth response type (code for authorization code flow, token for implicit flow)" 36 | - name: "client_id" 37 | in: "query" 38 | required: true 39 | schema: 40 | type: "string" 41 | description: "Client identifier" 42 | - name: "redirect_uri" 43 | in: "query" 44 | required: true 45 | schema: 46 | type: "string" 47 | format: "uri" 48 | description: "URI to redirect to after authorization" 49 | - name: "scope" 50 | in: "query" 51 | required: false 52 | schema: 53 | type: "string" 54 | description: "Space-separated list of requested permission scopes" 55 | - name: "state" 56 | in: "query" 57 | required: false 58 | schema: 59 | type: "string" 60 | description: "Client state value to be returned in the redirect" 61 | - name: "code_challenge" 62 | in: "query" 63 | required: false 64 | schema: 65 | type: "string" 66 | description: "PKCE code challenge" 67 | - name: "code_challenge_method" 68 | in: "query" 69 | required: false 70 | schema: 71 | type: "string" 72 | enum: ["plain", "S256"] 73 | description: "PKCE code challenge method" 74 | responses: 75 | "200": 76 | description: "Authorization page displayed successfully" 77 | content: 78 | text/html: 79 | schema: 80 | type: "string" 81 | 82 | /approve: 83 | post: 84 | summary: "Authorization Approval Endpoint" 85 | description: "Processes user login and authorization approval" 86 | requestBody: 87 | required: true 88 | content: 89 | application/x-www-form-urlencoded: 90 | schema: 91 | type: "object" 92 | required: 93 | - "action" 94 | - "oauthReqInfo" 95 | properties: 96 | action: 97 | type: "string" 98 | enum: ["approve", "reject", "login_approve"] 99 | description: "Action to take" 100 | oauthReqInfo: 101 | type: "string" 102 | description: "JSON string of OAuth request information" 103 | email: 104 | type: "string" 105 | format: "email" 106 | description: "User email address for login" 107 | password: 108 | type: "string" 109 | format: "password" 110 | description: "User password for login" 111 | responses: 112 | "200": 113 | description: "Approval processed successfully" 114 | content: 115 | text/html: 116 | schema: 117 | type: "string" 118 | "401": 119 | description: "Invalid login" 120 | content: 121 | text/html: 122 | schema: 123 | type: "string" 124 | 125 | /token: 126 | post: 127 | summary: "OAuth Token Endpoint" 128 | description: "Issues, refreshes, and revokes OAuth tokens" 129 | requestBody: 130 | required: true 131 | content: 132 | application/x-www-form-urlencoded: 133 | schema: 134 | type: "object" 135 | required: 136 | - "grant_type" 137 | properties: 138 | grant_type: 139 | type: "string" 140 | enum: ["authorization_code", "refresh_token"] 141 | description: "OAuth grant type" 142 | client_id: 143 | type: "string" 144 | description: "Client identifier" 145 | client_secret: 146 | type: "string" 147 | description: "Client secret for confidential clients" 148 | code: 149 | type: "string" 150 | description: "Authorization code (for authorization_code grant type)" 151 | redirect_uri: 152 | type: "string" 153 | format: "uri" 154 | description: "Redirect URI used in the authorization request" 155 | refresh_token: 156 | type: "string" 157 | description: "Refresh token (for refresh_token grant type)" 158 | code_verifier: 159 | type: "string" 160 | description: "PKCE code verifier (for authorization_code grant type with PKCE)" 161 | responses: 162 | "200": 163 | description: "Token issued successfully" 164 | content: 165 | application/json: 166 | schema: 167 | type: "object" 168 | properties: 169 | access_token: 170 | type: "string" 171 | description: "Access token" 172 | token_type: 173 | type: "string" 174 | description: "Token type (always 'bearer')" 175 | expires_in: 176 | type: "integer" 177 | description: "Token lifetime in seconds" 178 | refresh_token: 179 | type: "string" 180 | description: "Refresh token for obtaining new access tokens" 181 | scope: 182 | type: "string" 183 | description: "Space-separated list of granted scopes" 184 | "400": 185 | description: "Bad request" 186 | content: 187 | application/json: 188 | schema: 189 | type: "object" 190 | properties: 191 | error: 192 | type: "string" 193 | error_description: 194 | type: "string" 195 | "401": 196 | description: "Unauthorized" 197 | content: 198 | application/json: 199 | schema: 200 | type: "object" 201 | properties: 202 | error: 203 | type: "string" 204 | error_description: 205 | type: "string" 206 | 207 | /register: 208 | post: 209 | summary: "OAuth Client Registration Endpoint" 210 | description: "Registers a new OAuth client" 211 | requestBody: 212 | required: true 213 | content: 214 | application/json: 215 | schema: 216 | type: "object" 217 | required: 218 | - "redirect_uris" 219 | properties: 220 | redirect_uris: 221 | type: "array" 222 | items: 223 | type: "string" 224 | format: "uri" 225 | description: "List of allowed redirect URIs" 226 | client_name: 227 | type: "string" 228 | description: "Human-readable name of the client" 229 | logo_uri: 230 | type: "string" 231 | format: "uri" 232 | description: "URL to the client's logo" 233 | client_uri: 234 | type: "string" 235 | format: "uri" 236 | description: "URL to the client's homepage" 237 | policy_uri: 238 | type: "string" 239 | format: "uri" 240 | description: "URL to the client's privacy policy" 241 | tos_uri: 242 | type: "string" 243 | format: "uri" 244 | description: "URL to the client's terms of service" 245 | jwks_uri: 246 | type: "string" 247 | format: "uri" 248 | description: "URL to the client's JSON Web Key Set" 249 | contacts: 250 | type: "array" 251 | items: 252 | type: "string" 253 | format: "email" 254 | description: "List of contact emails" 255 | grant_types: 256 | type: "array" 257 | items: 258 | type: "string" 259 | description: "List of grant types the client supports" 260 | response_types: 261 | type: "array" 262 | items: 263 | type: "string" 264 | description: "List of response types the client supports" 265 | token_endpoint_auth_method: 266 | type: "string" 267 | enum: ["client_secret_basic", "client_secret_post", "none"] 268 | description: "Authentication method for the token endpoint" 269 | responses: 270 | "201": 271 | description: "Client registered successfully" 272 | content: 273 | application/json: 274 | schema: 275 | type: "object" 276 | properties: 277 | client_id: 278 | type: "string" 279 | client_secret: 280 | type: "string" 281 | redirect_uris: 282 | type: "array" 283 | items: 284 | type: "string" 285 | client_name: 286 | type: "string" 287 | logo_uri: 288 | type: "string" 289 | client_uri: 290 | type: "string" 291 | policy_uri: 292 | type: "string" 293 | tos_uri: 294 | type: "string" 295 | jwks_uri: 296 | type: "string" 297 | contacts: 298 | type: "array" 299 | items: 300 | type: "string" 301 | grant_types: 302 | type: "array" 303 | items: 304 | type: "string" 305 | response_types: 306 | type: "array" 307 | items: 308 | type: "string" 309 | token_endpoint_auth_method: 310 | type: "string" 311 | registration_client_uri: 312 | type: "string" 313 | client_id_issued_at: 314 | type: "integer" 315 | "400": 316 | description: "Invalid client metadata" 317 | content: 318 | application/json: 319 | schema: 320 | type: "object" 321 | properties: 322 | error: 323 | type: "string" 324 | error_description: 325 | type: "string" 326 | 327 | /.well-known/oauth-authorization-server: 328 | get: 329 | summary: "OAuth Server Metadata Endpoint" 330 | description: "Provides OAuth server metadata according to RFC 8414" 331 | responses: 332 | "200": 333 | description: "OAuth server metadata" 334 | content: 335 | application/json: 336 | schema: 337 | type: "object" 338 | properties: 339 | issuer: 340 | type: "string" 341 | authorization_endpoint: 342 | type: "string" 343 | format: "uri" 344 | token_endpoint: 345 | type: "string" 346 | format: "uri" 347 | registration_endpoint: 348 | type: "string" 349 | format: "uri" 350 | scopes_supported: 351 | type: "array" 352 | items: 353 | type: "string" 354 | response_types_supported: 355 | type: "array" 356 | items: 357 | type: "string" 358 | response_modes_supported: 359 | type: "array" 360 | items: 361 | type: "string" 362 | grant_types_supported: 363 | type: "array" 364 | items: 365 | type: "string" 366 | token_endpoint_auth_methods_supported: 367 | type: "array" 368 | items: 369 | type: "string" 370 | revocation_endpoint: 371 | type: "string" 372 | format: "uri" 373 | code_challenge_methods_supported: 374 | type: "array" 375 | items: 376 | type: "string" 377 | 378 | /sse: 379 | get: 380 | summary: "MCP Server-Sent Events Endpoint" 381 | description: "Protected API endpoint for MCP functionality via server-sent events" 382 | security: 383 | - bearerAuth: [] 384 | responses: 385 | "200": 386 | description: "SSE stream established" 387 | content: 388 | text/event-stream: 389 | schema: 390 | type: "string" 391 | "401": 392 | description: "Unauthorized" 393 | content: 394 | application/json: 395 | schema: 396 | type: "object" 397 | properties: 398 | error: 399 | type: "string" 400 | error_description: 401 | type: "string" 402 | 403 | components: 404 | securitySchemes: 405 | bearerAuth: 406 | type: "http" 407 | scheme: "bearer" 408 | bearerFormat: "JWT" 409 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import YAML from "yaml"; 2 | 3 | interface McpConfig { 4 | protocolVersion: string; 5 | serverInfo: { 6 | name: string; 7 | version: string; 8 | }; 9 | authEndpoint?: string; 10 | promptOperationIds: string[]; 11 | toolOperationIds: string[]; 12 | resourceOperationIds: string[]; 13 | requiresAuth: boolean; 14 | allOperations: Map< 15 | string, 16 | { path: string; method: string; operation: OpenAPIOperation } 17 | >; 18 | openapi: OpenAPISpec; 19 | } 20 | 21 | interface OpenAPIOperation { 22 | operationId: string; 23 | summary?: string; 24 | description?: string; 25 | parameters?: Array<{ 26 | name: string; 27 | in: string; 28 | required?: boolean; 29 | description?: string; 30 | schema?: any; 31 | }>; 32 | requestBody?: { 33 | content?: { 34 | [mediaType: string]: { 35 | schema?: any; 36 | }; 37 | }; 38 | }; 39 | responses?: { 40 | [statusCode: string]: { 41 | description?: string; 42 | content?: { 43 | [mediaType: string]: { 44 | schema?: any; 45 | }; 46 | }; 47 | }; 48 | }; 49 | security?: Array<{ [key: string]: string[] }>; 50 | } 51 | 52 | interface OpenAPISpec { 53 | openapi: string; 54 | info: { 55 | title: string; 56 | version: string; 57 | "x-mcp"?: { 58 | protocolVersion?: string; 59 | authEndpoint?: string; 60 | serverInfo?: { 61 | name: string; 62 | version: string; 63 | }; 64 | promptOperationIds?: string[]; 65 | toolOperationIds?: string[]; 66 | resourceOperationIds?: string[]; 67 | }; 68 | }; 69 | paths: { 70 | [path: string]: { 71 | [method: string]: OpenAPIOperation; 72 | }; 73 | }; 74 | components?: { 75 | securitySchemes?: { [name: string]: any }; 76 | schemas?: { [name: string]: any }; 77 | }; 78 | security?: Array<{ [key: string]: string[] }>; 79 | servers?: Array<{ url: string; description?: string }>; 80 | } 81 | 82 | // Helper function to create response with proper CORS headers 83 | function createCorsResponse( 84 | body: BodyInit | null, 85 | options: ResponseInit = {} 86 | ): Response { 87 | const corsHeaders = { 88 | "Access-Control-Allow-Origin": "*", 89 | "Access-Control-Allow-Methods": "POST, OPTIONS, GET", 90 | "Access-Control-Allow-Headers": 91 | "Content-Type, Authorization, MCP-Protocol-Version", 92 | "Access-Control-Max-Age": "86400", 93 | }; 94 | 95 | // Merge headers, ensuring CORS headers take precedence 96 | const headers = new Headers(options.headers); 97 | 98 | // Remove any existing CORS headers to prevent duplicates 99 | headers.delete("Access-Control-Allow-Origin"); 100 | headers.delete("Access-Control-Allow-Methods"); 101 | headers.delete("Access-Control-Allow-Headers"); 102 | headers.delete("Access-Control-Max-Age"); 103 | 104 | // Add our CORS headers 105 | Object.entries(corsHeaders).forEach(([key, value]) => { 106 | headers.set(key, value); 107 | }); 108 | 109 | return new Response(body, { 110 | ...options, 111 | headers, 112 | }); 113 | } 114 | 115 | export default { 116 | async fetch(request: Request): Promise { 117 | const url = new URL(request.url); 118 | 119 | // Handle CORS preflight 120 | if (request.method === "OPTIONS") { 121 | return createCorsResponse(null, { status: 204 }); 122 | } 123 | 124 | // Handle .well-known/oauth-protected-resource proxy 125 | if (url.pathname.startsWith("/.well-known/oauth-protected-resource/")) { 126 | const pathParts = url.pathname 127 | .replace("/.well-known/oauth-protected-resource/", "") 128 | .split("/") 129 | .filter(Boolean); 130 | 131 | if (pathParts.length >= 1) { 132 | const hostname = pathParts[0]; 133 | 134 | // Validate hostname format 135 | if (!isValidHostname(hostname)) { 136 | return createCorsResponse( 137 | JSON.stringify({ error: "Invalid hostname format" }), 138 | { 139 | status: 400, 140 | headers: { "Content-Type": "application/json" }, 141 | } 142 | ); 143 | } 144 | 145 | // Try proxying to https://{hostname}/.well-known/oauth-protected-resource/mcp first 146 | if (pathParts.length >= 2 && pathParts[1] === "mcp") { 147 | try { 148 | const targetUrl = `https://${hostname}/.well-known/oauth-protected-resource/mcp`; 149 | console.log(`Proxying oauth-protected-resource to: ${targetUrl}`); 150 | 151 | const response = await fetch(targetUrl, { 152 | method: request.method, 153 | headers: request.headers, 154 | ...(request.method !== "GET" && 155 | request.method !== "HEAD" && { 156 | body: request.body, 157 | }), 158 | }); 159 | 160 | // If successful, return the response with CORS headers 161 | if (response.ok) { 162 | const json: any = await response.json(); 163 | json.resource = `${url.origin}/${hostname}/mcp`; 164 | const responseHeaders = new Headers(); 165 | 166 | // Copy non-CORS headers from the original response 167 | response.headers.forEach((value, key) => { 168 | if (!key.toLowerCase().startsWith("access-control-")) { 169 | responseHeaders.set(key, value); 170 | } 171 | }); 172 | 173 | return createCorsResponse(JSON.stringify(json, undefined, 2), { 174 | status: response.status, 175 | statusText: response.statusText, 176 | headers: responseHeaders, 177 | }); 178 | } 179 | } catch (error) { 180 | console.log(`Failed to proxy to /mcp endpoint:`, error.message); 181 | } 182 | } 183 | 184 | // Fallback: try https://{hostname}/.well-known/oauth-protected-resource 185 | try { 186 | const targetUrl = `https://${hostname}/.well-known/oauth-protected-resource`; 187 | console.log( 188 | `Proxying oauth-protected-resource to fallback: ${targetUrl}` 189 | ); 190 | 191 | const response = await fetch(targetUrl, { 192 | method: request.method, 193 | headers: request.headers, 194 | ...(request.method !== "GET" && 195 | request.method !== "HEAD" && { 196 | body: request.body, 197 | }), 198 | }); 199 | 200 | if (!response.ok) { 201 | return response; 202 | } 203 | 204 | const json: any = await response.json(); 205 | json.resource = `${url.origin}/${hostname}/mcp`; 206 | 207 | const responseHeaders = new Headers(); 208 | 209 | // Copy non-CORS headers from the original response 210 | response.headers.forEach((value, key) => { 211 | if (!key.toLowerCase().startsWith("access-control-")) { 212 | responseHeaders.set(key, value); 213 | } 214 | }); 215 | 216 | return createCorsResponse(JSON.stringify(json), { 217 | status: response.status, 218 | statusText: response.statusText, 219 | headers: responseHeaders, 220 | }); 221 | } catch (error) { 222 | return createCorsResponse( 223 | JSON.stringify({ 224 | error: "Failed to proxy to oauth-protected-resource endpoint", 225 | details: error.message, 226 | }), 227 | { 228 | status: 502, 229 | headers: { "Content-Type": "application/json" }, 230 | } 231 | ); 232 | } 233 | } 234 | 235 | return createCorsResponse( 236 | JSON.stringify({ 237 | error: 238 | "Invalid oauth-protected-resource URL structure. Use: /.well-known/oauth-protected-resource/{hostname}/mcp", 239 | example: 240 | "https://mcp.openapisearch.com/.well-known/oauth-protected-resource/api.example.com/mcp", 241 | }), 242 | { 243 | status: 400, 244 | headers: { "Content-Type": "application/json" }, 245 | } 246 | ); 247 | } 248 | 249 | // Parse URL structure: /{hostname}/mcp 250 | const pathParts = url.pathname.split("/").filter(Boolean); 251 | if (pathParts.length !== 2 || pathParts[1] !== "mcp") { 252 | return createCorsResponse( 253 | JSON.stringify({ 254 | error: "Invalid URL structure. Use: /{hostname}/mcp", 255 | example: "https://openapimcp.com/api.example.com/mcp", 256 | }), 257 | { 258 | status: 400, 259 | headers: { "Content-Type": "application/json" }, 260 | } 261 | ); 262 | } 263 | 264 | const hostname = pathParts[0]; 265 | 266 | // Validate hostname format 267 | if (!isValidHostname(hostname)) { 268 | return createCorsResponse( 269 | JSON.stringify({ error: "Invalid hostname format" }), 270 | { 271 | status: 400, 272 | headers: { "Content-Type": "application/json" }, 273 | } 274 | ); 275 | } 276 | 277 | try { 278 | // Fetch and parse OpenAPI spec 279 | const openapi = await fetchOpenAPISpec(hostname); 280 | 281 | // Build MCP configuration from OpenAPI 282 | const mcpConfig = await buildMcpConfig(openapi, hostname); 283 | 284 | // Handle MCP requests 285 | if (request.method === "POST") { 286 | const response = await handleMcpRequest(request, mcpConfig, hostname); 287 | 288 | // Create new response with proper CORS headers 289 | const responseBody = response.body; 290 | const responseHeaders = new Headers(); 291 | 292 | // Copy non-CORS headers from the original response 293 | response.headers.forEach((value, key) => { 294 | if (!key.toLowerCase().startsWith("access-control-")) { 295 | responseHeaders.set(key, value); 296 | } 297 | }); 298 | 299 | return createCorsResponse(responseBody, { 300 | status: response.status, 301 | statusText: response.statusText, 302 | headers: responseHeaders, 303 | }); 304 | } 305 | 306 | return createCorsResponse( 307 | `MCP endpoint ready for ${hostname}.\nUse POST requests with MCP protocol.\nAvailable tools: ${mcpConfig.toolOperationIds.join( 308 | ", " 309 | )}\nRequires auth: ${mcpConfig.requiresAuth}`, 310 | { 311 | status: 200, 312 | headers: { "Content-Type": "text/plain" }, 313 | } 314 | ); 315 | } catch (error) { 316 | console.error("Error processing request:", error); 317 | return createCorsResponse( 318 | JSON.stringify({ 319 | error: "Failed to process OpenAPI specification", 320 | details: error.message, 321 | }), 322 | { 323 | status: 500, 324 | headers: { "Content-Type": "application/json" }, 325 | } 326 | ); 327 | } 328 | }, 329 | }; 330 | 331 | function isValidHostname(hostname: string): boolean { 332 | const hostnameRegex = 333 | /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/; 334 | return hostnameRegex.test(hostname) && hostname.length <= 253; 335 | } 336 | 337 | async function fetchOpenAPISpec(hostname: string): Promise { 338 | const urls = [ 339 | `https://${hostname}/.well-known/openapi`, 340 | `https://${hostname}/openapi.json`, 341 | `https://${hostname}/openapi.yaml`, 342 | `https://${hostname}/openapi.yml`, 343 | ]; 344 | 345 | for (const url of urls) { 346 | try { 347 | console.log(`Trying to fetch OpenAPI spec from: ${url}`); 348 | const response = await fetch(url, { 349 | headers: { Accept: "application/json, application/x-yaml, text/yaml" }, 350 | }); 351 | 352 | if (response.ok) { 353 | const contentType = response.headers.get("content-type") || ""; 354 | const text = await response.text(); 355 | 356 | let spec: any; 357 | if ( 358 | contentType.includes("yaml") || 359 | contentType.includes("yml") || 360 | url.includes(".yaml") || 361 | url.includes(".yml") 362 | ) { 363 | spec = YAML.parse(text); 364 | } else { 365 | spec = JSON.parse(text); 366 | } 367 | 368 | // Validate it's an OpenAPI spec 369 | if (spec.paths && spec.info) { 370 | console.log(`Found OpenAPI spec at: ${url}`); 371 | return spec as OpenAPISpec; 372 | } 373 | } 374 | } catch (error) { 375 | console.log(`Failed to fetch ${url}:`, error.message); 376 | continue; 377 | } 378 | } 379 | 380 | throw new Error( 381 | `No valid OpenAPI specification found for hostname: ${hostname}` 382 | ); 383 | } 384 | 385 | async function buildMcpConfig( 386 | openapi: OpenAPISpec, 387 | hostname: string 388 | ): Promise { 389 | const allOperations = new Map< 390 | string, 391 | { path: string; method: string; operation: OpenAPIOperation } 392 | >(); 393 | 394 | // Extract all operations 395 | for (const [path, methods] of Object.entries(openapi.paths)) { 396 | for (const [method, operation] of Object.entries(methods)) { 397 | if (operation.operationId) { 398 | allOperations.set(operation.operationId, { path, method, operation }); 399 | } 400 | } 401 | } 402 | 403 | // Get x-mcp configuration or use defaults 404 | const xMcp = openapi.info["x-mcp"]; 405 | 406 | const protocolVersion = xMcp?.protocolVersion || "2025-03-26"; 407 | const serverInfo = xMcp?.serverInfo || { 408 | name: openapi.info.title || `${hostname} API`, 409 | version: openapi.info.version || "1.0.0", 410 | }; 411 | 412 | // Default: all operations are tools unless explicitly specified in x-mcp 413 | let toolOperationIds: string[]; 414 | let promptOperationIds: string[]; 415 | let resourceOperationIds: string[]; 416 | 417 | if (xMcp) { 418 | // Use explicit configuration from x-mcp 419 | toolOperationIds = xMcp.toolOperationIds || []; 420 | promptOperationIds = xMcp.promptOperationIds || []; 421 | resourceOperationIds = xMcp.resourceOperationIds || []; 422 | 423 | // If none are specified, default all to tools 424 | if ( 425 | toolOperationIds.length === 0 && 426 | promptOperationIds.length === 0 && 427 | resourceOperationIds.length === 0 428 | ) { 429 | toolOperationIds = Array.from(allOperations.keys()); 430 | } 431 | } else { 432 | // No x-mcp: treat all operations as tools 433 | toolOperationIds = Array.from(allOperations.keys()); 434 | promptOperationIds = []; 435 | resourceOperationIds = []; 436 | } 437 | 438 | // Filter operation IDs to only include those that exist in the spec 439 | toolOperationIds = toolOperationIds.filter((id) => allOperations.has(id)); 440 | promptOperationIds = promptOperationIds.filter((id) => allOperations.has(id)); 441 | resourceOperationIds = resourceOperationIds.filter((id) => 442 | allOperations.has(id) 443 | ); 444 | 445 | // Check for authorization requirements 446 | const { requiresAuth } = await checkAuthRequirements( 447 | openapi, 448 | allOperations, 449 | hostname, 450 | xMcp?.authEndpoint 451 | ); 452 | 453 | return { 454 | protocolVersion, 455 | serverInfo, 456 | authEndpoint: xMcp?.authEndpoint, 457 | toolOperationIds, 458 | promptOperationIds, 459 | resourceOperationIds, 460 | requiresAuth, 461 | allOperations, 462 | openapi, 463 | }; 464 | } 465 | 466 | async function checkAuthRequirements( 467 | openapi: OpenAPISpec, 468 | allOperations: Map< 469 | string, 470 | { path: string; method: string; operation: OpenAPIOperation } 471 | >, 472 | hostname: string, 473 | authEndpoint?: string 474 | ): Promise<{ requiresAuth: boolean }> { 475 | // If authEndpoint is specified in x-mcp, test it 476 | if (authEndpoint) { 477 | try { 478 | const testUrl = `https://${hostname}${authEndpoint}`; 479 | console.log(`Testing auth with specified endpoint: ${testUrl}`); 480 | 481 | const testResponse = await fetch(testUrl, { 482 | method: "GET", 483 | headers: { Accept: "application/json" }, 484 | }); 485 | 486 | if (testResponse.status === 401) { 487 | console.log(`Auth required for ${hostname} (via authEndpoint)`); 488 | return { requiresAuth: true }; 489 | } 490 | 491 | return { requiresAuth: false }; 492 | } catch (error) { 493 | console.log(`Auth test failed for ${hostname}:`, error.message); 494 | return { requiresAuth: false }; 495 | } 496 | } 497 | 498 | // Check if there's global security or any operation has security 499 | const hasGlobalSecurity = openapi.security && openapi.security.length > 0; 500 | 501 | // Find first operation that requires auth 502 | let requiresAuth = false; 503 | 504 | for (const [opId, { path, method, operation }] of allOperations) { 505 | const hasSecurity = operation.security && operation.security.length > 0; 506 | 507 | if (hasGlobalSecurity || hasSecurity) { 508 | // Test the operation to see if it returns proper auth challenges 509 | try { 510 | const testUrl = `https://${hostname}${path}`; 511 | console.log(`Testing auth with: ${method.toUpperCase()} ${testUrl}`); 512 | 513 | const testResponse = await fetch(testUrl, { 514 | method: method.toUpperCase(), 515 | headers: { Accept: "application/json" }, 516 | }); 517 | 518 | if (testResponse.status === 401) { 519 | console.log(`Auth required for ${hostname} (via operation ${opId})`); 520 | requiresAuth = true; 521 | break; 522 | } 523 | } catch (error) { 524 | console.log(`Auth test failed for operation ${opId}:`, error.message); 525 | } 526 | } 527 | } 528 | 529 | return { requiresAuth }; 530 | } 531 | 532 | async function handleMcpRequest( 533 | request: Request, 534 | config: McpConfig, 535 | hostname: string 536 | ): Promise { 537 | try { 538 | const message: any = await request.json(); 539 | 540 | // Handle initialize 541 | if (message.method === "initialize") { 542 | if (config.requiresAuth) { 543 | const authResult = await checkAuth(request, config, hostname); 544 | if (authResult) { 545 | return authResult; 546 | } 547 | } 548 | 549 | return new Response( 550 | JSON.stringify({ 551 | jsonrpc: "2.0", 552 | id: message.id, 553 | result: { 554 | protocolVersion: config.protocolVersion, 555 | capabilities: { 556 | ...(config.promptOperationIds.length > 0 && { prompts: {} }), 557 | ...(config.resourceOperationIds.length > 0 && { resources: {} }), 558 | ...(config.toolOperationIds.length > 0 && { tools: {} }), 559 | }, 560 | serverInfo: config.serverInfo, 561 | }, 562 | }), 563 | { headers: { "Content-Type": "application/json" } } 564 | ); 565 | } 566 | 567 | if (message.method === "ping") { 568 | if (config.requiresAuth) { 569 | const authResult = await checkAuth(request, config, hostname); 570 | if (authResult) { 571 | return authResult; 572 | } 573 | } 574 | 575 | return new Response( 576 | JSON.stringify({ 577 | jsonrpc: "2.0", 578 | id: message.id, 579 | result: {}, 580 | }), 581 | { headers: { "Content-Type": "application/json" } } 582 | ); 583 | } 584 | 585 | // Handle initialized notification 586 | if (message.method === "notifications/initialized") { 587 | return new Response(null, { status: 202 }); 588 | } 589 | 590 | // Handle tools/list 591 | if (message.method === "tools/list") { 592 | if (config.requiresAuth) { 593 | const authResult = await checkAuth(request, config, hostname); 594 | if (authResult) { 595 | return authResult; 596 | } 597 | } 598 | 599 | const tools = config.toolOperationIds.map((opId) => { 600 | const op = config.allOperations.get(opId); 601 | return { 602 | name: opId, 603 | description: 604 | op?.operation.description || 605 | op?.operation.summary || 606 | `Tool for operation: ${opId}`, 607 | inputSchema: extractInputSchema(op?.operation), 608 | }; 609 | }); 610 | 611 | return new Response( 612 | JSON.stringify({ 613 | jsonrpc: "2.0", 614 | id: message.id, 615 | result: { tools }, 616 | }), 617 | { headers: { "Content-Type": "application/json" } } 618 | ); 619 | } 620 | 621 | // Handle prompts/list 622 | if (message.method === "prompts/list") { 623 | if (config.requiresAuth) { 624 | const authResult = await checkAuth(request, config, hostname); 625 | if (authResult) { 626 | return authResult; 627 | } 628 | } 629 | 630 | const prompts = config.promptOperationIds.map((opId) => { 631 | const op = config.allOperations.get(opId); 632 | return { 633 | name: opId, 634 | description: op?.operation.description || op?.operation.summary, 635 | arguments: extractArguments(op?.operation), 636 | }; 637 | }); 638 | 639 | return new Response( 640 | JSON.stringify({ 641 | jsonrpc: "2.0", 642 | id: message.id, 643 | result: { prompts }, 644 | }), 645 | { headers: { "Content-Type": "application/json" } } 646 | ); 647 | } 648 | 649 | // Handle resources/list 650 | if (message.method === "resources/list") { 651 | if (config.requiresAuth) { 652 | const authResult = await checkAuth(request, config, hostname); 653 | if (authResult) { 654 | return authResult; 655 | } 656 | } 657 | 658 | const resources = config.resourceOperationIds.map((opId) => { 659 | const op = config.allOperations.get(opId); 660 | return { 661 | uri: `resource://${opId}`, 662 | name: opId, 663 | description: op?.operation.description || op?.operation.summary, 664 | mimeType: inferMimeType(op?.operation), 665 | }; 666 | }); 667 | 668 | return new Response( 669 | JSON.stringify({ 670 | jsonrpc: "2.0", 671 | id: message.id, 672 | result: { resources }, 673 | }), 674 | { headers: { "Content-Type": "application/json" } } 675 | ); 676 | } 677 | 678 | // Handle tools/call 679 | if (message.method === "tools/call") { 680 | const { name, arguments: args } = message.params; 681 | 682 | if (!config.toolOperationIds.includes(name)) { 683 | return createError(message.id, -32602, `Unknown tool: ${name}`); 684 | } 685 | 686 | try { 687 | const apiResponse = await proxyToOperation( 688 | name, 689 | args, 690 | request, 691 | hostname, 692 | config 693 | ); 694 | 695 | // If 401 or 402, proxy the response as-is 696 | if (apiResponse.status === 401 || apiResponse.status === 402) { 697 | return apiResponse; 698 | } 699 | 700 | const text = await apiResponse.text(); 701 | 702 | return new Response( 703 | JSON.stringify({ 704 | jsonrpc: "2.0", 705 | id: message.id, 706 | result: { 707 | content: [{ type: "text", text }], 708 | isError: !apiResponse.ok, 709 | }, 710 | }), 711 | { headers: { "Content-Type": "application/json" } } 712 | ); 713 | } catch (error) { 714 | return new Response( 715 | JSON.stringify({ 716 | jsonrpc: "2.0", 717 | id: message.id, 718 | result: { 719 | content: [{ type: "text", text: `Error: ${error.message}` }], 720 | isError: true, 721 | }, 722 | }), 723 | { headers: { "Content-Type": "application/json" } } 724 | ); 725 | } 726 | } 727 | 728 | // Handle prompts/get 729 | if (message.method === "prompts/get") { 730 | const { name, arguments: args } = message.params; 731 | 732 | if (!config.promptOperationIds.includes(name)) { 733 | return createError(message.id, -32602, `Unknown prompt: ${name}`); 734 | } 735 | 736 | try { 737 | const apiResponse = await proxyToOperation( 738 | name, 739 | args, 740 | request, 741 | hostname, 742 | config 743 | ); 744 | 745 | // If 401 or 402, proxy the response as-is 746 | if (apiResponse.status === 401 || apiResponse.status === 402) { 747 | return apiResponse; 748 | } 749 | 750 | const text = await apiResponse.text(); 751 | 752 | return new Response( 753 | JSON.stringify({ 754 | jsonrpc: "2.0", 755 | id: message.id, 756 | result: { 757 | description: 758 | config.allOperations.get(name)?.operation.description, 759 | messages: [ 760 | { 761 | role: "user", 762 | content: { 763 | type: "text", 764 | text: apiResponse.ok 765 | ? text 766 | : `Error: ${apiResponse.status} ${apiResponse.statusText}\n${text}`, 767 | }, 768 | }, 769 | ], 770 | }, 771 | }), 772 | { headers: { "Content-Type": "application/json" } } 773 | ); 774 | } catch (error) { 775 | return createError( 776 | message.id, 777 | -32603, 778 | `Error executing prompt: ${error.message}` 779 | ); 780 | } 781 | } 782 | 783 | // Handle resources/read 784 | if (message.method === "resources/read") { 785 | const { uri } = message.params; 786 | const opId = uri.replace("resource://", ""); 787 | 788 | if (!config.resourceOperationIds.includes(opId)) { 789 | return createError(message.id, -32002, `Resource not found: ${uri}`); 790 | } 791 | 792 | try { 793 | const apiResponse = await proxyToOperation( 794 | opId, 795 | {}, 796 | request, 797 | hostname, 798 | config 799 | ); 800 | 801 | // If 401 or 402, proxy the response as-is 802 | if (apiResponse.status === 401 || apiResponse.status === 402) { 803 | return apiResponse; 804 | } 805 | 806 | const text = await apiResponse.text(); 807 | const contentType = 808 | apiResponse.headers.get("content-type") || "text/plain"; 809 | 810 | return new Response( 811 | JSON.stringify({ 812 | jsonrpc: "2.0", 813 | id: message.id, 814 | result: { 815 | contents: [ 816 | { 817 | uri, 818 | mimeType: contentType, 819 | text, 820 | }, 821 | ], 822 | }, 823 | }), 824 | { headers: { "Content-Type": "application/json" } } 825 | ); 826 | } catch (error) { 827 | return createError( 828 | message.id, 829 | -32603, 830 | `Error reading resource: ${error.message}` 831 | ); 832 | } 833 | } 834 | 835 | return createError( 836 | message.id, 837 | -32601, 838 | `Method not found: ${message.method}` 839 | ); 840 | } catch (error) { 841 | return createError(null, -32700, "Parse error"); 842 | } 843 | } 844 | 845 | async function checkAuth( 846 | request: Request, 847 | config: McpConfig, 848 | hostname: string 849 | ): Promise { 850 | // Use authEndpoint if specified, otherwise find first operation that requires auth 851 | let testEndpoint = config.authEndpoint; 852 | 853 | if (!testEndpoint) { 854 | // Find first operation that might require auth 855 | for (const [opId, { path, method, operation }] of config.allOperations) { 856 | const hasGlobalSecurity = 857 | config.openapi.security && config.openapi.security.length > 0; 858 | const hasSecurity = operation.security && operation.security.length > 0; 859 | 860 | if (hasGlobalSecurity || hasSecurity) { 861 | testEndpoint = path; 862 | break; 863 | } 864 | } 865 | } 866 | 867 | if (!testEndpoint) { 868 | return null; // No auth required 869 | } 870 | 871 | try { 872 | const apiResponse = await fetch(`https://${hostname}${testEndpoint}`, { 873 | method: "GET", 874 | headers: { 875 | Accept: "application/json", 876 | Authorization: request.headers.get("Authorization") || "", 877 | }, 878 | }); 879 | 880 | if (apiResponse.status === 401 || apiResponse.status === 402) { 881 | let wwwAuth = apiResponse.headers.get("www-authenticate"); 882 | 883 | if (!wwwAuth) { 884 | // Add resource metadata if it exists 885 | const resourceMetadataUrl = `https://${hostname}/.well-known/oauth-protected-resource`; 886 | try { 887 | const metadataResponse = await fetch(resourceMetadataUrl); 888 | if (metadataResponse.ok) { 889 | wwwAuth = `resource_metadata="${resourceMetadataUrl}"`; 890 | } 891 | } catch (error) { 892 | // Ignore metadata fetch errors 893 | } 894 | } 895 | 896 | // Create new headers without existing CORS headers 897 | const headers = new Headers(); 898 | apiResponse.headers.forEach((value, key) => { 899 | if (!key.toLowerCase().startsWith("access-control-")) { 900 | headers.set(key, value); 901 | } 902 | }); 903 | 904 | if (wwwAuth && !headers.has("www-authenticate")) { 905 | headers.set("www-authenticate", wwwAuth); 906 | } 907 | 908 | return new Response(apiResponse.body, { 909 | status: apiResponse.status, 910 | statusText: apiResponse.statusText, 911 | headers, 912 | }); 913 | } 914 | 915 | return null; // Auth passed 916 | } catch (error) { 917 | return new Response( 918 | JSON.stringify({ error: "Authentication check failed" }), 919 | { 920 | status: 500, 921 | headers: { "Content-Type": "application/json" }, 922 | } 923 | ); 924 | } 925 | } 926 | 927 | async function proxyToOperation( 928 | operationId: string, 929 | args: any, 930 | originalRequest: Request, 931 | hostname: string, 932 | config: McpConfig 933 | ): Promise { 934 | const op = config.allOperations.get(operationId); 935 | if (!op) { 936 | throw new Error(`Operation not found: ${operationId}`); 937 | } 938 | 939 | // Build the API request URL 940 | let url = op.path; 941 | const queryParams = new URLSearchParams(); 942 | const bodyData: any = {}; 943 | 944 | // Handle path and query parameters 945 | if (op.operation.parameters) { 946 | for (const param of op.operation.parameters) { 947 | const value = args[param.name]; 948 | if (value !== undefined) { 949 | if (param.in === "path") { 950 | url = url.replace( 951 | `{${param.name}}`, 952 | encodeURIComponent(String(value)) 953 | ); 954 | } else if (param.in === "query") { 955 | queryParams.set(param.name, String(value)); 956 | } 957 | } 958 | } 959 | } 960 | 961 | // Handle request body - merge remaining args into body 962 | const usedParamNames = new Set( 963 | op.operation.parameters?.map((p) => p.name) || [] 964 | ); 965 | 966 | for (const [key, value] of Object.entries(args)) { 967 | if (!usedParamNames.has(key)) { 968 | bodyData[key] = value; 969 | } 970 | } 971 | 972 | // Determine base URL from OpenAPI servers or default to hostname 973 | let baseUrl = `https://${hostname}`; 974 | if (config.openapi.servers && config.openapi.servers.length > 0) { 975 | const server = config.openapi.servers[0]; 976 | if (server.url.startsWith("http")) { 977 | baseUrl = server.url; 978 | } else if (server.url.startsWith("/")) { 979 | baseUrl = `https://${hostname}${server.url}`; 980 | } else { 981 | baseUrl = `https://${hostname}/${server.url}`; 982 | } 983 | } 984 | 985 | // Build final URL 986 | const finalUrl = new URL(url, baseUrl); 987 | if (queryParams.toString()) { 988 | finalUrl.search = queryParams.toString(); 989 | } 990 | 991 | // Prepare headers 992 | const headers: HeadersInit = { 993 | Accept: "application/json", 994 | "User-Agent": "OpenAPI-MCP-Worker/1.0", 995 | }; 996 | 997 | // Forward authorization header 998 | const authHeader = originalRequest.headers.get("Authorization"); 999 | if (authHeader) { 1000 | headers["Authorization"] = authHeader; 1001 | } 1002 | 1003 | // Add content-type for requests with body 1004 | if (op.method.toLowerCase() !== "get" && Object.keys(bodyData).length > 0) { 1005 | headers["Content-Type"] = "application/json"; 1006 | } 1007 | 1008 | console.log(`Proxying to: ${op.method.toUpperCase()} ${finalUrl.toString()}`); 1009 | console.log(`Headers:`, headers); 1010 | if (Object.keys(bodyData).length > 0) { 1011 | console.log(`Body:`, bodyData); 1012 | } 1013 | 1014 | // Make the API request 1015 | const apiRequest = new Request(finalUrl.toString(), { 1016 | method: op.method.toUpperCase(), 1017 | headers, 1018 | ...(op.method.toLowerCase() !== "get" && 1019 | Object.keys(bodyData).length > 0 && { 1020 | body: JSON.stringify(bodyData), 1021 | }), 1022 | }); 1023 | 1024 | return fetch(apiRequest); 1025 | } 1026 | 1027 | function extractArguments(operation?: OpenAPIOperation) { 1028 | if (!operation) return []; 1029 | 1030 | const args = []; 1031 | 1032 | // Extract from parameters 1033 | if (operation.parameters) { 1034 | for (const param of operation.parameters) { 1035 | args.push({ 1036 | name: param.name, 1037 | description: param.description, 1038 | required: param.required || false, 1039 | }); 1040 | } 1041 | } 1042 | 1043 | // Extract from request body schema properties 1044 | if ( 1045 | operation.requestBody?.content?.["application/json"]?.schema?.properties 1046 | ) { 1047 | const props = 1048 | operation.requestBody.content["application/json"].schema.properties; 1049 | const required = 1050 | operation.requestBody.content["application/json"].schema.required || []; 1051 | 1052 | for (const [name, schema] of Object.entries(props)) { 1053 | args.push({ 1054 | name, 1055 | description: (schema as any).description, 1056 | required: required.includes(name), 1057 | }); 1058 | } 1059 | } 1060 | 1061 | return args; 1062 | } 1063 | 1064 | function extractInputSchema(operation?: OpenAPIOperation) { 1065 | if (!operation) { 1066 | return { 1067 | type: "object", 1068 | properties: {}, 1069 | }; 1070 | } 1071 | 1072 | // Start with basic object schema 1073 | const schema: any = { 1074 | type: "object", 1075 | properties: {}, 1076 | required: [], 1077 | }; 1078 | 1079 | // Add parameters as properties 1080 | if (operation.parameters) { 1081 | for (const param of operation.parameters) { 1082 | schema.properties[param.name] = param.schema || { type: "string" }; 1083 | if (param.required) { 1084 | schema.required.push(param.name); 1085 | } 1086 | } 1087 | } 1088 | 1089 | // Merge request body schema 1090 | if (operation.requestBody?.content?.["application/json"]?.schema) { 1091 | const bodySchema = operation.requestBody.content["application/json"].schema; 1092 | if (bodySchema.properties) { 1093 | Object.assign(schema.properties, bodySchema.properties); 1094 | } 1095 | if (bodySchema.required) { 1096 | schema.required.push(...bodySchema.required); 1097 | } 1098 | } 1099 | 1100 | return schema; 1101 | } 1102 | 1103 | function inferMimeType(operation?: OpenAPIOperation): string { 1104 | if (!operation) return "application/json"; 1105 | 1106 | // Check response content types 1107 | const responses = operation.responses; 1108 | if (responses) { 1109 | for (const response of Object.values(responses)) { 1110 | if (response.content) { 1111 | const contentTypes = Object.keys(response.content); 1112 | if (contentTypes.length > 0) { 1113 | const preferred = ["text/plain", "text/markdown", "application/json"]; 1114 | const pref = contentTypes.find((x) => preferred.includes(x)); 1115 | if (pref) { 1116 | return pref; 1117 | } 1118 | return contentTypes[0]; 1119 | } 1120 | } 1121 | } 1122 | } 1123 | 1124 | return "application/json"; 1125 | } 1126 | 1127 | function createError(id: any, code: number, message: string): Response { 1128 | return new Response( 1129 | JSON.stringify({ 1130 | jsonrpc: "2.0", 1131 | id, 1132 | error: { code, message }, 1133 | }), 1134 | { 1135 | status: 200, 1136 | headers: { "Content-Type": "application/json" }, 1137 | } 1138 | ); 1139 | } 1140 | -------------------------------------------------------------------------------- /mcp.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by json-schema-to-typescript. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 5 | * and run json-schema-to-typescript to regenerate this file. 6 | */ 7 | 8 | /** 9 | * The sender or recipient of messages and data in a conversation. 10 | * 11 | * This interface was referenced by `McpSchema`'s JSON-Schema 12 | * via the `definition` "Role". 13 | */ 14 | export type Role = "assistant" | "user"; 15 | /** 16 | * This interface was referenced by `McpSchema`'s JSON-Schema 17 | * via the `definition` "ClientNotification". 18 | */ 19 | export type ClientNotification = 20 | | CancelledNotification 21 | | InitializedNotification 22 | | ProgressNotification 23 | | RootsListChangedNotification; 24 | /** 25 | * This interface was referenced by `McpSchema`'s JSON-Schema 26 | * via the `definition` "ClientRequest". 27 | */ 28 | export type ClientRequest = 29 | | InitializeRequest 30 | | PingRequest 31 | | ListResourcesRequest 32 | | ReadResourceRequest 33 | | SubscribeRequest 34 | | UnsubscribeRequest 35 | | ListPromptsRequest 36 | | GetPromptRequest 37 | | ListToolsRequest 38 | | CallToolRequest 39 | | SetLevelRequest 40 | | CompleteRequest; 41 | /** 42 | * This interface was referenced by `McpSchema`'s JSON-Schema 43 | * via the `definition` "ClientResult". 44 | */ 45 | export type ClientResult = Result | CreateMessageResult | ListRootsResult; 46 | /** 47 | * An opaque token used to represent a cursor for pagination. 48 | * 49 | * This interface was referenced by `McpSchema`'s JSON-Schema 50 | * via the `definition` "Cursor". 51 | */ 52 | export type Cursor = string; 53 | /** 54 | * A uniquely identifying ID for a request in JSON-RPC. 55 | * 56 | * This interface was referenced by `McpSchema`'s JSON-Schema 57 | * via the `definition` "RequestId". 58 | */ 59 | export type RequestId = string | number; 60 | /** 61 | * A JSON-RPC batch request, as described in https://www.jsonrpc.org/specification#batch. 62 | * 63 | * This interface was referenced by `McpSchema`'s JSON-Schema 64 | * via the `definition` "JSONRPCBatchRequest". 65 | */ 66 | export type JSONRPCBatchRequest = (JSONRPCRequest | JSONRPCNotification)[]; 67 | /** 68 | * A JSON-RPC batch response, as described in https://www.jsonrpc.org/specification#batch. 69 | * 70 | * This interface was referenced by `McpSchema`'s JSON-Schema 71 | * via the `definition` "JSONRPCBatchResponse". 72 | */ 73 | export type JSONRPCBatchResponse = (JSONRPCResponse | JSONRPCError)[]; 74 | /** 75 | * Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. 76 | * 77 | * This interface was referenced by `McpSchema`'s JSON-Schema 78 | * via the `definition` "JSONRPCMessage". 79 | */ 80 | export type JSONRPCMessage = 81 | | JSONRPCRequest 82 | | JSONRPCNotification 83 | | (JSONRPCRequest | JSONRPCNotification)[] 84 | | JSONRPCResponse 85 | | JSONRPCError 86 | | (JSONRPCResponse | JSONRPCError)[]; 87 | /** 88 | * The severity of a log message. 89 | * 90 | * These map to syslog message severities, as specified in RFC-5424: 91 | * https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 92 | * 93 | * This interface was referenced by `McpSchema`'s JSON-Schema 94 | * via the `definition` "LoggingLevel". 95 | */ 96 | export type LoggingLevel = "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning"; 97 | /** 98 | * A progress token, used to associate progress notifications with the original request. 99 | * 100 | * This interface was referenced by `McpSchema`'s JSON-Schema 101 | * via the `definition` "ProgressToken". 102 | */ 103 | export type ProgressToken = string | number; 104 | /** 105 | * This interface was referenced by `McpSchema`'s JSON-Schema 106 | * via the `definition` "ServerNotification". 107 | */ 108 | export type ServerNotification = 109 | | CancelledNotification 110 | | ProgressNotification 111 | | ResourceListChangedNotification 112 | | ResourceUpdatedNotification 113 | | PromptListChangedNotification 114 | | ToolListChangedNotification 115 | | LoggingMessageNotification; 116 | /** 117 | * This interface was referenced by `McpSchema`'s JSON-Schema 118 | * via the `definition` "ServerRequest". 119 | */ 120 | export type ServerRequest = PingRequest | CreateMessageRequest | ListRootsRequest; 121 | /** 122 | * This interface was referenced by `McpSchema`'s JSON-Schema 123 | * via the `definition` "ServerResult". 124 | */ 125 | export type ServerResult = 126 | | Result 127 | | InitializeResult 128 | | ListResourcesResult 129 | | ReadResourceResult 130 | | ListPromptsResult 131 | | GetPromptResult 132 | | ListToolsResult 133 | | CallToolResult 134 | | CompleteResult; 135 | 136 | export interface McpSchema { 137 | [k: string]: unknown; 138 | } 139 | /** 140 | * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed 141 | * 142 | * This interface was referenced by `McpSchema`'s JSON-Schema 143 | * via the `definition` "Annotations". 144 | */ 145 | export interface Annotations { 146 | /** 147 | * Describes who the intended customer of this object or data is. 148 | * 149 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 150 | */ 151 | audience?: Role[]; 152 | /** 153 | * Describes how important this data is for operating the server. 154 | * 155 | * A value of 1 means "most important," and indicates that the data is 156 | * effectively required, while 0 means "least important," and indicates that 157 | * the data is entirely optional. 158 | */ 159 | priority?: number; 160 | [k: string]: unknown; 161 | } 162 | /** 163 | * Audio provided to or from an LLM. 164 | * 165 | * This interface was referenced by `McpSchema`'s JSON-Schema 166 | * via the `definition` "AudioContent". 167 | */ 168 | export interface AudioContent { 169 | annotations?: Annotations1; 170 | /** 171 | * The base64-encoded audio data. 172 | */ 173 | data: string; 174 | /** 175 | * The MIME type of the audio. Different providers may support different audio types. 176 | */ 177 | mimeType: string; 178 | type: "audio"; 179 | [k: string]: unknown; 180 | } 181 | /** 182 | * Optional annotations for the client. 183 | */ 184 | export interface Annotations1 { 185 | /** 186 | * Describes who the intended customer of this object or data is. 187 | * 188 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 189 | */ 190 | audience?: Role[]; 191 | /** 192 | * Describes how important this data is for operating the server. 193 | * 194 | * A value of 1 means "most important," and indicates that the data is 195 | * effectively required, while 0 means "least important," and indicates that 196 | * the data is entirely optional. 197 | */ 198 | priority?: number; 199 | [k: string]: unknown; 200 | } 201 | /** 202 | * This interface was referenced by `McpSchema`'s JSON-Schema 203 | * via the `definition` "BlobResourceContents". 204 | */ 205 | export interface BlobResourceContents { 206 | /** 207 | * A base64-encoded string representing the binary data of the item. 208 | */ 209 | blob: string; 210 | /** 211 | * The MIME type of this resource, if known. 212 | */ 213 | mimeType?: string; 214 | /** 215 | * The URI of this resource. 216 | */ 217 | uri: string; 218 | [k: string]: unknown; 219 | } 220 | /** 221 | * Used by the client to invoke a tool provided by the server. 222 | * 223 | * This interface was referenced by `McpSchema`'s JSON-Schema 224 | * via the `definition` "CallToolRequest". 225 | */ 226 | export interface CallToolRequest { 227 | method: "tools/call"; 228 | params: { 229 | arguments?: { 230 | [k: string]: unknown; 231 | }; 232 | name: string; 233 | [k: string]: unknown; 234 | }; 235 | [k: string]: unknown; 236 | } 237 | /** 238 | * The server's response to a tool call. 239 | * 240 | * Any errors that originate from the tool SHOULD be reported inside the result 241 | * object, with `isError` set to true, _not_ as an MCP protocol-level error 242 | * response. Otherwise, the LLM would not be able to see that an error occurred 243 | * and self-correct. 244 | * 245 | * However, any errors in _finding_ the tool, an error indicating that the 246 | * server does not support tool calls, or any other exceptional conditions, 247 | * should be reported as an MCP error response. 248 | * 249 | * This interface was referenced by `McpSchema`'s JSON-Schema 250 | * via the `definition` "CallToolResult". 251 | */ 252 | export interface CallToolResult { 253 | /** 254 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 255 | */ 256 | _meta?: { 257 | [k: string]: unknown; 258 | }; 259 | content: (TextContent | ImageContent | AudioContent | EmbeddedResource)[]; 260 | /** 261 | * Whether the tool call ended in an error. 262 | * 263 | * If not set, this is assumed to be false (the call was successful). 264 | */ 265 | isError?: boolean; 266 | [k: string]: unknown; 267 | } 268 | /** 269 | * Text provided to or from an LLM. 270 | * 271 | * This interface was referenced by `McpSchema`'s JSON-Schema 272 | * via the `definition` "TextContent". 273 | */ 274 | export interface TextContent { 275 | annotations?: Annotations2; 276 | /** 277 | * The text content of the message. 278 | */ 279 | text: string; 280 | type: "text"; 281 | [k: string]: unknown; 282 | } 283 | /** 284 | * Optional annotations for the client. 285 | */ 286 | export interface Annotations2 { 287 | /** 288 | * Describes who the intended customer of this object or data is. 289 | * 290 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 291 | */ 292 | audience?: Role[]; 293 | /** 294 | * Describes how important this data is for operating the server. 295 | * 296 | * A value of 1 means "most important," and indicates that the data is 297 | * effectively required, while 0 means "least important," and indicates that 298 | * the data is entirely optional. 299 | */ 300 | priority?: number; 301 | [k: string]: unknown; 302 | } 303 | /** 304 | * An image provided to or from an LLM. 305 | * 306 | * This interface was referenced by `McpSchema`'s JSON-Schema 307 | * via the `definition` "ImageContent". 308 | */ 309 | export interface ImageContent { 310 | annotations?: Annotations3; 311 | /** 312 | * The base64-encoded image data. 313 | */ 314 | data: string; 315 | /** 316 | * The MIME type of the image. Different providers may support different image types. 317 | */ 318 | mimeType: string; 319 | type: "image"; 320 | [k: string]: unknown; 321 | } 322 | /** 323 | * Optional annotations for the client. 324 | */ 325 | export interface Annotations3 { 326 | /** 327 | * Describes who the intended customer of this object or data is. 328 | * 329 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 330 | */ 331 | audience?: Role[]; 332 | /** 333 | * Describes how important this data is for operating the server. 334 | * 335 | * A value of 1 means "most important," and indicates that the data is 336 | * effectively required, while 0 means "least important," and indicates that 337 | * the data is entirely optional. 338 | */ 339 | priority?: number; 340 | [k: string]: unknown; 341 | } 342 | /** 343 | * The contents of a resource, embedded into a prompt or tool call result. 344 | * 345 | * It is up to the client how best to render embedded resources for the benefit 346 | * of the LLM and/or the user. 347 | * 348 | * This interface was referenced by `McpSchema`'s JSON-Schema 349 | * via the `definition` "EmbeddedResource". 350 | */ 351 | export interface EmbeddedResource { 352 | annotations?: Annotations4; 353 | resource: TextResourceContents | BlobResourceContents; 354 | type: "resource"; 355 | [k: string]: unknown; 356 | } 357 | /** 358 | * Optional annotations for the client. 359 | */ 360 | export interface Annotations4 { 361 | /** 362 | * Describes who the intended customer of this object or data is. 363 | * 364 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 365 | */ 366 | audience?: Role[]; 367 | /** 368 | * Describes how important this data is for operating the server. 369 | * 370 | * A value of 1 means "most important," and indicates that the data is 371 | * effectively required, while 0 means "least important," and indicates that 372 | * the data is entirely optional. 373 | */ 374 | priority?: number; 375 | [k: string]: unknown; 376 | } 377 | /** 378 | * This interface was referenced by `McpSchema`'s JSON-Schema 379 | * via the `definition` "TextResourceContents". 380 | */ 381 | export interface TextResourceContents { 382 | /** 383 | * The MIME type of this resource, if known. 384 | */ 385 | mimeType?: string; 386 | /** 387 | * The text of the item. This must only be set if the item can actually be represented as text (not binary data). 388 | */ 389 | text: string; 390 | /** 391 | * The URI of this resource. 392 | */ 393 | uri: string; 394 | [k: string]: unknown; 395 | } 396 | /** 397 | * This notification can be sent by either side to indicate that it is cancelling a previously-issued request. 398 | * 399 | * The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. 400 | * 401 | * This notification indicates that the result will be unused, so any associated processing SHOULD cease. 402 | * 403 | * A client MUST NOT attempt to cancel its `initialize` request. 404 | * 405 | * This interface was referenced by `McpSchema`'s JSON-Schema 406 | * via the `definition` "CancelledNotification". 407 | */ 408 | export interface CancelledNotification { 409 | method: "notifications/cancelled"; 410 | params: { 411 | /** 412 | * An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. 413 | */ 414 | reason?: string; 415 | /** 416 | * The ID of the request to cancel. 417 | * 418 | * This MUST correspond to the ID of a request previously issued in the same direction. 419 | */ 420 | requestId: string | number; 421 | [k: string]: unknown; 422 | }; 423 | [k: string]: unknown; 424 | } 425 | /** 426 | * Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. 427 | * 428 | * This interface was referenced by `McpSchema`'s JSON-Schema 429 | * via the `definition` "ClientCapabilities". 430 | */ 431 | export interface ClientCapabilities { 432 | /** 433 | * Experimental, non-standard capabilities that the client supports. 434 | */ 435 | experimental?: { 436 | [k: string]: { 437 | [k: string]: unknown; 438 | }; 439 | }; 440 | /** 441 | * Present if the client supports listing roots. 442 | */ 443 | roots?: { 444 | /** 445 | * Whether the client supports notifications for changes to the roots list. 446 | */ 447 | listChanged?: boolean; 448 | [k: string]: unknown; 449 | }; 450 | /** 451 | * Present if the client supports sampling from an LLM. 452 | */ 453 | sampling?: { 454 | [k: string]: unknown; 455 | }; 456 | [k: string]: unknown; 457 | } 458 | /** 459 | * This notification is sent from the client to the server after initialization has finished. 460 | * 461 | * This interface was referenced by `McpSchema`'s JSON-Schema 462 | * via the `definition` "InitializedNotification". 463 | */ 464 | export interface InitializedNotification { 465 | method: "notifications/initialized"; 466 | params?: { 467 | /** 468 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 469 | */ 470 | _meta?: { 471 | [k: string]: unknown; 472 | }; 473 | [k: string]: unknown; 474 | }; 475 | [k: string]: unknown; 476 | } 477 | /** 478 | * An out-of-band notification used to inform the receiver of a progress update for a long-running request. 479 | * 480 | * This interface was referenced by `McpSchema`'s JSON-Schema 481 | * via the `definition` "ProgressNotification". 482 | */ 483 | export interface ProgressNotification { 484 | method: "notifications/progress"; 485 | params: { 486 | /** 487 | * An optional message describing the current progress. 488 | */ 489 | message?: string; 490 | /** 491 | * The progress thus far. This should increase every time progress is made, even if the total is unknown. 492 | */ 493 | progress: number; 494 | /** 495 | * The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. 496 | */ 497 | progressToken: string | number; 498 | /** 499 | * Total number of items to process (or total progress required), if known. 500 | */ 501 | total?: number; 502 | [k: string]: unknown; 503 | }; 504 | [k: string]: unknown; 505 | } 506 | /** 507 | * A notification from the client to the server, informing it that the list of roots has changed. 508 | * This notification should be sent whenever the client adds, removes, or modifies any root. 509 | * The server should then request an updated list of roots using the ListRootsRequest. 510 | * 511 | * This interface was referenced by `McpSchema`'s JSON-Schema 512 | * via the `definition` "RootsListChangedNotification". 513 | */ 514 | export interface RootsListChangedNotification { 515 | method: "notifications/roots/list_changed"; 516 | params?: { 517 | /** 518 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 519 | */ 520 | _meta?: { 521 | [k: string]: unknown; 522 | }; 523 | [k: string]: unknown; 524 | }; 525 | [k: string]: unknown; 526 | } 527 | /** 528 | * This request is sent from the client to the server when it first connects, asking it to begin initialization. 529 | * 530 | * This interface was referenced by `McpSchema`'s JSON-Schema 531 | * via the `definition` "InitializeRequest". 532 | */ 533 | export interface InitializeRequest { 534 | method: "initialize"; 535 | params: { 536 | capabilities: ClientCapabilities; 537 | clientInfo: Implementation; 538 | /** 539 | * The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. 540 | */ 541 | protocolVersion: string; 542 | [k: string]: unknown; 543 | }; 544 | [k: string]: unknown; 545 | } 546 | /** 547 | * Describes the name and version of an MCP implementation. 548 | * 549 | * This interface was referenced by `McpSchema`'s JSON-Schema 550 | * via the `definition` "Implementation". 551 | */ 552 | export interface Implementation { 553 | name: string; 554 | version: string; 555 | [k: string]: unknown; 556 | } 557 | /** 558 | * A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. 559 | * 560 | * This interface was referenced by `McpSchema`'s JSON-Schema 561 | * via the `definition` "PingRequest". 562 | */ 563 | export interface PingRequest { 564 | method: "ping"; 565 | params?: { 566 | _meta?: { 567 | /** 568 | * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. 569 | */ 570 | progressToken?: string | number; 571 | [k: string]: unknown; 572 | }; 573 | [k: string]: unknown; 574 | }; 575 | [k: string]: unknown; 576 | } 577 | /** 578 | * Sent from the client to request a list of resources the server has. 579 | * 580 | * This interface was referenced by `McpSchema`'s JSON-Schema 581 | * via the `definition` "ListResourcesRequest". 582 | */ 583 | export interface ListResourcesRequest { 584 | method: "resources/list"; 585 | params?: { 586 | /** 587 | * An opaque token representing the current pagination position. 588 | * If provided, the server should return results starting after this cursor. 589 | */ 590 | cursor?: string; 591 | [k: string]: unknown; 592 | }; 593 | [k: string]: unknown; 594 | } 595 | /** 596 | * Sent from the client to the server, to read a specific resource URI. 597 | * 598 | * This interface was referenced by `McpSchema`'s JSON-Schema 599 | * via the `definition` "ReadResourceRequest". 600 | */ 601 | export interface ReadResourceRequest { 602 | method: "resources/read"; 603 | params: { 604 | /** 605 | * The URI of the resource to read. The URI can use any protocol; it is up to the server how to interpret it. 606 | */ 607 | uri: string; 608 | [k: string]: unknown; 609 | }; 610 | [k: string]: unknown; 611 | } 612 | /** 613 | * Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. 614 | * 615 | * This interface was referenced by `McpSchema`'s JSON-Schema 616 | * via the `definition` "SubscribeRequest". 617 | */ 618 | export interface SubscribeRequest { 619 | method: "resources/subscribe"; 620 | params: { 621 | /** 622 | * The URI of the resource to subscribe to. The URI can use any protocol; it is up to the server how to interpret it. 623 | */ 624 | uri: string; 625 | [k: string]: unknown; 626 | }; 627 | [k: string]: unknown; 628 | } 629 | /** 630 | * Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. 631 | * 632 | * This interface was referenced by `McpSchema`'s JSON-Schema 633 | * via the `definition` "UnsubscribeRequest". 634 | */ 635 | export interface UnsubscribeRequest { 636 | method: "resources/unsubscribe"; 637 | params: { 638 | /** 639 | * The URI of the resource to unsubscribe from. 640 | */ 641 | uri: string; 642 | [k: string]: unknown; 643 | }; 644 | [k: string]: unknown; 645 | } 646 | /** 647 | * Sent from the client to request a list of prompts and prompt templates the server has. 648 | * 649 | * This interface was referenced by `McpSchema`'s JSON-Schema 650 | * via the `definition` "ListPromptsRequest". 651 | */ 652 | export interface ListPromptsRequest { 653 | method: "prompts/list"; 654 | params?: { 655 | /** 656 | * An opaque token representing the current pagination position. 657 | * If provided, the server should return results starting after this cursor. 658 | */ 659 | cursor?: string; 660 | [k: string]: unknown; 661 | }; 662 | [k: string]: unknown; 663 | } 664 | /** 665 | * Used by the client to get a prompt provided by the server. 666 | * 667 | * This interface was referenced by `McpSchema`'s JSON-Schema 668 | * via the `definition` "GetPromptRequest". 669 | */ 670 | export interface GetPromptRequest { 671 | method: "prompts/get"; 672 | params: { 673 | /** 674 | * Arguments to use for templating the prompt. 675 | */ 676 | arguments?: { 677 | [k: string]: string; 678 | }; 679 | /** 680 | * The name of the prompt or prompt template. 681 | */ 682 | name: string; 683 | [k: string]: unknown; 684 | }; 685 | [k: string]: unknown; 686 | } 687 | /** 688 | * Sent from the client to request a list of tools the server has. 689 | * 690 | * This interface was referenced by `McpSchema`'s JSON-Schema 691 | * via the `definition` "ListToolsRequest". 692 | */ 693 | export interface ListToolsRequest { 694 | method: "tools/list"; 695 | params?: { 696 | /** 697 | * An opaque token representing the current pagination position. 698 | * If provided, the server should return results starting after this cursor. 699 | */ 700 | cursor?: string; 701 | [k: string]: unknown; 702 | }; 703 | [k: string]: unknown; 704 | } 705 | /** 706 | * A request from the client to the server, to enable or adjust logging. 707 | * 708 | * This interface was referenced by `McpSchema`'s JSON-Schema 709 | * via the `definition` "SetLevelRequest". 710 | */ 711 | export interface SetLevelRequest { 712 | method: "logging/setLevel"; 713 | params: { 714 | /** 715 | * The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. 716 | */ 717 | level: "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning"; 718 | [k: string]: unknown; 719 | }; 720 | [k: string]: unknown; 721 | } 722 | /** 723 | * A request from the client to the server, to ask for completion options. 724 | * 725 | * This interface was referenced by `McpSchema`'s JSON-Schema 726 | * via the `definition` "CompleteRequest". 727 | */ 728 | export interface CompleteRequest { 729 | method: "completion/complete"; 730 | params: { 731 | /** 732 | * The argument's information 733 | */ 734 | argument: { 735 | /** 736 | * The name of the argument 737 | */ 738 | name: string; 739 | /** 740 | * The value of the argument to use for completion matching. 741 | */ 742 | value: string; 743 | [k: string]: unknown; 744 | }; 745 | ref: PromptReference | ResourceReference; 746 | [k: string]: unknown; 747 | }; 748 | [k: string]: unknown; 749 | } 750 | /** 751 | * Identifies a prompt. 752 | * 753 | * This interface was referenced by `McpSchema`'s JSON-Schema 754 | * via the `definition` "PromptReference". 755 | */ 756 | export interface PromptReference { 757 | /** 758 | * The name of the prompt or prompt template 759 | */ 760 | name: string; 761 | type: "ref/prompt"; 762 | [k: string]: unknown; 763 | } 764 | /** 765 | * A reference to a resource or resource template definition. 766 | * 767 | * This interface was referenced by `McpSchema`'s JSON-Schema 768 | * via the `definition` "ResourceReference". 769 | */ 770 | export interface ResourceReference { 771 | type: "ref/resource"; 772 | /** 773 | * The URI or URI template of the resource. 774 | */ 775 | uri: string; 776 | [k: string]: unknown; 777 | } 778 | /** 779 | * This interface was referenced by `McpSchema`'s JSON-Schema 780 | * via the `definition` "EmptyResult". 781 | * 782 | * This interface was referenced by `McpSchema`'s JSON-Schema 783 | * via the `definition` "Result". 784 | */ 785 | export interface Result { 786 | /** 787 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 788 | */ 789 | _meta?: { 790 | [k: string]: unknown; 791 | }; 792 | [k: string]: unknown; 793 | } 794 | /** 795 | * The client's response to a sampling/create_message request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it. 796 | * 797 | * This interface was referenced by `McpSchema`'s JSON-Schema 798 | * via the `definition` "CreateMessageResult". 799 | */ 800 | export interface CreateMessageResult { 801 | /** 802 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 803 | */ 804 | _meta?: { 805 | [k: string]: unknown; 806 | }; 807 | content: TextContent | ImageContent | AudioContent; 808 | /** 809 | * The name of the model that generated the message. 810 | */ 811 | model: string; 812 | role: Role; 813 | /** 814 | * The reason why sampling stopped, if known. 815 | */ 816 | stopReason?: string; 817 | [k: string]: unknown; 818 | } 819 | /** 820 | * The client's response to a roots/list request from the server. 821 | * This result contains an array of Root objects, each representing a root directory 822 | * or file that the server can operate on. 823 | * 824 | * This interface was referenced by `McpSchema`'s JSON-Schema 825 | * via the `definition` "ListRootsResult". 826 | */ 827 | export interface ListRootsResult { 828 | /** 829 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 830 | */ 831 | _meta?: { 832 | [k: string]: unknown; 833 | }; 834 | roots: Root[]; 835 | [k: string]: unknown; 836 | } 837 | /** 838 | * Represents a root directory or file that the server can operate on. 839 | * 840 | * This interface was referenced by `McpSchema`'s JSON-Schema 841 | * via the `definition` "Root". 842 | */ 843 | export interface Root { 844 | /** 845 | * An optional name for the root. This can be used to provide a human-readable 846 | * identifier for the root, which may be useful for display purposes or for 847 | * referencing the root in other parts of the application. 848 | */ 849 | name?: string; 850 | /** 851 | * The URI identifying the root. This *must* start with file:// for now. 852 | * This restriction may be relaxed in future versions of the protocol to allow 853 | * other URI schemes. 854 | */ 855 | uri: string; 856 | [k: string]: unknown; 857 | } 858 | /** 859 | * The server's response to a completion/complete request 860 | * 861 | * This interface was referenced by `McpSchema`'s JSON-Schema 862 | * via the `definition` "CompleteResult". 863 | */ 864 | export interface CompleteResult { 865 | /** 866 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 867 | */ 868 | _meta?: { 869 | [k: string]: unknown; 870 | }; 871 | completion: { 872 | /** 873 | * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. 874 | */ 875 | hasMore?: boolean; 876 | /** 877 | * The total number of completion options available. This can exceed the number of values actually sent in the response. 878 | */ 879 | total?: number; 880 | /** 881 | * An array of completion values. Must not exceed 100 items. 882 | */ 883 | values: string[]; 884 | [k: string]: unknown; 885 | }; 886 | [k: string]: unknown; 887 | } 888 | /** 889 | * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. 890 | * 891 | * This interface was referenced by `McpSchema`'s JSON-Schema 892 | * via the `definition` "CreateMessageRequest". 893 | */ 894 | export interface CreateMessageRequest { 895 | method: "sampling/createMessage"; 896 | params: { 897 | /** 898 | * A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. 899 | */ 900 | includeContext?: "allServers" | "none" | "thisServer"; 901 | /** 902 | * The maximum number of tokens to sample, as requested by the server. The client MAY choose to sample fewer tokens than requested. 903 | */ 904 | maxTokens: number; 905 | messages: SamplingMessage[]; 906 | /** 907 | * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. 908 | */ 909 | metadata?: { 910 | [k: string]: unknown; 911 | }; 912 | modelPreferences?: ModelPreferences; 913 | stopSequences?: string[]; 914 | /** 915 | * An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. 916 | */ 917 | systemPrompt?: string; 918 | temperature?: number; 919 | [k: string]: unknown; 920 | }; 921 | [k: string]: unknown; 922 | } 923 | /** 924 | * Describes a message issued to or received from an LLM API. 925 | * 926 | * This interface was referenced by `McpSchema`'s JSON-Schema 927 | * via the `definition` "SamplingMessage". 928 | */ 929 | export interface SamplingMessage { 930 | content: TextContent | ImageContent | AudioContent; 931 | role: Role; 932 | [k: string]: unknown; 933 | } 934 | /** 935 | * The server's preferences for which model to select. The client MAY ignore these preferences. 936 | */ 937 | export interface ModelPreferences { 938 | /** 939 | * How much to prioritize cost when selecting a model. A value of 0 means cost 940 | * is not important, while a value of 1 means cost is the most important 941 | * factor. 942 | */ 943 | costPriority?: number; 944 | /** 945 | * Optional hints to use for model selection. 946 | * 947 | * If multiple hints are specified, the client MUST evaluate them in order 948 | * (such that the first match is taken). 949 | * 950 | * The client SHOULD prioritize these hints over the numeric priorities, but 951 | * MAY still use the priorities to select from ambiguous matches. 952 | */ 953 | hints?: ModelHint[]; 954 | /** 955 | * How much to prioritize intelligence and capabilities when selecting a 956 | * model. A value of 0 means intelligence is not important, while a value of 1 957 | * means intelligence is the most important factor. 958 | */ 959 | intelligencePriority?: number; 960 | /** 961 | * How much to prioritize sampling speed (latency) when selecting a model. A 962 | * value of 0 means speed is not important, while a value of 1 means speed is 963 | * the most important factor. 964 | */ 965 | speedPriority?: number; 966 | [k: string]: unknown; 967 | } 968 | /** 969 | * Hints to use for model selection. 970 | * 971 | * Keys not declared here are currently left unspecified by the spec and are up 972 | * to the client to interpret. 973 | * 974 | * This interface was referenced by `McpSchema`'s JSON-Schema 975 | * via the `definition` "ModelHint". 976 | */ 977 | export interface ModelHint { 978 | /** 979 | * A hint for a model name. 980 | * 981 | * The client SHOULD treat this as a substring of a model name; for example: 982 | * - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` 983 | * - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. 984 | * - `claude` should match any Claude model 985 | * 986 | * The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: 987 | * - `gemini-1.5-flash` could match `claude-3-haiku-20240307` 988 | */ 989 | name?: string; 990 | [k: string]: unknown; 991 | } 992 | /** 993 | * The server's response to a prompts/get request from the client. 994 | * 995 | * This interface was referenced by `McpSchema`'s JSON-Schema 996 | * via the `definition` "GetPromptResult". 997 | */ 998 | export interface GetPromptResult { 999 | /** 1000 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1001 | */ 1002 | _meta?: { 1003 | [k: string]: unknown; 1004 | }; 1005 | /** 1006 | * An optional description for the prompt. 1007 | */ 1008 | description?: string; 1009 | messages: PromptMessage[]; 1010 | [k: string]: unknown; 1011 | } 1012 | /** 1013 | * Describes a message returned as part of a prompt. 1014 | * 1015 | * This is similar to `SamplingMessage`, but also supports the embedding of 1016 | * resources from the MCP server. 1017 | * 1018 | * This interface was referenced by `McpSchema`'s JSON-Schema 1019 | * via the `definition` "PromptMessage". 1020 | */ 1021 | export interface PromptMessage { 1022 | content: TextContent | ImageContent | AudioContent | EmbeddedResource; 1023 | role: Role; 1024 | [k: string]: unknown; 1025 | } 1026 | /** 1027 | * After receiving an initialize request from the client, the server sends this response. 1028 | * 1029 | * This interface was referenced by `McpSchema`'s JSON-Schema 1030 | * via the `definition` "InitializeResult". 1031 | */ 1032 | export interface InitializeResult { 1033 | /** 1034 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1035 | */ 1036 | _meta?: { 1037 | [k: string]: unknown; 1038 | }; 1039 | capabilities: ServerCapabilities; 1040 | /** 1041 | * Instructions describing how to use the server and its features. 1042 | * 1043 | * This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. 1044 | */ 1045 | instructions?: string; 1046 | /** 1047 | * The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. 1048 | */ 1049 | protocolVersion: string; 1050 | serverInfo: Implementation; 1051 | [k: string]: unknown; 1052 | } 1053 | /** 1054 | * Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. 1055 | * 1056 | * This interface was referenced by `McpSchema`'s JSON-Schema 1057 | * via the `definition` "ServerCapabilities". 1058 | */ 1059 | export interface ServerCapabilities { 1060 | /** 1061 | * Present if the server supports argument autocompletion suggestions. 1062 | */ 1063 | completions?: { 1064 | [k: string]: unknown; 1065 | }; 1066 | /** 1067 | * Experimental, non-standard capabilities that the server supports. 1068 | */ 1069 | experimental?: { 1070 | [k: string]: { 1071 | [k: string]: unknown; 1072 | }; 1073 | }; 1074 | /** 1075 | * Present if the server supports sending log messages to the client. 1076 | */ 1077 | logging?: { 1078 | [k: string]: unknown; 1079 | }; 1080 | /** 1081 | * Present if the server offers any prompt templates. 1082 | */ 1083 | prompts?: { 1084 | /** 1085 | * Whether this server supports notifications for changes to the prompt list. 1086 | */ 1087 | listChanged?: boolean; 1088 | [k: string]: unknown; 1089 | }; 1090 | /** 1091 | * Present if the server offers any resources to read. 1092 | */ 1093 | resources?: { 1094 | /** 1095 | * Whether this server supports notifications for changes to the resource list. 1096 | */ 1097 | listChanged?: boolean; 1098 | /** 1099 | * Whether this server supports subscribing to resource updates. 1100 | */ 1101 | subscribe?: boolean; 1102 | [k: string]: unknown; 1103 | }; 1104 | /** 1105 | * Present if the server offers any tools to call. 1106 | */ 1107 | tools?: { 1108 | /** 1109 | * Whether this server supports notifications for changes to the tool list. 1110 | */ 1111 | listChanged?: boolean; 1112 | [k: string]: unknown; 1113 | }; 1114 | [k: string]: unknown; 1115 | } 1116 | /** 1117 | * A request that expects a response. 1118 | * 1119 | * This interface was referenced by `McpSchema`'s JSON-Schema 1120 | * via the `definition` "JSONRPCRequest". 1121 | */ 1122 | export interface JSONRPCRequest { 1123 | id: RequestId; 1124 | jsonrpc: "2.0"; 1125 | method: string; 1126 | params?: { 1127 | _meta?: { 1128 | /** 1129 | * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. 1130 | */ 1131 | progressToken?: string | number; 1132 | [k: string]: unknown; 1133 | }; 1134 | [k: string]: unknown; 1135 | }; 1136 | [k: string]: unknown; 1137 | } 1138 | /** 1139 | * A notification which does not expect a response. 1140 | * 1141 | * This interface was referenced by `McpSchema`'s JSON-Schema 1142 | * via the `definition` "JSONRPCNotification". 1143 | */ 1144 | export interface JSONRPCNotification { 1145 | jsonrpc: "2.0"; 1146 | method: string; 1147 | params?: { 1148 | /** 1149 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 1150 | */ 1151 | _meta?: { 1152 | [k: string]: unknown; 1153 | }; 1154 | [k: string]: unknown; 1155 | }; 1156 | [k: string]: unknown; 1157 | } 1158 | /** 1159 | * A successful (non-error) response to a request. 1160 | * 1161 | * This interface was referenced by `McpSchema`'s JSON-Schema 1162 | * via the `definition` "JSONRPCResponse". 1163 | */ 1164 | export interface JSONRPCResponse { 1165 | id: RequestId; 1166 | jsonrpc: "2.0"; 1167 | result: Result; 1168 | [k: string]: unknown; 1169 | } 1170 | /** 1171 | * A response to a request that indicates an error occurred. 1172 | * 1173 | * This interface was referenced by `McpSchema`'s JSON-Schema 1174 | * via the `definition` "JSONRPCError". 1175 | */ 1176 | export interface JSONRPCError { 1177 | error: { 1178 | /** 1179 | * The error type that occurred. 1180 | */ 1181 | code: number; 1182 | /** 1183 | * Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). 1184 | */ 1185 | data?: { 1186 | [k: string]: unknown; 1187 | }; 1188 | /** 1189 | * A short description of the error. The message SHOULD be limited to a concise single sentence. 1190 | */ 1191 | message: string; 1192 | [k: string]: unknown; 1193 | }; 1194 | id: RequestId; 1195 | jsonrpc: "2.0"; 1196 | [k: string]: unknown; 1197 | } 1198 | /** 1199 | * The server's response to a prompts/list request from the client. 1200 | * 1201 | * This interface was referenced by `McpSchema`'s JSON-Schema 1202 | * via the `definition` "ListPromptsResult". 1203 | */ 1204 | export interface ListPromptsResult { 1205 | /** 1206 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1207 | */ 1208 | _meta?: { 1209 | [k: string]: unknown; 1210 | }; 1211 | /** 1212 | * An opaque token representing the pagination position after the last returned result. 1213 | * If present, there may be more results available. 1214 | */ 1215 | nextCursor?: string; 1216 | prompts: Prompt[]; 1217 | [k: string]: unknown; 1218 | } 1219 | /** 1220 | * A prompt or prompt template that the server offers. 1221 | * 1222 | * This interface was referenced by `McpSchema`'s JSON-Schema 1223 | * via the `definition` "Prompt". 1224 | */ 1225 | export interface Prompt { 1226 | /** 1227 | * A list of arguments to use for templating the prompt. 1228 | */ 1229 | arguments?: PromptArgument[]; 1230 | /** 1231 | * An optional description of what this prompt provides 1232 | */ 1233 | description?: string; 1234 | /** 1235 | * The name of the prompt or prompt template. 1236 | */ 1237 | name: string; 1238 | [k: string]: unknown; 1239 | } 1240 | /** 1241 | * Describes an argument that a prompt can accept. 1242 | * 1243 | * This interface was referenced by `McpSchema`'s JSON-Schema 1244 | * via the `definition` "PromptArgument". 1245 | */ 1246 | export interface PromptArgument { 1247 | /** 1248 | * A human-readable description of the argument. 1249 | */ 1250 | description?: string; 1251 | /** 1252 | * The name of the argument. 1253 | */ 1254 | name: string; 1255 | /** 1256 | * Whether this argument must be provided. 1257 | */ 1258 | required?: boolean; 1259 | [k: string]: unknown; 1260 | } 1261 | /** 1262 | * Sent from the client to request a list of resource templates the server has. 1263 | * 1264 | * This interface was referenced by `McpSchema`'s JSON-Schema 1265 | * via the `definition` "ListResourceTemplatesRequest". 1266 | */ 1267 | export interface ListResourceTemplatesRequest { 1268 | method: "resources/templates/list"; 1269 | params?: { 1270 | /** 1271 | * An opaque token representing the current pagination position. 1272 | * If provided, the server should return results starting after this cursor. 1273 | */ 1274 | cursor?: string; 1275 | [k: string]: unknown; 1276 | }; 1277 | [k: string]: unknown; 1278 | } 1279 | /** 1280 | * The server's response to a resources/templates/list request from the client. 1281 | * 1282 | * This interface was referenced by `McpSchema`'s JSON-Schema 1283 | * via the `definition` "ListResourceTemplatesResult". 1284 | */ 1285 | export interface ListResourceTemplatesResult { 1286 | /** 1287 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1288 | */ 1289 | _meta?: { 1290 | [k: string]: unknown; 1291 | }; 1292 | /** 1293 | * An opaque token representing the pagination position after the last returned result. 1294 | * If present, there may be more results available. 1295 | */ 1296 | nextCursor?: string; 1297 | resourceTemplates: ResourceTemplate[]; 1298 | [k: string]: unknown; 1299 | } 1300 | /** 1301 | * A template description for resources available on the server. 1302 | * 1303 | * This interface was referenced by `McpSchema`'s JSON-Schema 1304 | * via the `definition` "ResourceTemplate". 1305 | */ 1306 | export interface ResourceTemplate { 1307 | annotations?: Annotations5; 1308 | /** 1309 | * A description of what this template is for. 1310 | * 1311 | * This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. 1312 | */ 1313 | description?: string; 1314 | /** 1315 | * The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. 1316 | */ 1317 | mimeType?: string; 1318 | /** 1319 | * A human-readable name for the type of resource this template refers to. 1320 | * 1321 | * This can be used by clients to populate UI elements. 1322 | */ 1323 | name: string; 1324 | /** 1325 | * A URI template (according to RFC 6570) that can be used to construct resource URIs. 1326 | */ 1327 | uriTemplate: string; 1328 | [k: string]: unknown; 1329 | } 1330 | /** 1331 | * Optional annotations for the client. 1332 | */ 1333 | export interface Annotations5 { 1334 | /** 1335 | * Describes who the intended customer of this object or data is. 1336 | * 1337 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 1338 | */ 1339 | audience?: Role[]; 1340 | /** 1341 | * Describes how important this data is for operating the server. 1342 | * 1343 | * A value of 1 means "most important," and indicates that the data is 1344 | * effectively required, while 0 means "least important," and indicates that 1345 | * the data is entirely optional. 1346 | */ 1347 | priority?: number; 1348 | [k: string]: unknown; 1349 | } 1350 | /** 1351 | * The server's response to a resources/list request from the client. 1352 | * 1353 | * This interface was referenced by `McpSchema`'s JSON-Schema 1354 | * via the `definition` "ListResourcesResult". 1355 | */ 1356 | export interface ListResourcesResult { 1357 | /** 1358 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1359 | */ 1360 | _meta?: { 1361 | [k: string]: unknown; 1362 | }; 1363 | /** 1364 | * An opaque token representing the pagination position after the last returned result. 1365 | * If present, there may be more results available. 1366 | */ 1367 | nextCursor?: string; 1368 | resources: Resource[]; 1369 | [k: string]: unknown; 1370 | } 1371 | /** 1372 | * A known resource that the server is capable of reading. 1373 | * 1374 | * This interface was referenced by `McpSchema`'s JSON-Schema 1375 | * via the `definition` "Resource". 1376 | */ 1377 | export interface Resource { 1378 | annotations?: Annotations6; 1379 | /** 1380 | * A description of what this resource represents. 1381 | * 1382 | * This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. 1383 | */ 1384 | description?: string; 1385 | /** 1386 | * The MIME type of this resource, if known. 1387 | */ 1388 | mimeType?: string; 1389 | /** 1390 | * A human-readable name for this resource. 1391 | * 1392 | * This can be used by clients to populate UI elements. 1393 | */ 1394 | name: string; 1395 | /** 1396 | * The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. 1397 | * 1398 | * This can be used by Hosts to display file sizes and estimate context window usage. 1399 | */ 1400 | size?: number; 1401 | /** 1402 | * The URI of this resource. 1403 | */ 1404 | uri: string; 1405 | [k: string]: unknown; 1406 | } 1407 | /** 1408 | * Optional annotations for the client. 1409 | */ 1410 | export interface Annotations6 { 1411 | /** 1412 | * Describes who the intended customer of this object or data is. 1413 | * 1414 | * It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). 1415 | */ 1416 | audience?: Role[]; 1417 | /** 1418 | * Describes how important this data is for operating the server. 1419 | * 1420 | * A value of 1 means "most important," and indicates that the data is 1421 | * effectively required, while 0 means "least important," and indicates that 1422 | * the data is entirely optional. 1423 | */ 1424 | priority?: number; 1425 | [k: string]: unknown; 1426 | } 1427 | /** 1428 | * Sent from the server to request a list of root URIs from the client. Roots allow 1429 | * servers to ask for specific directories or files to operate on. A common example 1430 | * for roots is providing a set of repositories or directories a server should operate 1431 | * on. 1432 | * 1433 | * This request is typically used when the server needs to understand the file system 1434 | * structure or access specific locations that the client has permission to read from. 1435 | * 1436 | * This interface was referenced by `McpSchema`'s JSON-Schema 1437 | * via the `definition` "ListRootsRequest". 1438 | */ 1439 | export interface ListRootsRequest { 1440 | method: "roots/list"; 1441 | params?: { 1442 | _meta?: { 1443 | /** 1444 | * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. 1445 | */ 1446 | progressToken?: string | number; 1447 | [k: string]: unknown; 1448 | }; 1449 | [k: string]: unknown; 1450 | }; 1451 | [k: string]: unknown; 1452 | } 1453 | /** 1454 | * The server's response to a tools/list request from the client. 1455 | * 1456 | * This interface was referenced by `McpSchema`'s JSON-Schema 1457 | * via the `definition` "ListToolsResult". 1458 | */ 1459 | export interface ListToolsResult { 1460 | /** 1461 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1462 | */ 1463 | _meta?: { 1464 | [k: string]: unknown; 1465 | }; 1466 | /** 1467 | * An opaque token representing the pagination position after the last returned result. 1468 | * If present, there may be more results available. 1469 | */ 1470 | nextCursor?: string; 1471 | tools: Tool[]; 1472 | [k: string]: unknown; 1473 | } 1474 | /** 1475 | * Definition for a tool the client can call. 1476 | * 1477 | * This interface was referenced by `McpSchema`'s JSON-Schema 1478 | * via the `definition` "Tool". 1479 | */ 1480 | export interface Tool { 1481 | annotations?: ToolAnnotations; 1482 | /** 1483 | * A human-readable description of the tool. 1484 | * 1485 | * This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. 1486 | */ 1487 | description?: string; 1488 | /** 1489 | * A JSON Schema object defining the expected parameters for the tool. 1490 | */ 1491 | inputSchema: { 1492 | properties?: { 1493 | [k: string]: { 1494 | [k: string]: unknown; 1495 | }; 1496 | }; 1497 | required?: string[]; 1498 | type: "object"; 1499 | [k: string]: unknown; 1500 | }; 1501 | /** 1502 | * The name of the tool. 1503 | */ 1504 | name: string; 1505 | [k: string]: unknown; 1506 | } 1507 | /** 1508 | * Optional additional tool information. 1509 | */ 1510 | export interface ToolAnnotations { 1511 | /** 1512 | * If true, the tool may perform destructive updates to its environment. 1513 | * If false, the tool performs only additive updates. 1514 | * 1515 | * (This property is meaningful only when `readOnlyHint == false`) 1516 | * 1517 | * Default: true 1518 | */ 1519 | destructiveHint?: boolean; 1520 | /** 1521 | * If true, calling the tool repeatedly with the same arguments 1522 | * will have no additional effect on the its environment. 1523 | * 1524 | * (This property is meaningful only when `readOnlyHint == false`) 1525 | * 1526 | * Default: false 1527 | */ 1528 | idempotentHint?: boolean; 1529 | /** 1530 | * If true, this tool may interact with an "open world" of external 1531 | * entities. If false, the tool's domain of interaction is closed. 1532 | * For example, the world of a web search tool is open, whereas that 1533 | * of a memory tool is not. 1534 | * 1535 | * Default: true 1536 | */ 1537 | openWorldHint?: boolean; 1538 | /** 1539 | * If true, the tool does not modify its environment. 1540 | * 1541 | * Default: false 1542 | */ 1543 | readOnlyHint?: boolean; 1544 | /** 1545 | * A human-readable title for the tool. 1546 | */ 1547 | title?: string; 1548 | [k: string]: unknown; 1549 | } 1550 | /** 1551 | * Notification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically. 1552 | * 1553 | * This interface was referenced by `McpSchema`'s JSON-Schema 1554 | * via the `definition` "LoggingMessageNotification". 1555 | */ 1556 | export interface LoggingMessageNotification { 1557 | method: "notifications/message"; 1558 | params: { 1559 | /** 1560 | * The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. 1561 | */ 1562 | data: { 1563 | [k: string]: unknown; 1564 | }; 1565 | /** 1566 | * The severity of this log message. 1567 | */ 1568 | level: "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning"; 1569 | /** 1570 | * An optional name of the logger issuing this message. 1571 | */ 1572 | logger?: string; 1573 | [k: string]: unknown; 1574 | }; 1575 | [k: string]: unknown; 1576 | } 1577 | /** 1578 | * The server's preferences for model selection, requested of the client during sampling. 1579 | * 1580 | * Because LLMs can vary along multiple dimensions, choosing the "best" model is 1581 | * rarely straightforward. Different models excel in different areas—some are 1582 | * faster but less capable, others are more capable but more expensive, and so 1583 | * on. This interface allows servers to express their priorities across multiple 1584 | * dimensions to help clients make an appropriate selection for their use case. 1585 | * 1586 | * These preferences are always advisory. The client MAY ignore them. It is also 1587 | * up to the client to decide how to interpret these preferences and how to 1588 | * balance them against other considerations. 1589 | * 1590 | * This interface was referenced by `McpSchema`'s JSON-Schema 1591 | * via the `definition` "ModelPreferences". 1592 | */ 1593 | export interface ModelPreferences1 { 1594 | /** 1595 | * How much to prioritize cost when selecting a model. A value of 0 means cost 1596 | * is not important, while a value of 1 means cost is the most important 1597 | * factor. 1598 | */ 1599 | costPriority?: number; 1600 | /** 1601 | * Optional hints to use for model selection. 1602 | * 1603 | * If multiple hints are specified, the client MUST evaluate them in order 1604 | * (such that the first match is taken). 1605 | * 1606 | * The client SHOULD prioritize these hints over the numeric priorities, but 1607 | * MAY still use the priorities to select from ambiguous matches. 1608 | */ 1609 | hints?: ModelHint[]; 1610 | /** 1611 | * How much to prioritize intelligence and capabilities when selecting a 1612 | * model. A value of 0 means intelligence is not important, while a value of 1 1613 | * means intelligence is the most important factor. 1614 | */ 1615 | intelligencePriority?: number; 1616 | /** 1617 | * How much to prioritize sampling speed (latency) when selecting a model. A 1618 | * value of 0 means speed is not important, while a value of 1 means speed is 1619 | * the most important factor. 1620 | */ 1621 | speedPriority?: number; 1622 | [k: string]: unknown; 1623 | } 1624 | /** 1625 | * This interface was referenced by `McpSchema`'s JSON-Schema 1626 | * via the `definition` "Notification". 1627 | */ 1628 | export interface Notification { 1629 | method: string; 1630 | params?: { 1631 | /** 1632 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 1633 | */ 1634 | _meta?: { 1635 | [k: string]: unknown; 1636 | }; 1637 | [k: string]: unknown; 1638 | }; 1639 | [k: string]: unknown; 1640 | } 1641 | /** 1642 | * This interface was referenced by `McpSchema`'s JSON-Schema 1643 | * via the `definition` "PaginatedRequest". 1644 | */ 1645 | export interface PaginatedRequest { 1646 | method: string; 1647 | params?: { 1648 | /** 1649 | * An opaque token representing the current pagination position. 1650 | * If provided, the server should return results starting after this cursor. 1651 | */ 1652 | cursor?: string; 1653 | [k: string]: unknown; 1654 | }; 1655 | [k: string]: unknown; 1656 | } 1657 | /** 1658 | * This interface was referenced by `McpSchema`'s JSON-Schema 1659 | * via the `definition` "PaginatedResult". 1660 | */ 1661 | export interface PaginatedResult { 1662 | /** 1663 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1664 | */ 1665 | _meta?: { 1666 | [k: string]: unknown; 1667 | }; 1668 | /** 1669 | * An opaque token representing the pagination position after the last returned result. 1670 | * If present, there may be more results available. 1671 | */ 1672 | nextCursor?: string; 1673 | [k: string]: unknown; 1674 | } 1675 | /** 1676 | * An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. 1677 | * 1678 | * This interface was referenced by `McpSchema`'s JSON-Schema 1679 | * via the `definition` "PromptListChangedNotification". 1680 | */ 1681 | export interface PromptListChangedNotification { 1682 | method: "notifications/prompts/list_changed"; 1683 | params?: { 1684 | /** 1685 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 1686 | */ 1687 | _meta?: { 1688 | [k: string]: unknown; 1689 | }; 1690 | [k: string]: unknown; 1691 | }; 1692 | [k: string]: unknown; 1693 | } 1694 | /** 1695 | * The server's response to a resources/read request from the client. 1696 | * 1697 | * This interface was referenced by `McpSchema`'s JSON-Schema 1698 | * via the `definition` "ReadResourceResult". 1699 | */ 1700 | export interface ReadResourceResult { 1701 | /** 1702 | * This result property is reserved by the protocol to allow clients and servers to attach additional metadata to their responses. 1703 | */ 1704 | _meta?: { 1705 | [k: string]: unknown; 1706 | }; 1707 | contents: (TextResourceContents | BlobResourceContents)[]; 1708 | [k: string]: unknown; 1709 | } 1710 | /** 1711 | * This interface was referenced by `McpSchema`'s JSON-Schema 1712 | * via the `definition` "Request". 1713 | */ 1714 | export interface Request { 1715 | method: string; 1716 | params?: { 1717 | _meta?: { 1718 | /** 1719 | * If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. 1720 | */ 1721 | progressToken?: string | number; 1722 | [k: string]: unknown; 1723 | }; 1724 | [k: string]: unknown; 1725 | }; 1726 | [k: string]: unknown; 1727 | } 1728 | /** 1729 | * The contents of a specific resource or sub-resource. 1730 | * 1731 | * This interface was referenced by `McpSchema`'s JSON-Schema 1732 | * via the `definition` "ResourceContents". 1733 | */ 1734 | export interface ResourceContents { 1735 | /** 1736 | * The MIME type of this resource, if known. 1737 | */ 1738 | mimeType?: string; 1739 | /** 1740 | * The URI of this resource. 1741 | */ 1742 | uri: string; 1743 | [k: string]: unknown; 1744 | } 1745 | /** 1746 | * An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. 1747 | * 1748 | * This interface was referenced by `McpSchema`'s JSON-Schema 1749 | * via the `definition` "ResourceListChangedNotification". 1750 | */ 1751 | export interface ResourceListChangedNotification { 1752 | method: "notifications/resources/list_changed"; 1753 | params?: { 1754 | /** 1755 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 1756 | */ 1757 | _meta?: { 1758 | [k: string]: unknown; 1759 | }; 1760 | [k: string]: unknown; 1761 | }; 1762 | [k: string]: unknown; 1763 | } 1764 | /** 1765 | * A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. 1766 | * 1767 | * This interface was referenced by `McpSchema`'s JSON-Schema 1768 | * via the `definition` "ResourceUpdatedNotification". 1769 | */ 1770 | export interface ResourceUpdatedNotification { 1771 | method: "notifications/resources/updated"; 1772 | params: { 1773 | /** 1774 | * The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. 1775 | */ 1776 | uri: string; 1777 | [k: string]: unknown; 1778 | }; 1779 | [k: string]: unknown; 1780 | } 1781 | /** 1782 | * An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. 1783 | * 1784 | * This interface was referenced by `McpSchema`'s JSON-Schema 1785 | * via the `definition` "ToolListChangedNotification". 1786 | */ 1787 | export interface ToolListChangedNotification { 1788 | method: "notifications/tools/list_changed"; 1789 | params?: { 1790 | /** 1791 | * This parameter name is reserved by MCP to allow clients and servers to attach additional metadata to their notifications. 1792 | */ 1793 | _meta?: { 1794 | [k: string]: unknown; 1795 | }; 1796 | [k: string]: unknown; 1797 | }; 1798 | [k: string]: unknown; 1799 | } 1800 | /** 1801 | * Additional properties describing a Tool to clients. 1802 | * 1803 | * NOTE: all properties in ToolAnnotations are **hints**. 1804 | * They are not guaranteed to provide a faithful description of 1805 | * tool behavior (including descriptive properties like `title`). 1806 | * 1807 | * Clients should never make tool use decisions based on ToolAnnotations 1808 | * received from untrusted servers. 1809 | * 1810 | * This interface was referenced by `McpSchema`'s JSON-Schema 1811 | * via the `definition` "ToolAnnotations". 1812 | */ 1813 | export interface ToolAnnotations1 { 1814 | /** 1815 | * If true, the tool may perform destructive updates to its environment. 1816 | * If false, the tool performs only additive updates. 1817 | * 1818 | * (This property is meaningful only when `readOnlyHint == false`) 1819 | * 1820 | * Default: true 1821 | */ 1822 | destructiveHint?: boolean; 1823 | /** 1824 | * If true, calling the tool repeatedly with the same arguments 1825 | * will have no additional effect on the its environment. 1826 | * 1827 | * (This property is meaningful only when `readOnlyHint == false`) 1828 | * 1829 | * Default: false 1830 | */ 1831 | idempotentHint?: boolean; 1832 | /** 1833 | * If true, this tool may interact with an "open world" of external 1834 | * entities. If false, the tool's domain of interaction is closed. 1835 | * For example, the world of a web search tool is open, whereas that 1836 | * of a memory tool is not. 1837 | * 1838 | * Default: true 1839 | */ 1840 | openWorldHint?: boolean; 1841 | /** 1842 | * If true, the tool does not modify its environment. 1843 | * 1844 | * Default: false 1845 | */ 1846 | readOnlyHint?: boolean; 1847 | /** 1848 | * A human-readable title for the tool. 1849 | */ 1850 | title?: string; 1851 | [k: string]: unknown; 1852 | } 1853 | --------------------------------------------------------------------------------