@clementvial

Developer from Canada 🇨🇦
Product, infrastructure, AI, and web3.
Mostly on AWS and Cloudflare.

All notes

MCP Went Stateless, and the Durable Object Went With It

In February I wrote that a remote MCP server on Workers needs somewhere to put the session, and that somewhere was a Durable Object. The 2026-07-28 spec removed the session.

There’s no initialize handshake and no Mcp-Session-Id. Every request carries its own protocol version, client identity, and capabilities in _meta, so it can land on any instance behind a plain round-robin load balancer. A client that wants to inspect the server before calling anything can use server/discover, but nothing requires it.

So the Durable Object was holding routing state for a protocol that no longer has any:

wrangler.jsonc
{
"durable_objects": {
"bindings": [{ "name": "MCP_OBJECT", "class_name": "MyMCP" }]
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyMCP"] }]
}

McpAgent goes with it. What’s left is a fetch handler:

src/index.ts
import { createMcpHandler } from 'agents/mcp/server';
export default {
fetch(request: Request, env: Env, ctx: ExecutionContext) {
return createMcpHandler(createServer)(request, env, ctx);
},
};

The same /mcp route still answers stateless 2025 Streamable HTTP clients, so the server can move before every editor does.

Tools get called twice now

The part that isn’t a deletion is elicitation. A server that needed input mid-call used to push an elicitation/create down an open stream. It now returns a result with resultType: "input_required", the client collects the answer, and the client retries the original call with inputResponses attached.

Retries the original call. If a tool writes something before it asks for confirmation, it writes it twice. Stream resumability was removed in the same release, so a dropped response also ends in the client re-issuing the request. Both paths arrive at the same requirement: side-effecting tools have to be idempotent, and nothing in the protocol does that for you.

What still deserves a Durable Object

Anything the application itself needs to coordinate. Mine needed none of that. The object existed so two HTTP requests could find each other, which was transport bookkeeping I paid for on every deploy. That’s the piece that’s gone, and it was never my state to begin with.