Repository navigation
feat(mcp): generate an MCP server in TypeScript (--target mcp) - #71
Open
mattzcarey wants to merge 9 commits into
Open
mattzcarey wants to merge 9 commits into
mattzcarey wants to merge 9 commits into
Conversation
`forge openapi.json --target mcp --out server` generates TypeScript for the official MCP TypeScript SDK (@modelcontextprotocol/server): - <group>/tools.ts: the tools of the group's methods, each with the JSON Schema of its arguments, its hints, the method's extensions, and a request function that builds its API request - <group>/schemas.ts: a constant for each model's schema, shared by every tool whose arguments use it - index.ts: every tool, and the API's base URL and version - README.md: the methods without a tool, and why - _/: the runtime, which serves the tools with low-level tools/list and tools/call handlers and sends their requests Tools are named after their groups and method in snake_case. Hidden methods get tools, since a client can only call the tools it lists; deprecated ones don't. Methods whose required body isn't JSON, or whose tool name is taken or too long, are listed in README.md instead.
The generated files import the tool type and pathParam from _/tool.ts, and index.ts re-exports only that half of the runtime, so a server can import the tools, for example lazily or as a module of its own, without pulling in @modelcontextprotocol/server. _/server.ts still serves them.
Each tool gets responses: the JSON Schema of every response body the method declares, by status (2XX, error ranges and default), with a $defs of the models they reach, from shared outputs.ts constants. Response schemas keep read-only fields and leave out descriptions and defaults: they're for a server to check responses against, so listTools leaves them out of tools/list.
Each tool gets an outputSchema: the JSON body schema of its successful
(2xx) responses, or anyOf the distinct ones, with $defs holding only the
models those reach. A tool with a 2xx response that has no JSON body gets
none. listTools leaves it out unless called with { outputSchema: true }.
rfcs/mcp.md says which methods get a tool, how tools and arguments are named, how a call becomes a request, how schemas are built for arguments, responses and output, the hints and extensions, and what the runtime does.
Only createHandler serves the tools on an McpServer, so serveTools is no longer exported, and its extra tools, which nothing passed, are gone.
worker.ts default-exports createHandler with every tool. It calls the API with the MCP request's Authorization header and refuses requests from another site's Origin, so the generated server deploys as a Worker with no code of its own.
Every server around the tools differs in how it authenticates and what it
serves alongside them, so the runtime keeps only what every server needs:
the Tool type, listTools, toolsByName and buildRequest, in _/tool.ts.
worker.ts, createHandler, serveTools and callTool are gone, and nothing
imports the MCP SDK at runtime.
buildRequest returns the request as plain data, { method, url, headers,
body? }, which crosses RPC as it is and fetch(request.url, request)
sends.
commit: |
…ession CodeQL flagged /\/+$/ as polynomial on many slashes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
forge openapi.json --target mcp --out toolsgenerates the tools of an MCP server in TypeScript: one tool per API method, as data a server lists and calls. Each tool has its argument schema, hints, extensions and the JSON Schemas of its responses, plus arequestfunction that builds its HTTP request.rfcs/mcp.mddescribes how the target maps the API onto MCP.It generates no server. Every server around the tools differs: how it authenticates, what it serves alongside them, what it does with an API error. So the runtime is one file,
_/tool.ts, with only what every server needs:listTools: the listingtools/listsends, built once, with output schemas only when askedtoolsByName: the index fortools/callbuildRequest: the request as plain data,{ method, url, headers, body? }, which crosses RPC as it is andfetchsendsWhen I closed #53 the plan was to run this in production before proposing it. It now serves
mcp.cloudflare.com/mcp?codemode=false&experimentalForge=true, with about 3,000 tools regenerated every six hours. Our server applies its own policy to the tools, plans calls in a separate Worker and sends them with the session's token. It checks responses against the generated response schemas so teams can fix the spec before tools advertise output schemas (cloudflare/mcp#350 moves it to this version).One question for review: methods with
x-forge-hiddenget a tool. Forge says hidden methods are left out of listings, sotools/listshould probably skip them too.Tests ran, and cloudflare/mcp's suite passes on code generated by this branch.