Cloudflare Agents 0.20: migrate to MCP SDK v2 without breaking legacy clients
Quick answer
Cloudflare released Agents SDK v0.20.0 on July 27, 2026 with client and server support for the MCP 2026-07-28 release candidate. The preferred server path is now a stateless factory from @modelcontextprotocol/server, served through createMcpHandler from agents/mcp/server. McpAgent is deprecated and feature-frozen, but that does not mean every existing server should be switched off in one deploy.
Use this rule:
- If the endpoint only serves ordinary tools, prompts, and resources, migrate directly to the stateless factory.
- If it depends on protocol sessions, replay, standalone streams, pushed server-to-client requests, or session deletion, add the stateless route first and keep a temporary legacy lane.
- Remove the legacy lane only after clients have moved and existing sessions have drained.
The rest of this guide turns that rule into a package matrix, a rollout sequence, and eight acceptance tests.
Who this is for
This guide is for developers running an MCP server or client on Cloudflare Workers with the agents package. It is especially relevant if the code imports McpAgent, WorkerTransport, createMcpHandler, or the v1 @modelcontextprotocol/sdk.
It is not a general explanation of every MCP 2026-07-28 protocol change. For request semantics and conformance coverage, use the MCP stateless migration checklist. This page focuses on the Cloudflare implementation boundary: packages, imports, routing, Origin policy, OAuth, old sessions, and rollback.
What changed
Agents 0.20 splits the preferred server and client paths across the MCP SDK v2 packages. A stateless server imports McpServer from the exact version of @modelcontextprotocol/server required by the installed Agents release—currently 2.0.0-beta.5—and imports the handler from agents/mcp/server. The handler receives a factory, so each request gets an isolated server instance.
The default handler can also serve ordinary legacy clients through stateless compatibility. That fallback is not a complete v1 session transport: session-only GET and DELETE requests return 405, and replay, standalone streams, and pushed sampling, elicitation, or roots are unavailable.
Cloudflare also documents two important rollout boundaries:
- Stored HTTP session IDs created before 0.20 do not carry the negotiated protocol version. The upgraded client discards them and reconnects; in-flight work tied to an old remote session does not resume.
-
McpAgentremains available for migration, but it is deprecated and feature-frozen. Passing a v1 server to the overloadedcreateMcpHandleris also deprecated for removal in the next major Agents release.
Choose the migration lane
| Existing behavior | Target lane | Keep temporarily | Promotion condition |
|---|---|---|---|
| Tools, prompts, and resources without protocol-session state | Stateless | Nothing | v2 and ordinary legacy client tests pass |
McpAgent, replay, standalone stream, pushed requests, or session deletion |
Dual lane |
McpAgent or createLegacyMcpHandler
|
Stateful dependency replaced and old sessions drained |
| Custom gateway or browser client | Stateless plus explicit edge policy | Existing route until header and Origin tests pass | Proxy preserves MCP headers and rejects invalid Origins |
| SDK v1 integration that cannot yet create a v2 server | Temporary legacy | Isolated v1 route | Integration emits an SDK v2 server and passes the same canaries |
Do not preserve v1 merely because the current file imports it. Preserve it only for a behavior that the stateless lane cannot yet replace.
Change the package and entrypoint boundary
For a new stateless server, use the exact MCP package version required by Agents 0.20:
npm i agents@0.20.0 @modelcontextprotocol/server@2.0.0-beta.5 zod
Then move construction into a factory while keeping the Worker default export as an object:
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
function createServer() {
const server = new McpServer({
name: "example-server",
version: "1.0.0",
});
// Register tools, prompts, and resources here.
return server;
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
} satisfies ExportedHandler;
The small difference matters:
// SDK v1 shape: constructed server
createMcpHandler(createServer())
// SDK v2 shape: factory
createMcpHandler(createServer)
Do not default-export the callable handler. Cloudflare warns that Wrangler interprets a function default export as a WorkerEntrypoint class. Also avoid upgrading unrelated agent dependencies in the same change. The Agents SDK and AI SDK v7 checklist treats that package pair as a separate migration variable.
Run a six-stage rollout
1. Inventory sessionful behavior
Search for McpAgent, WorkerTransport, Mcp-Session-Id, standalone GET, session DELETE, replay storage, sampling, roots, and pushed elicitation. Label every dependency as required, replaceable, or unused.
2. Pin one reproducible package set
Pin the Agents and MCP SDK versions together while v2 remains beta. Keep the manifest and lockfile in one review. If the application imports agents/skills or agents/browser, install @cloudflare/codemode explicitly; it is now an optional peer rather than an MCP-only transitive dependency.
3. Deploy the stateless route first
Move ordinary tools, prompts, and resources to the factory. If sessionful behavior remains, route legacy traffic to a separate temporary lane with isLegacyRequest(). Do not make two independent tool definitions that can drift.
4. Verify the edge contract
Test the request through every real proxy or custom domain. Preserve MCP-Protocol-Version, Mcp-Method, Mcp-Name, and any declared Mcp-Param-* headers. Configure allowedHostnames and allowedOriginHostnames explicitly for custom domains. CORS is not authentication.
5. Recheck OAuth and recovery
Run a clean authorization, a reauthorization, and a restart or hibernation path. Verify that the application receives the expected identity and scopes without logging tokens. Expect pre-0.20 stored HTTP sessions to reconnect instead of resuming in-flight work.
6. Canary, drain, remove
Send a bounded client group to the stateless path. Compare tool completion, prompt and resource reads, authentication failures, invalid-Origin rejection, reconnects, and operator-visible errors. Keep rollback as a route switch plus the previous artifact. Remove the legacy lane only after session counts reach zero and no required sessionful behavior remains.
Eight acceptance tests
| Test | Expected result |
|---|---|
| v2 client calls one read-only tool | Final tool result succeeds |
| Ordinary legacy client calls the same tool | Succeeds through stateless compatibility |
Legacy session-only GET or DELETE hits stateless compatibility |
Fails explicitly with 405
|
| Valid custom-domain Origin and Host | Accepted |
| Malformed, opaque, or unapproved Origin | Rejected with 403
|
| OAuth clean login and reauthorization | Correct identity and scopes; no token in logs |
| Upgrade with a stored pre-0.20 HTTP session | Reconnects; does not claim old in-flight work resumed |
| Rollback drill | Previous route and artifact restore without changing tool authority |
Use a non-destructive tool for the canary. A protocol migration does not justify broader filesystem, network, deploy, or secret access.
Copyable rollout record
cloudflare_mcp_v2_rollout:
agents_version: "0.20.0"
mcp_server_version: "2.0.0-beta.5"
endpoint_class: "stateless | temporary-dual-lane"
sessionful_dependencies: []
stateless_route: "/mcp"
legacy_route: "none | documented temporary route"
edge_checks: ["MCP headers", "Host allowlist", "Origin allowlist"]
auth_checks: ["clean login", "reauthorization", "no token logs"]
canary_clients: "bounded cohort"
drain_signal: "zero active legacy sessions"
rollback: "restore prior route map and prior artifact"
promotion_gate: "all eight tests pass with recorded evidence"
This is an operating record, not a Cloudflare configuration schema.
Common mistakes
Replacing McpAgent without classifying its state. Deprecation is a direction, not evidence that replay or pushed requests are no longer needed.
Assuming legacy compatibility means a full v1 session. The default compatibility lane covers ordinary stateless requests, not GET streams, session deletion, replay, or pushed server requests.
Using broad wildcard Origins without another validator. Cloudflare allows delegating Origin checks only when trusted upstream middleware performs them. Keep Host, Origin, CORS, and authentication as separate controls.
Treating reconnect as resume. The upgraded client deliberately discards old session IDs that lack a protocol version. Report interrupted work honestly and retry only if the operation is safe to repeat.
FAQ
Must every existing McpAgent server migrate immediately?
No. It is deprecated and feature-frozen, so new development should target the stateless factory. Existing sessionful servers need a staged migration and may keep a temporary legacy lane while replacing required behavior.
Can one stateless route support old and new clients?
For ordinary tools, prompts, and resources, yes. createMcpHandler uses stateless legacy compatibility by default. It is not a replacement for complete v1 session behavior.
Should I install the latest MCP beta independently?
No. Cloudflare says to use the exact MCP SDK versions required by the installed Agents release while v2 is beta. Upgrade the package set together and rerun the acceptance matrix.
Top comments (0)