Skip to content

feat(forge-mcp): generate MCP tools with a forge plugin and serve them over Streamable HTTP - #53

Closed
mattzcarey wants to merge 2 commits into
cloudflare:mainfrom
mattzcarey:feat/forge-mcp
Closed

mattzcarey wants to merge 2 commits into
cloudflare:mainfrom
mattzcarey:feat/forge-mcp

Conversation

@mattzcarey

Copy link
Copy Markdown
Member

Adds @cloudflare/forge-mcp: a TypeScript forge plugin that turns an OpenAPI document into an MCP tool catalogue (mcp-tools.json), and a runtime on the MCP TypeScript SDK that serves a catalogue over Streamable HTTP. The README spells out how methods become tools, as asked for on #49.

This is the TypeScript, HTTP-only counterpart to #49. A Worker on the TS SDK can serve the generated tools with its own auth in front, and the catalogue is plain data, so the same file could back other runtimes.

flowchart LR
  doc[openapi.forge.json] -->|"forge --target wasm://forge-mcp.wasm"| cat[mcp-tools.json]
  cat --> rt["createForgeMcpHandler<br/>tools/list · tools/call"]
  rt -->|one request per call| api[API]
Loading
  • Plugin (src/tools.ts, src/schema.ts): one tool per non-deprecated method. Hidden methods are included, because a client can only call what tools/list returns. Each tool has the group path plus method name in snake case, a self-contained JSON Schema 2020-12 input with only the $defs it reaches, and hints from the HTTP method. Methods with a required non-JSON body are skipped and listed in skipped with the reason. catalogueFile(forge, { include, name, transform }) is the hook for writing your own plugin.
  • Runtime (src/server.ts, src/request.ts): createForgeMcpHandler({ catalogue, authorize }) returns a fetch. tools/list is built once per catalogue, and tools/call substitutes path parameters, serializes the query in form style and sends body as JSON. A non-2xx response is an isError result. serveCatalogue puts the same tools on an existing server.
  • Example (example/): a Worker serving the Cloudflare API. pnpm example:generate runs the plugin on the latest openapi.forge.json.
const mcp = createForgeMcpHandler({
  catalogue,
  authorize: (request) => {
    const authorization = request.headers.get('Authorization');
    return authorization ? { Authorization: authorization } : new Response('Unauthorized', { status: 401 });
  },
});
export default { fetch: (request: Request) => mcp.fetch(request) };

On the latest Cloudflare release, the plugin writes 3,053 tools in about 1.5 s; 27 methods are skipped for non-JSON bodies. The example Worker is 671 KB gzipped. I connected the MCP TS client to it under wrangler dev: tools/list returned all 3,053 tools in 112 ms, and accounts_list and zones_list succeeded against the live API with an OAuth token. Unit tests ran (pnpm --filter @cloudflare/forge-mcp test).

…m over Streamable HTTP

@cloudflare/forge-mcp has two halves. A TypeScript forge plugin turns the
document into a tool catalogue, mcp-tools.json: one tool per method, with a
self-contained JSON Schema input, annotations from the HTTP method, and the
route its arguments take. A runtime on the MCP TypeScript SDK serves a
catalogue with low-level tools/list and tools/call handlers.

The README documents how methods become tools. example/ serves the Cloudflare
API's catalogue (3,053 tools from the latest openapi.forge.json) from a Worker.
A tool's extensions carry a plugin's own data, such as the permissions a
method needs, and are never sent to clients. catalogueTools(catalogue)
gives a server the tools/list payload and name lookup when it lists other
tools too. The package now builds to dist/ (JavaScript and declarations),
which its exports point to, so it works from node_modules.
@mattzcarey

Copy link
Copy Markdown
Member Author

Closing for now. We'll keep working on this on a branch and try it in production behind ?forge=true on cloudflare/mcp before proposing anything here.

@mattzcarey mattzcarey closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant