MCP Goes Stateless: What the 2026-07-28 Spec Means for Teams Running Servers
The new MCP spec drops the handshake and sessions, adds routable headers and cache hints, hardens auth, and puts Roots, Sampling and Logging on a deprecation clock.
Umut Korkmaz
Published: 4 min read
On this page7
The Model Context Protocol published its 2026-07-28 specification yesterday, and it reads less like a feature drop and more like an infrastructure change. The initialize handshake and protocol sessions are gone, and every request now describes itself. That is what lets an MCP server sit behind a plain round-robin load balancer, with no shared session store, like any other HTTP service. It is also why this release has breaking changes that teams should plan for now.
No handshake, no session ID
The initialize/initialized exchange and the Mcp-Session-Id header are removed. Each request carries its protocol version and client capabilities in _meta, clients should identify themselves on every request, and a version mismatch comes back as UnsupportedProtocolVersionError. Servers must implement a new server/discover RPC; clients may call it first but do not have to. ping and logging/setLevel are removed as well.
The maintainers are clear that dropping the protocol session does not force your application to be stateless. If a server needs state across calls, the recommended pattern is to mint an explicit handle from a tool and let the model pass it back as an argument. I like this. Hidden transport state is hard to reason about in review, while a visible handle is something you can log, validate and expire.
Headers a gateway can use
Streamable HTTP POST requests must now include Mcp-Method, plus Mcp-Name for tools/call, resources/read and prompts/get, so a gateway, rate limiter or WAF can route and meter without parsing bodies. On a self-hosted nginx box, per-tool-call rate limiting becomes ordinary config:
map $http_mcp_method $mcp_call_key {
default "";
"tools/call" $binary_remote_addr;
}
limit_req_zone $mcp_call_key zone=mcp_calls:10m rate=5r/s;
# inside the /mcp location:
limit_req zone=mcp_calls burst=10;
One caution: if you authorize on headers at the edge, the server behind it has to reject requests whose headers disagree with the body. The transport spec requires exactly that (400 with HeaderMismatch, code -32020), and the changelog adds HeaderMismatchError to the schema, so confirm your SDK version enforces it before you trust the header alone.
Changes that break quietly
Several changes will not show up as compile errors:
- SSE stream resumability and
Last-Event-IDredelivery are removed. A broken response stream loses the in-flight request, and the client must reissue it with a new request ID. Tools with side effects should be idempotent, or move to Tasks. - Server-initiated
elicitation/create,sampling/createMessageandroots/listare replaced by Multi Round-Trip Requests. The server returnsresultType: "input_required"withinputRequests, and the client retries the original call withinputResponses. Every result now carries a requiredresultType. - The resource-not-found error code moves from
-32002to-32602. Grep for the old number. - Results from
tools/list,prompts/list,resources/list,resources/readandresources/templates/listmust carryttlMsandcacheScope("public"or"private"). Treatprivateas a hard rule for any shared cache in the path. - Servers should return tools in a deterministic order, and the changelog ties this directly to prompt cache hit rates. I have written about prompt caching before: a reshuffled tool list can invalidate a prompt prefix you are paying to cache.
Auth hardening
Authorization servers should return iss per RFC 9207, and clients must validate it against the recorded issuer before redeeming the code, which is the RFC's countermeasure to authorization-server mix-up attacks. Client credentials are now bound to the issuer that minted them: key persisted credentials by issuer, never reuse them across authorization servers, and re-register when the server changes. Clients must set application_type during Dynamic Client Registration, which the announcement says stops authorization servers rejecting localhost redirects for desktop and CLI apps. DCR itself is formally deprecated in favor of Client ID Metadata Documents, though it keeps working for backward compatibility.
Deprecations with a clock
Roots, Sampling and Logging are deprecated under a new lifecycle policy with a minimum twelve-month window. The suggested migrations are concrete: pass directories through tool parameters, resource URIs or config instead of Roots; call LLM provider APIs directly instead of Sampling; log to stderr or use OpenTelemetry instead of Logging. The legacy HTTP+SSE transport is formally deprecated too.
Tasks move into the io.modelcontextprotocol/tasks extension, with polling through tasks/get and a new tasks/update; tasks/list is gone. It joins MCP Apps and Enterprise Managed Authorization under a now-formal extensions framework. The TypeScript, Python, Go and C# SDKs supported the new version on release day, and the Rust SDK supports it in beta.
What I'm doing about it
For a team running MCP servers, this is the list I would start on Monday:
- Find every use of the session ID and any per-connection state, and replace it with explicit handles passed as tool arguments.
- Audit side-effecting tools for idempotency, because a dropped stream now means a retry, not a resume.
- Add header-based routing and rate limits at the gateway, and confirm the server rejects header/body mismatches.
- Set
ttlMsandcacheScopeon purpose, and make tool ordering deterministic. - Put Roots, Sampling and Logging removal on the roadmap while the twelve-month window is still long.
Code Pulse ships an MCP server and Convoke has MCP hooks, so both are on my own list, starting with the handshake removal and the new resultType on every result. Dropping sessions is the right call. It makes MCP something you operate like HTTP, and that is a much easier thing to run.
Sources
Primary references used for this post.
- blog.modelcontextprotocol.io/posts/2026-07-28 (opens in a new tab)
- modelcontextprotocol.io/specification/2026-07-28 (opens in a new tab)
- modelcontextprotocol.io/specification/2026-07-28/changelog (opens in a new tab)
- modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http (opens in a new tab)
- datatracker.ietf.org/doc/html/rfc9207 (opens in a new tab)