The Model Context Protocol just deleted its own handshake. The 2026-07-28 release candidate, locked on May 21 and finalizing July 28, removes theinitialize/initializedexchange and theMcp-Session-Idheader entirely. If your MCP server keeps aMap<sessionId, State>anywhere, it breaks under the new spec.
This is the largest revision since MCP launched in November 2024, and it's a philosophical reversal: the protocol that asked every client to open a stateful session now requires that any request can hit any replica. This guide covers the stateless MCP server migration end to end: what's removed, where your state goes, and the checklist to run before July.
TL;DR
- The MCP 2026-07-28 release candidate makes the protocol stateless at the core. No handshake, no session ID, mandatory
Mcp-Method/Mcp-Nameheaders for content-agnostic load balancing. - Your per-session state moves to one of three places: the request body, a Task handle, or an external store.
- Authorization aligns with OAuth 2.1 across six Final SEPs. Delete your bespoke auth code and use the SDK helpers.
roots,sampling, andloggingare deprecated with a 12-month removal clock.
What does the MCP 2026-07-28 release candidate actually change?
Stateless MCP means every request must be independently serviceable: the server may not consult any memory keyed on a session to recall a client's protocol version, capabilities, or prior calls. SEP-2575 removes the handshake, and SEP-2567 removesMcp-Session-Id, replacing implicit sessions with explicit state handles.
The maintainers, David Soria Parra and Den Delimarsky, frame it plainly in the announcement:
"The headline change is that MCP is now stateless at the protocol layer. Six Specification Enhancement Proposals (SEPs) work together to get there, completing the plan we laid out in The Future of MCP Transports in December."
Here's the removal map every migration starts from:
| Removed in the RC | Replaced by |
|---|---|
initialize/initializedmethods |
First request carriesprotocolVersion,clientInfo,capabilitiesin_meta |
Mcp-Session-Idheader |
Explicit state handles the server issues for long-running work |
| Handshake capability negotiation | server/discovermethod, callable anytime |
| Sticky-session load balancing | MandatoryMcp-Method+Mcp-Nameheaders; any replica serves any call |
The why is operational. Production servers behind gateways and CDNs had to pin every client to one pod because the session ID was opaque to the load balancer. One pod dies, all its in-flight tool calls die with it. Independent analysis like PipeLab's runtime security post documents this pain in detail.
Where does your session state go now?
Every value in your session map has exactly one correct destination, and it depends on the state's lifetime. Audit the map first; the rest of the migration is mechanical.
| State lifetime | Old home | New home |
|---|---|---|
| One call | Server memory | The request body (_meta+ params) |
| One long-running invocation | Memory + open SSE stream | A Task handle (SEP-2663) |
| Across many calls | Memory keyed onMcp-Session-Id |
External store (Redis, Postgres) keyed on a client-supplied correlation ID |
| Auth context | Per-session token | OAuth 2.1 access token, validated on every request |
The load-bearing pattern is request-body-as-state. If a piece of state can travel in the request, it should. The mcpmigrate.dev guide puts the consequence well: "Anything keyed on the session loses its anchor."
The idempotency contract
Statelessness means retries, and retries mean idempotency. Under SEP-2567, any mutating call SHOULD carry anIdempotency-Keyheader. The server SHOULD cache the response per key and return it on replay, and MUST discard a cached result it can't serve back identically.
Two safety properties matter: replay-safety (cached response is byte-equal to the original) and key uniqueness (same key with a different request body is a 409 Conflict, never a silent overwrite). Hash the canonicalized JSON body with SHA-256 and store it next to the cached response.
A 24-hour TTL is a sane default outside financial flows.
How to build an MCP server the stateless way
The before-shape is familiar: a session map, aninitializehandler, and a transport that mints session IDs.
// BEFORE: pre-RC, session-keyed
const sessions = new Map<string, SessionState>();
server.setRequestHandler("tools/call", async (req, extra) => {
const state = sessions.get(extra.sessionId!); // breaks under the RC
if (!state) throw new Error("session not initialized");
// ...
});
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => crypto.randomUUID(), // the anchor to remove
});
The after-shape has noinitializehandler and no session map. Idempotency lives in Redis; long-running work returns a Task handle:
// AFTER: 2026-07-28 RC shape, typescript-sdk v1.26.x
server.setRequestHandler("tools/call", async (req, extra) => {
if (req.params.name === "tickets.create") {
const idemKey = extra.requestInfo.headers["idempotency-key"];
if (!idemKey) throw new Error("Idempotency-Key required for mutating calls");
const requestHash = sha256(JSON.stringify(req.params.arguments));
const cached = await redis.hgetall(`idem:${idemKey}`);
if (cached.request_hash && cached.request_hash !== requestHash) {
throw new Error("Idempotency-Key reused for a different request body");
}
if (cached.response) return JSON.parse(cached.response);
// execute, cache with 24h TTL, return
}
if (req.params.name === "tickets.escalate") {
const taskId = `task_${crypto.randomUUID()}`;
await redis.hset(`task:${taskId}`, { status: "working" });
return { task: { taskId, status: "working" } }; // polymorphic result, SEP-2663
}
});
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // the official stateless signal in v1.26.x
});
One version caveat: as of the spec lock there's no tagged2026-07-28SDK release. The TypeScript SDK's v1.26.x line supports the stateless transport option today; v2 onmainis pre-alpha and its helper signatures may shift.
MCP OAuth 2.1: the part you get to delete
Six Final SEPs align MCP authorization with OAuth 2.1, and the practical instruction is to replace your custom auth code with the official SDK helpers. PKCE is required for all authorization code flows, redirect URIs must match exactly, the implicit and ROPC grants are gone, bearer tokens can't ride in query strings, and refresh tokens must be sender-constrained or one-time-use.
This is the single biggest reliability win in the RC for anyone who has integrated against more than one MCP server. Pre-RC, every server picked its own auth scheme. Now compliant servers share one flow shape. Descope's walkthrough of the MCP authorization spec and the mcp.directory OAuth 2.1 explainer cover the wiring.
Tasks, Server Cards, and the deprecation list
The MCP Tasks extension (SEP-2663) makestools/callresults polymorphic: immediate result or Task handle, with lifecycle statesworking,input_required,completed,failed, andcancelled. Clients poll viatasks/get, send mid-run input viatasks/update, and abort viatasks/cancel. Keep theTaskId → statemapping in an external store and any replica can serve the polling.
MCP Server Cards are not in the RC as a final feature. At lock, they exist as a Working Group charter describing a.well-knownmetadata document for discovery. Track it; don't depend on it.
And SEP-2577 deprecatesroots,sampling, andloggingwith a 12-month minimum removal window. Logging moves to stderr or OpenTelemetry's published MCP semantic conventions. Sampling's job moves to the Tasksinput_requiredstate. Roots gets no replacement; validate paths in tool input. The nullpointer.se critique argues sampling deserved a fix rather than deletion, and it's a fair read of the trade-off.
The migration checklist
- Audit your session map. Assign every value to request body, Task handle, or external store.
- Strip all
Mcp-Session-Idreads and writes. Any handler touchingextra.sessionIdis a target. - Adopt the SDK's OAuth 2.1 helpers and delete bespoke auth.
- Switch to the stateless transport option (
sessionIdGenerator: undefinedon v1.26.x). - Implement
Idempotency-Keyvalidation with body hashing and a TTL. - Wire the Tasks extension with state in your external store.
- Test idempotency: ten identical replays, one side effect, byte-equal responses; a changed body on the same key returns 409.
- Test scale-out: two replicas behind plain round-robin, a thousand mixed calls, responses byte-equal to a single-replica run.
- Check your SDK floor against CVE-2026-25536, a pre-RC Python SDK regression that desynchronizes Streamable HTTP responses under load. RC-pinned SDKs are patched; old pins aren't.
- Run conformance with the official MCP Inspector, which shipped day-one RC support.
Why migrate now instead of waiting
The honest context is two-sided. Anthropic, OpenAI, and Google all run first-party agent products on MCP, with Anthropic's code execution with MCP work reporting a vendor-stated 98.7% token reduction in its tests.
Meanwhile Perplexity's CTO Denis Yarats announced a move away from MCP toward APIs and CLIs at the Ask 2026 conference in March, and Y Combinator's Garry Tan posted "MCP sucks honestly," a critique grounded in his gstack project's measurements of MCP round-trip overhead. Both quotes are attributed via secondary sources; no first-party transcript or original post was located.
The RC answers the auth complaint decisively and the deployment-scale complaint structurally. It narrows the context-window complaint without closing it. And it shifts real complexity onto server authors: you now own the idempotency cache, the external store, and the Tasks lifecycle.
A single-replica hobby server pays that tax for little benefit. A fleet behind a CDN gets the only architecture that works at scale.
Early signal favors moving. In the first community testing week, Alpic's Frédéric Barthelet reported a one-third reduction in SSE-related infrastructure cost after switching a server to the RC's stateless transport. Notably, the RC announcement drew no front-page Hacker News thread in that window; adoption is running ahead of discussion.
What this means for you
Start the safe steps today: the session-map audit, theMcp-Session-Idstrip, and the OAuth 2.1 helper swap are all forward-compatible. Hold the transport switch and Tasks wiring until you can pin an RC-tagged SDK, and re-verify v2 helper signatures when they ship. Budget real staging time for the idempotency and scale-out tests; they're where stateless migrations actually fail.
The spec finalizes July 28. Servers that still read a session header after that date are running on a branch the three largest model vendors aren't investing in.
Sources
- The 2026-07-28 MCP Specification Release Candidate, canonical announcement by the MCP maintainers
- SEP-2575: Make MCP Stateless, removes the handshake and session concept
- SEP-2567: Sessionless MCP via Explicit State Handles, removes
Mcp-Session-Id, defines idempotency guidance - SEP-2663: Tasks Extension, polymorphic results and long-running work
- SEP-2577: Deprecate Roots, Sampling, and Logging, the 12-month deprecation policy in action
- Server Card Working Group Charter, discovery metadata, pre-final
- Diving Into the MCP Authorization Specification (Descope), OAuth 2.1 flow walkthrough
- OAuth 2.1 for Remote MCP Servers (mcp.directory), practical auth migration guide
- Stateless MCP and Runtime Security (PipeLab), the deployment-vs-author complexity trade-off
- OpenTelemetry semantic conventions for MCP, the logging replacement path
- MCP TypeScript SDK, v1.26.x stateless transport support
- MCP Inspector, conformance testing with day-one RC support
- The new MCP spec and the unfortunate deprecation of MCP Sampling (nullpointer.se), dissenting view on SEP-2577
- Perplexity CTO Moves Away from MCP, the critique front, Ask 2026
- Code execution with MCP (Anthropic), vendor-stated 98.7% token reduction
