agents-0-20-0-18-b
Claude Code's read-only tools, web search on, ludion sync off
Question
How do I serve a stateless MCP server from a Worker with the Cloudflare agents package and the v2 MCP SDK?
Answer key
- A correct answer says
- `createMcpHandler` accepts a factory returning a v2 `McpServer`/`Server` from `@modelcontextprotocol/server` and serves stateless MCP with legacy compatibility; `createLegacyMcpHandler` remains for SDK v1 servers.
- The stale answer
- `createMcpHandler` takes an `McpServer` from `@modelcontextprotocol/sdk` (v1).
Answer
Install `agents`, `@modelcontextprotocol/server` (pinned to the exact version your `agents` release expects) and `zod`. Then pass a **factory function** that builds a new v2 `McpServer` to `createMcpHandler` from `agents/mcp/server`, and call the handler from your Worker's `fetch`. Pass the function itself, not a server you've already created: the handler calls it on every request so each request gets its own server. The handler is stateless by default and still accepts older clients.
```ts
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "echo-server", version: "1.0.0" });
server.registerTool("echo",
{ description: "Echo back a message", inputSchema: { message: z.string() } },
async ({ message }) => ({ content: [{ type: "text", text: message }] }));
return server;
}
export default {
fetch: (req, env, ctx) => createMcpHandler(createServer)(req, env, ctx),
} satisfies ExportedHandler;
```
This needs `agents` v0.20.0 or later, which is where v2 support was added. The v0.20.0 release notes pin `@modelcontextprotocol/server@2.0.0-beta.5`, but the docs say `2.0.0`, so check which version your installed release asks for. `McpAgent` is deprecated and frozen. `createLegacyMcpHandler` is only meant as a temporary bridge for old SDK v1 endpoints. I based the example on the docs' description rather than copying the official snippet word for word.
Sources:
- [MCP handler API](https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/)
- [Changelog: Agents SDK v0.20.0 / MCP SDK v2](https://developers.cloudflare.com/changelog/post/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2/)
- [Migrate to MCP SDK v2](https://developers.cloudflare.com/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/)
- [agents@0.20.0 release notes](https://newreleases.io/project/github/cloudflare/agents/release/agents@0.20.0)Grade
correct
The judge's reason: It says createMcpHandler takes a factory returning a v2 McpServer from @modelcontextprotocol/server, serves stateless MCP that still accepts older clients, and keeps createLegacyMcpHandler for v1, without presenting the stale v1 usage as current.
Session log
Tool call: ToolSearch
{ "query": "select:WebSearch,WebFetch", "max_results": 2 }Tool output
{"type":"tool_reference","tool_name":"WebSearch"} {"type":"tool_reference","tool_name":"WebFetch"}Tool call: WebSearch
{ "query": "Cloudflare agents createMcpHandler stateless MCP SDK v2 @modelcontextprotocol/server", "mode": "standard" }Tool output
Web search results for query: "Cloudflare agents createMcpHandler stateless MCP SDK v2 @modelcontextprotocol/server" Links: [{"title":"github cloudflare/agents agents@0.20.0","url":"https://newreleases.io/project/github/cloudflare/agents/release/agents@0.20.0"},{"title":"github cloudflare/agents @cloudflare/codemode@0.5.1","url":"https://newreleases.io/project/github/cloudflare/agents/release/@cloudflare%2Fcodemode@0.5.1"},{"title":"handler api","url":"https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/"},{"title":"glama.ai","url":"https://glama.ai/mcp/servers/xfewc9yuwq"},{"title":"2026 07 27 agents sdk v0.20.0 mcp sdk v2","url":"https://developers.cloudflare.com/changelog/post/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2/"},{"title":"migrate to mcp sdk v2","url":"https://developers.cloudflare.com/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/"},{"title":"developers.cloudflare.com","url":"https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/index.md"},{"title":"ec570ea1.previews.developers.cloudflare.com","url":"https://ec570ea1.previews.developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/index.md"},{"title":"6e8c1c3a.previews.developers.cloudflare.com","url":"https://6e8c1c3a.previews.developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/index.md"}] Search results show that Cloudflare's Agents SDK added MCP SDK v2 support to `createMcpHandler` in v0.20.0, and the setup is now stateless. - **Release:** The v0.20.0 notes describe the change as adding MCP SDK v2 support to the handler, with the server package pinned as a peer dependency. Add MCP SDK v2 support to createMcpHandler. Pass a factory returning McpServer or Server from the exact-pinned @modelcontextprotocol/server@2.0.0-beta.5 peer dependency - **Statelessness:** Cloudflare's docs list it as stateless with legacy compatibility by default, and the handler is imported from `agents/mcp/server`. - **Factory pattern:** The factory creates a new server per request, so each request is isolated. The changelog says the factory creates an isolated server for each request. - **Install:** The docs give npm i agents @modelcontextprotocol/server@2.0.0 zod for a stateless server, and a separate `@modelcontextprotocol/sdk@1.30.0` install for the explicit legacy path. - **Legacy path:** `createLegacyMcpHandler` is kept for SDK v1 endpoints that don't use `McpAgent`, and the migration guide says to remove it once clients migrate. For an SDK v1 endpoint that does not use McpAgent, use createLegacyMcpHandler only on that temporary legacy branch. Remove it after clients migrate and existing sessions drain. - **Status:** McpAgent is deprecated and feature-frozen. Caveat: the v0.20.0 release and the SDK v2 packages are listed as beta (`2.0.0-beta.5`) in the release notes, while the docs reference `2.0.0`. Check the exact versions your installed Agents release requires. REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.Tool call: WebFetch
{ "url": "https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/index.md", "prompt": "Show the minimal stateless example code using createMcpHandler with MCP SDK v2 (imports, factory, export default fetch), verbatim." }Tool output
I can't reproduce the page's example verbatim, because that would exceed the 125-character limit on quoted material. Here's an equivalent minimal stateless server written from the page's description: ```ts import { McpServer } from "@modelcontextprotocol/server"; import { createMcpHandler } from "agents/mcp/server"; import { z } from "zod"; // Pass a factory, not a server instance: "Pass the factory itself." function createServer() { const server = new McpServer({ name: "echo-server", version: "1.0.0" }); server.registerTool( "echo", { description: "Echo back a message", inputSchema: { message: z.string() }, }, async ({ message }) => ({ content: [{ type: "text", text: message }], }), ); return server; } export default { fetch(request, env, ctx) { return createMcpHandler(createServer)(request, env, ctx); }, } satisfies ExportedHandler; ``` The example uses the SDK v2 `McpServer` from `@modelcontextprotocol/server`, `createMcpHandler` from `agents/mcp/server`, and `zod` for the input schema. Install them with `@modelcontextprotocol/server@2.0.0` pinned, as the page recommends. For the exact original snippet, see the MCP handler APIs page on developers.cloudflare.com.Agent
Install `agents`, `@modelcontextprotocol/server` (pinned to the exact version your `agents` release expects) and `zod`. Then pass a **factory function** that builds a new v2 `McpServer` to `createMcpHandler` from `agents/mcp/server`, and call the handler from your Worker's `fetch`. Pass the function itself, not a server you've already created: the handler calls it on every request so each request gets its own server. The handler is stateless by default and still accepts older clients. ```ts import { McpServer } from "@modelcontextprotocol/server"; import { createMcpHandler } from "agents/mcp/server"; import { z } from "zod"; function createServer() { const server = new McpServer({ name: "echo-server", version: "1.0.0" }); server.registerTool("echo", { description: "Echo back a message", inputSchema: { message: z.string() } }, async ({ message }) => ({ content: [{ type: "text", text: message }] })); return server; } export default { fetch: (req, env, ctx) => createMcpHandler(createServer)(req, env, ctx), } satisfies ExportedHandler; ``` This needs `agents` v0.20.0 or later, which is where v2 support was added. The v0.20.0 release notes pin `@modelcontextprotocol/server@2.0.0-beta.5`, but the docs say `2.0.0`, so check which version your installed release asks for. `McpAgent` is deprecated and frozen. `createLegacyMcpHandler` is only meant as a temporary bridge for old SDK v1 endpoints. I based the example on the docs' description rather than copying the official snippet word for word. Sources: - [MCP handler API](https://developers.cloudflare.com/agents/model-context-protocol/apis/handler-api/) - [Changelog: Agents SDK v0.20.0 / MCP SDK v2](https://developers.cloudflare.com/changelog/post/2026-07-27-agents-sdk-v0.20.0-mcp-sdk-v2/) - [Migrate to MCP SDK v2](https://developers.cloudflare.com/agents/model-context-protocol/guides/migrate-to-mcp-sdk-v2/) - [agents@0.20.0 release notes](https://newreleases.io/project/github/cloudflare/agents/release/agents@0.20.0)