MCP 2026-07-28: The Stateless Spec
On July 28, 2026 the Model Context Protocol locked its biggest revision since launch. The headline: MCP is stateless. The initialize handshake and Mcp-Session-Id header are gone; every request carries its own identity in _meta; any server instance behind a load balancer can answer any call. Serverless MCP is no longer a workaround โ it's the shape of the protocol.
- What actually changed in the 2026-07-28 spec โ the four removals and the four additions that matter
- Multi Round-Trip Requests (MRTR): how a stateless server asks the user a follow-up question without a session
- The Extensions framework and the two official extensions: MCP Apps and Tasks
- Authorization hardening: OAuth 2.1, RFC 9207 iss validation, and what Dynamic Client Registration now requires
- A concrete migration path that doesn't break your 2025-11-25 clients โ with the SDK beta versions to install today
The one paragraph versionโ
Before: initialize handshake โ Mcp-Session-Id cookie โ sticky client/server pair for the life of the session. After: no handshake, no session id, no stickiness. Client identity and capabilities ride on every request inside a _meta object. A new server/discover method replaces the initial capability exchange. Application state โ a shopping cart, an in-flight task โ lives in handles the tool returns and the client passes back on the next call, exactly like a REST resource id. That single change is what unlocks serverless and edge deployment, real horizontal scaling, and the ability for two competing implementations to sit behind the same URL.
What got removedโ
- Mcp-Session-Id header โ and the entire notion of a protocol-level session
- initialize / initialized handshake โ replaced by _meta on every request + server/discover on demand
- Roots, Sampling, and Logging โ deprecated. Roots: pass paths as tool params. Sampling: call the LLM yourself. Logging: use stderr or OpenTelemetry.
- tasks/list โ removed even inside the new Tasks extension, because listing tasks without a session leaks across tenants
The Roots/Sampling/Logging deprecations are the ones most existing servers actually touch. All three assumed a persistent, bidirectional serverโclient channel. In a stateless world the client owns the LLM, the paths, and the log sink โ the server just answers the tool call.
What got addedโ
- A stateless RPC clients call whenever they need to know what a server offers. Replaces the capabilities the old handshake used to return. Cache the response with the new ttlMs / cacheScope fields on the response itself โ the spec inherits HTTP Cache-Control semantics.
- Mcp-Method and Mcp-Name (e.g. tools/call, search) let a load balancer route without parsing JSON-RPC. MCP-Protocol-Version replaces the session as the version negotiator.
- The stateless answer to elicitation. If a tool needs input mid-execution, the server returns an InputRequiredResult carrying the question and an opaque requestState blob. The client re-issues the call with inputResponses plus the echoed requestState โ and any server instance can pick it up.
- Reverse-DNS namespaced, independently versioned, negotiated through an extensions capability map. Official extensions ship first: MCP Apps (sandboxed HTML UIs, SEP-1865) and Tasks (long-running operations with tasks/get, tasks/update, tasks/cancel; SEP-2663). Everything else that used to demand a core-spec change now ships as an extension.
- Tool inputSchema and outputSchema now accept oneOf / anyOf / allOf, conditionals, and $ref. If you were flattening your schema to fit MCP's old subset, stop.
- traceparent, tracestate, and baggage travel in _meta. Distributed tracing across a client โ connector โ server chain finally works out of the box.
- 12-month minimum between Active โ Deprecated โ Removed. Anything deprecated on July 28, 2026 keeps working until at least July 28, 2027, and removal needs its own SEP.
The one JSON change that catches everyoneโ
Missing-resource error code moved from -32002 to -32602 (Invalid Params). If your client has an if (err.code === -32002) branch anywhere, it silently stops firing on July 28. This is the single most common integration bug in the RC.
Before / Afterโ
// BEFORE (2025-11-25): stateful handshake
POST /mcp { "method": "initialize", "params": { "capabilities": {...} } }
โ sets Mcp-Session-Id: abc123
POST /mcp Mcp-Session-Id: abc123
{ "method": "tools/call", "params": { "name": "search", ... } }
// server assumes it "knows" you because of the session cookie
// AFTER (2026-07-28): self-describing request
POST /mcp Mcp-Method: tools/call
Mcp-Name: search
MCP-Protocol-Version: 2026-07-28
{
"method": "tools/call",
"params": { "name": "search", "arguments": {...} },
"_meta": {
"clientInfo": { "name": "claude-code", "version": "..." },
"capabilities": { "extensions": { "com.anthropic.apps": "1" } },
"traceparent": "00-..."
}
}
// any instance can serve it; no session, no stickiness
Multi Round-Trip Requests, concretelyโ
The elegant part of the spec. A server that needs to ask "which calendar?" mid-execution doesn't need to hold a socket open. It replies:
{
"resultType": "input_required",
"inputRequests": {
"calendarId": { "type": "string", "prompt": "Which calendar?" }
},
"requestState": "base64(<opaque server-signed blob>)"
}
The client shows the prompt, gathers inputResponses, and re-fires the original request with both fields. The server treats requestState as authoritative โ often signing it โ so it doesn't need to remember the earlier attempt at all. That's the whole trick behind stateless elicitation: the state travels on the wire, not on the server.
The two official extensionsโ
- MCP Apps (SEP-1865): a server can ship an HTML UI, rendered in a sandboxed iframe by the client. Every action inside the UI still goes through the same JSON-RPC audit path as a normal tool call โ no back-door writes. Think interactive report + confirm-before-execute, not arbitrary web pages.
- Tasks (SEP-2663): the long-running-operation pattern, redesigned for a session-free world. tools/call returns a task handle; the client drives tasks/get and tasks/update to poll or stream, and tasks/cancel to abort. tasks/list is intentionally gone โ enumerating tasks without a session is a cross-tenant leak.
- Breaking change: the 2025-11-25 experimental Tasks API is not compatible with the new extension. If you shipped against it, treat the migration as a rewrite, not an upgrade.
Authorization: the OAuth cleanupโ
The old spec was OAuth-ish. The new one is OAuth 2.1 / OIDC-shaped:
- Clients must validate the
issparameter on authorization responses per RFC 9207 โ the fix for the mix-up attacks that hit MCP servers earlier this year. - Dynamic Client Registration now requires clients to declare an OpenID Connect
application_type, so an identity provider can enforce different rules for native vs web vs machine clients. - Refresh tokens follow the standard OIDC refresh flow โ meaning enterprise SSO (Entra, Okta, PingID) works without custom middleware for the first time.
Practical read: if you were building custom OAuth glue to make MCP fit Okta, you can delete most of it.
Install the beta todayโ
Python โ mcp v2.0.0b1 (single endpoint serves both revisions)
uv add "mcp[cli]==2.0.0b1" # or pip install "mcp[cli]==2.0.0b1"
TypeScript โ split packages, explicit opt-in for stateless
npm install @modelcontextprotocol/server@beta npm install @modelcontextprotocol/client@beta
Go โ v1.7.0-pre.1
go get github.com/modelcontextprotocol/go-sdk@v1.7.0-pre.1
C# โ v2.0.0-preview.1
dotnet add package ModelContextProtocol --prerelease
Compatibility promise from the SDK team: "nothing breaks today, and nothing breaks on July 28 either." New clients auto-negotiate down to the old handshake when they hit a 2025-11-25 server; the Python v2 server answers both revisions from the same endpoint by default; TypeScript and Go require an explicit opt-in to expose the stateless variant. TypeScript v1.x gets bug fixes and security updates for at least six months.
Migration playbookโ
- They still work through July 28, 2027. Do not build new servers against them. For paths: accept them as tool arguments. For LLM calls: use your own client. For logs: write to stderr and let the harness aggregate.
- If your server today remembers 'the user is halfway through a checkout,' return a basket_id from the tool call and require it on the next one. State on the wire, not in memory.
- Even if you keep the old handshake for now, exposing server/discover is what lets 2026-07-28 clients skip initialize entirely. This is the single change that unlocks serverless.
- Grep every codebase you own for 32002. This is the silent-failure trap of the migration.
- Send clientInfo and capabilities in _meta on every request. Propagate traceparent while you're in there โ you'll want the traces the first time something breaks in production.
- MCP Apps is worth it when a UI beats a chat turn (data-grid confirms, chart-driven approvals). Tasks is worth it any time an operation might exceed the client's HTTP idle timeout. Neither is free โ both add client surface area.
- It is not upgraded. Rewrite against the extension, keep the old endpoint alive during rollout, and remove it on your own schedule.
Gotchas people are actually hitting in the RCโ
- Sticky load balancers still โworkโ โ until they don't. A stateless server behind a sticky LB looks fine in dev and shreds cache hit rate in prod. Turn stickiness off explicitly.
- requestState is opaque to the client โ but it is not free storage. Servers that pack half a megabyte of context into it will blow up client memory. Sign a small handle, store the rest server-side keyed on the handle.
- MCP Apps iframes are sandboxed, not sanitized. A malicious server can still exfiltrate anything the user types into its UI. Treat an MCP App like third-party code โ allowlist which servers are allowed to render UIs at all.
- server/discover has no auth requirement in the base spec. Anything you expose there is discoverable by any client that reaches your URL. Do not put internal tool metadata there.
- The JSON Schema 2020-12 upgrade means clients that hand-wrote a validator against the old subset can now silently under-validate. Use a real JSON Schema library, not a hand-roll.
Where this lands on the AILmanac mapโ
- MCP Apps: Interactive UIs Inside a Tool Call โ a full deep-dive on the first official extension: capability negotiation, the
ui://scheme, the postMessage bridge, and the security model. - MCP & Connecting to Tools โ the API-side connector. Still current; the connector abstracts the transport, so the wire change is invisible to that request shape. Only the servers the connector talks to migrate.
- MCP in Claude Code โ how Claude Code speaks MCP to local + remote servers. The stateless model is the reason you can finally point Claude Code at a serverless MCP endpoint without weird timeouts.
- The MCP Token Tax โ deferred loading is the token-side lever; the extensions framework is the protocol-side lever for the same problem.
- Securing MCP Servers โ the NSA's May 2026 MCP security guidance and the RC's authorization hardening reinforce each other. Read both.
Quick checkโ
Check yourself
0/4Vocabulary you'll see on GitHub this weekโ
Sources & further readingโ
- The 2026-07-28 MCP Specification Release Candidate โ the primary announcement, with the removals, additions, and SEP list.
- Beta SDKs for the 2026-07-28 MCP Spec RC โ Python, TypeScript, Go, C# beta versions and the backward-compat promise.
- Bringing MCP 2026-07-28 to Claude โ Anthropic's rollout note for Claude and Claude Code.
- MCP 2026-07-28: From Local Tool to Distributed Protocol โ the sharpest third-party migration walk-through.
- MCP Just Went Stateless โ What the 2026 Spec Changes About Scaling โ Microsoft's perspective on stateless MCP on App Service.
- MCP Is Going Stateless. Here's What That Means โ Arcade.dev's practitioner take, useful for the ops implications.
- NSA CSI: MCP Security Design Considerations (PDF) โ the NSA's May 2026 hardening guidance. Pair with this spec's authorization changes.