DEV Community

Ahab
Ahab

Posted on • Originally published at indieseek.co

Cloudflare Agents 0.20 MCP SDK v2 Migration

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:

  1. 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.
  2. McpAgent remains available for migration, but it is deprecated and feature-frozen. Passing a v1 server to the overloaded createMcpHandler is 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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

The small difference matters:

// SDK v1 shape: constructed server
createMcpHandler(createServer())

// SDK v2 shape: factory
createMcpHandler(createServer)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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.

Sources

Top comments (0)