MCP Went Stateless: Migrating Servers to the 2026-07-28 Specification
💡 Tool Tip:JSON Formatter, API Tester, JWT Decoder
The 2026-07-28 Model Context Protocol specification shipped on July 28, 2026 and makes MCP stateless at the protocol layer. The initialize handshake and session header are gone, elicitation becomes a multi round-trip request flow with requestState, routing moves to Mcp-Method headers, list results become cacheable, and Tier 1 SDKs were updated. Here is the migration path, with before-and-after code.
Stateless at the protocol layer
1. The Headline: a Stateless Protocol Core
MCP began life as a bidirectional, stateful protocol: a client initialized a session, negotiated capabilities, and kept that session alive across calls. The 2026-07-28 specification turns it into a request/response protocol. The maintainers framed this as the most highly-requested change from developers who wanted better reliability and scalability, and they chose a clean break on purpose so that future revisions can evolve without rewriting transport or lifecycle code. Release notes also introduce a formal deprecation policy and an extensions framework. The practical effect is that horizontal scaling no longer requires sticky routing and a shared session store, both of which were among the most common sources of subtle production bugs in MCP deployments.
// BEFORE 2026-07-28: a stateful initialize handshake
const session = await client.initialize({
protocolVersion: "2025-06-18",
clientInfo: { name: "my-agent", version: "1.0.0" },
capabilities: { elicitation: {} },
});
// client and server capabilities live on `session`
const tools = await session.request("tools/list", {});2. The Handshake Is Gone
The initialize handshake is removed. Client capabilities are declared through _meta and headers on each request, and server capabilities are retrieved through a server/discover call rather than negotiated once at connection time. This is the single biggest code change for server authors, and it is mechanical: you stop storing negotiated state on a session object and start reading protocol version and client info from per-request metadata. Code sample 1 shows the old shape and code sample 2 the new one. The payoff is that two requests from the same client may land on different instances and nothing breaks, because there is no session left to lose. If your server kept anything in memory between calls, that memory now belongs in the request, in a cache keyed by the request, or in an external store.
// AFTER 2026-07-28: no handshake, per-request metadata
const meta = {
"protocol-version": "2026-07-28",
"client-info": { name: "my-agent", version: "1.0.0" },
"client-capabilities": { elicitation: {} },
};
const server = await client.request("server/discover", {}, { meta });3. Elicitation Becomes Multi Round-Trip
Interactive prompts used to assume a live session. The new specification replaces elicitation with a multi round-trip request flow. Instead of holding a session open, the server returns an InputRequiredResult together with a unique requestState identifier. After the client collects input from the user, it sends a new request carrying the original request's id and the requestState it received, and the server uses that state to reconstruct what it was doing. Code sample 3 sketches the exchange. It is a little more ceremony than a live prompt and dramatically more robust: the interaction survives a server restart, a rolling deploy, or a load balancer sending the follow-up to a different instance, none of which a stateful prompt could survive.
// Elicitation is now a multi round-trip request, not a live session.
const first = await client.request("tools/call", {
name: "book_flight",
arguments: { from: "SFO" },
});
if (first.InputRequiredResult) {
const { requestState, prompt } = first.InputRequiredResult;
const answer = await askUser(prompt);
const done = await client.request("tools/call", {
name: "book_flight",
arguments: { from: "SFO", returnDate: answer },
requestState, // server reconstructs its own state
requestId: first.id,
});
}4. Header Routing and Cacheable Lists
Two features quietly change how you deploy. First, requests carry an Mcp-Method header, with name-based routing available through Mcp-Name, so a gateway can route traffic by method without parsing the body; your server should honor and validate those headers. Second, list results are now cacheable: emit ttlMs and cacheScope on tools/list and resource reads so clients and gateways can cache them. Code sample 4 shows the shape. Together they let a fleet of identical stateless instances sit behind a normal load balancer and serve most discovery traffic from cache. That is the operational difference this release was really about: MCP servers are now shaped like the rest of your web infrastructure instead of like a special long-lived process that only one engineer understands.
// Emit ttlMs and cacheScope so gateways and clients can cache discovery.
return {
tools: TOOLS,
ttlMs: 60_000,
cacheScope: "public",
};5. Migrating Your Server
The upgrade is four steps. Update to an SDK build that speaks 2026-07-28; the Tier 1 SDKs for TypeScript, Python, Go, and C# were updated alongside the specification. Delete handshake and session handling, and read protocol version and client info from _meta per request. Emit ttlMs and cacheScope on list operations, and validate Mcp-Method and Mcp-Name. Then replace any in-memory session state with either request-scoped data or an external cache. Code sample 5 shows an entire server compressed to a stateless handler. The breaking changes are real: treat early guidance as get-ready rather than rip-everything-out-today, run both protocol versions during a transition window, and lean on the new deprecation policy to plan the rest. Authorization hardening also landed, so revisit your OAuth flows while you are in there.
// A whole MCP server in a Worker: no session store, no sticky routing.
export default {
async fetch(req, env) {
const method = req.headers.get("Mcp-Method");
const name = req.headers.get("Mcp-Name");
const meta = JSON.parse(req.headers.get("Mcp-Meta") || "{}");
const body = await req.json();
const handler = ROUTES[method];
if (!handler) return new Response("unknown method", { status: 404 });
return Response.json(await handler({ name, meta, body, env }));
},
};6. Why This Matters Beyond MCP
Statelessness is a maturity signal. A protocol that requires sticky sessions is a protocol you cannot run at the edge, cannot scale cheaply, and cannot reason about during an incident. MCP spent about eighteen months becoming the de facto standard for AI-to-tool integration, and this release is what a standard does when it grows up: it removes the clever parts that do not scale. The same instinct applies to your own agent infrastructure. If a component in your stack must remember something between calls, ask whether it really should. After July 28, 2026 the answer is more often no. Server-rendered interfaces and long-running Tasks also arrived as formal extensions, which means the interesting surface for MCP servers is now user interfaces and durable work, not connection management.
Scale behind a load balancer
The handshake is gone
📌 Frequently Asked Questions
What is the 2026-07-28 MCP specification?
The largest revision since launch. It makes MCP stateless at the protocol layer, removes the initialize handshake, adds multi round-trip requests, header-based routing, cacheable list results, authorization hardening, a formal extensions framework, and a deprecation policy.
Is it a breaking change?
Yes. The specification shipped on July 28, 2026 and contains breaking changes, so treat early write-ups as get-ready guidance rather than a mandate to rip everything out today.
What replaces elicitation?
A multi round-trip request flow. The server returns an InputRequiredResult with a unique requestState identifier, and the client sends a follow-up request carrying the original request id and that state so the server can reconstruct its work.
How do clients declare capabilities now?
Through _meta and headers on each request, rather than a one-time handshake. Server capabilities are retrieved through a server/discover call.
Which SDKs were updated?
The Tier 1 SDKs for TypeScript, Python, Go, and C# shipped alongside the specification, and MCP servers can now run in a single Cloudflare Worker with no stateful infrastructure.