Cloudflare Agents SDK now supports the new MCP spec — no more Durable Object required!
Hi, it's me! I'm super excited about this one!
Cloudflare ChangelogToday there's a solid update to Cloudflare's Agents SDK, so let's dig into it right away!
What was announced?
According to the Cloudflare Changelog, Agents SDK v0.20.0 (released July 27, 2026) adds client and server support for the MCP (Model Context Protocol) 2026-07-28 release candidate. With this, Workers can serve tools, prompts, resources, and elicitation without an MCP transport session or a Durable Object. Agents can also connect to both MCP 2026-07-28 servers and existing legacy servers.
The story so far
Until now, running an MCP server on Workers usually meant using McpAgent and keeping session state in a Durable Object. The server-side createMcpHandler also took a v1 server directly, built around the assumption of persistent sessions. On the client side, you had to keep track of which protocol generation each server spoke.
What changes
The big change is that createMcpHandler now accepts a server factory function. Since it creates an isolated server instance for every request, you can run a stateless MCP server on Workers without provisioning a Durable Object at all!
On top of that, the agents/mcp/server entry point keeps McpAgent, WorkerTransport, MCP client transports, and SDK v1 modules out of stateless server bundles, so your bundle stays smaller.
And a single route — createMcpHandler(createServer)(request, env, ctx) — can serve both MCP 2026-07-28 clients and legacy clients making stateless requests. For ordinary tools, prompts, and resources, you don't need separate routes or tool definitions for each protocol generation.
Dive Deep
How the client side works
The MCP client manager now uses @modelcontextprotocol/client. For each connection, it probes for MCP 2026-07-28 support with server/discover, and falls back to the legacy initialize handshake on the same connection if the server doesn't support it. Existing addMcpServer calls don't need a protocol-version setting or separate clients per generation.
For stateless requests, elicitation uses input_required through multi-round-trip requests (MRTR). The legacy path keeps using the same form and URL handlers for pushed requests. The SDK collects the input, retries the original operation, and resolves the original callTool, getPrompt, or readResource promise with the final result.
OAuth callbacks now validate issuer metadata through the v2 SDK, too. Discovery state and issuer-bound credentials persist across browser redirects and Durable Object hibernation.
Writing a stateless server
Here's how simple it is to spin up a stateless server with a factory function.
import { McpServer } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
function createServer() {
return new McpServer({ name: "example", version: "1.0.0" });
}
export default {
fetch(request, env, ctx) {
return createMcpHandler(createServer)(request, env, ctx);
},
};
The Workers wrapper validates the present browser Origin, supports explicit delegation to trusted Origin middleware, and exposes request handling plus typed change notifications.
Migrating existing servers
If an existing McpAgent server still depends on sessionful features (protocol sessions, RPC, pushed server-to-client requests, standalone streams, or replay), you can run the stateless path side by side with the existing one. Use isLegacyRequest() to route only legacy traffic to the existing route.
import { isLegacyRequest } from "@modelcontextprotocol/server";
import { createMcpHandler } from "agents/mcp/server";
import { MyMcpAgent } from "./legacy-server";
import { createServer } from "./server";
const stateless = createMcpHandler(createServer, {
route: "/mcp",
legacy: "reject",
});
const legacy = MyMcpAgent.serve("/mcp");
export default {
async fetch(request, env, ctx) {
if (await isLegacyRequest(request)) {
return legacy.fetch(request, env, ctx);
}
return stateless(request, env, ctx);
},
};
Once you've migrated the remaining sessionful features and let existing sessions drain, you can remove the legacy route. Upgrading is just npm i agents@latest (or the yarn/pnpm/bun equivalent).
Deprecations in v0.20.0
Here's what's being deprecated in this release!
McpAgent— replaced by an SDK v2 factory withcreateMcpHandler. Feature-frozen, with no removal version announcedcreateMcpHandler(v1Server, options)— move the server to an SDK v2 factory and callcreateMcpHandler(factory, options)instead. Scheduled for removal in the next major versionMCPClientManager.callTool(params, resultSchema, options)and the equivalentwithX402Clientoverload — usecallTool(params, options)orcallTool(confirm, params, options)instead. A compatibility overload with no removal version announced
By the way, the MCP 2026-07-28 draft separately deprecates Roots, Sampling, Logging, the old HTTP+SSE transport, and Dynamic Client Registration.
Wrap-up
- Agents SDK v0.20.0 adds client and server support for the MCP 2026-07-28 release candidate
createMcpHandlernow accepts a factory function, enabling stateless MCP servers without a Durable Object- A single route can serve both MCP 2026-07-28 clients and legacy clients
McpAgentis feature-frozen and deprecated; useisLegacyRequest()to run old and new routes side by side while migrating sessionful features
If you're running MCP servers on Workers, or want to spin up a lightweight one, this is definitely worth a look!