The July 28 MCP rewrite goes stateless. It fixes your load balancer and does nothing for the bill you actually pay: tool-schema context bloat.
If you shipped an MCP server in the last year, you built it around a lie the spec told you: that a connection is a thing you can hold onto. On July 28, 2026, that assumption gets deleted.
The 2026-07-28 release candidate locked on May 21, and it goes stateless. No more initialize/initialized handshake. No more Mcp-Session-Id header pinning a client to one process. The whole "open a session, keep it warm, route everything back to the same box" model that every tutorial taught you is now legacy. (It's still an RC until July 28, so treat the wire details below as the candidate, not the carved-in-stone final — but the SDK teams are already migrating against it.)
I want to make two arguments at once. First: going stateless is the right call, and it's overdue. Second: it fixes the operational pain everyone complained about while doing nothing for the thing that's actually expensive about MCP. Those aren't in tension. You can ship a beautiful round-robin deployment and still set your token budget on fire.
What actually changed on the wire
Here's the old request path. A client connects, does the handshake, gets a session ID, and from then on every request carries that ID and has to land on the same server instance that minted it.
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 4e9c1a7f-2b3d-44a8-9f10-0c2d6a1b88ef
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search_repo","arguments":{"q":"retry"}}}
That Mcp-Session-Id is the load-balancer tax. It forces sticky sessions or a shared session store, because instance B has no idea what instance A negotiated during initialize.
The new path drops the session entirely. Client metadata that used to be negotiated once at connection setup now rides per-request in a _meta field, and two new required headers — Mcp-Method and Mcp-Name — let gateways and rate-limiters route without parsing the JSON body.
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Method: tools/call
Mcp-Name: search_repo
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"search_repo","arguments":{"q":"retry"},
"_meta":{"client":{"name":"my-agent","version":"3.1.0"}}}}
Capability exchange that used to happen in initialize now happens through a server/discover call any instance can answer. List and resource responses also pick up ttlMs and cacheScope so a gateway can cache them like HTTP. That last part matters more than it looks — I'll come back to it.
Deleting the session store
The migration most people will underestimate is server state. If your server looks like this, every line of it is now dead weight:
# BEFORE — stateful, 2025-11-25 style
sessions = {} # in-memory, or worse, Redis you now have to operate
def on_initialize(req):
sid = uuid4()
sessions[sid] = {"caps": req.params["capabilities"],
"client": req.params["clientInfo"]}
return {"sessionId": sid, "capabilities": SERVER_CAPS}
def on_tools_call(req, headers):
s = sessions[headers["Mcp-Session-Id"]] # KeyError on the wrong box
...
After, there is no session map and no on_initialize. Each handler is pure with respect to the request — it reads what it needs from _meta and answers:
# AFTER — stateless, 2026-07-28 RC style
def on_discover(req):
return {"capabilities": SERVER_CAPS} # any instance can answer
def on_tools_call(req):
client = req.params["_meta"]["client"] # travels with the request
return run_tool(req.params["name"], req.params["arguments"])
The payoff is exactly what the spec authors advertised: you can put this behind "a plain round-robin load balancer" with no sticky routing and no shared store. Server-to-client prompts that used to need a persistent SSE stream now return an InputRequiredResult carrying echoed state, so any instance can resume the round trip. Kill your Redis session cache. Kill your sticky-session annotations. This is a genuinely smaller system to operate.
Tasks got demoted, and the lifecycle flipped
Tasks were an experimental core feature. In the RC they're reclassified as an opt-in extension, and tasks/list is gone — the spec is blunt that it "can't be scoped safely without sessions." If you can't pin a client to a box, you can't safely enumerate "its" tasks.
The lifecycle inverts. Instead of the server tracking long-running work against a session, tools/call returns a task handle and the client becomes the driver, polling and steering with tasks/get, tasks/update, and tasks/cancel.
// 1. client calls a long-running tool
{"jsonrpc":"2.0","id":7,"method":"tools/call",
"params":{"name":"reindex","arguments":{"scope":"all"}}}
// 2. server hands back a task handle (illustrative shape)
{"jsonrpc":"2.0","id":7,
"result":{"task":{"id":"tsk_91af","status":"working"}}}
// 3. client drives it from any instance
{"jsonrpc":"2.0","id":8,"method":"tasks/get",
"params":{"id":"tsk_91af"}}
If you leaned on tasks/list to rebuild a dashboard of in-flight work, that pattern is over. You now own task identity — persist the handles client-side (or in a real datastore your tools write to), because the protocol won't remember them for you.
The error code and auth changes that'll trip your tests
Two smaller changes will fail your integration suite quietly if you don't grep for them.
The resource-not-found code moves from the MCP-specific -32002 to the standard JSON-RPC -32602. If you have assertions or client branches matching on -32002, they'll silently stop firing.
- if (err.code === -32002) handleMissingResource();
+ if (err.code === -32602) handleMissingResource();
On auth, six SEPs pull MCP toward plain OAuth/OIDC. The one that bites first is mandatory iss validation per RFC 9207: the authorization response now has to carry the issuer identifier and your client has to check it matches the server you started the flow with. If you hand-rolled an OAuth client that ignored iss, it was technically exploitable (mix-up attacks) and is now non-conformant. Roots, Sampling, and Logging also enter deprecation on 12-month removal clocks — not gone in July, but don't build anything new on them.
Why none of this fixes the actual disease
Here's my contrarian read. Everything above is plumbing. It makes MCP cheaper to operate and does almost nothing about what makes MCP expensive to run: the model pays for your tool schemas on every single turn.
MCP co-creator David Soria Parra put it plainly in April 2026 — "a significant portion of the context window is consumed before the model does any actual reasoning." Statelessness doesn't touch that. Arguably it makes the framing worse: by deleting the session you delete the one place a server could have remembered "this client already saw these 40 tool definitions," so the obvious answer becomes re-sending schemas and re-sending context on more requests, not fewer.
I pulled traces on one of my own agents after reading the RC. The tool-schema block plus re-sent prior context regularly ate well over half the prompt before a single user token showed up. That's not a spec bug — it's the cost model. And the stateless rewrite's headline win is operational, not token-economic.
The one lever the RC actually hands you here is the new ttlMs and cacheScope on list/resource responses. That's the part to obsess over. A gateway that caches tool listings instead of re-fetching them per call is the closest thing in this release to addressing context cost — and it's buried under the routing-headers announcement. Use it. Cache aggressively at the edge, trim your tool surface to what the agent actually calls, and stop shipping 40 tools when the agent uses 6.
The takeaway
You have roughly ten weeks. The work is mechanical: delete initialize, delete the session store, move client info into _meta, add Mcp-Method/Mcp-Name, rewrite Tasks as client-driven, fix the -32002 → -32602 assertions, and validate iss. Do it, and your deployment gets simpler and cheaper to operate.
But don't confuse a simpler deployment with a cheaper agent. Statelessness solved the load balancer's problem. The model's problem — paying rent on your whole tool catalog every turn — is still sitting there, and the spec just made it slightly harder to cache your way out of. Migrate for the operations win. Then go fight the real bill.
Are you migrating before July 28 or waiting for the final spec to land? And has anyone actually measured what their tool schemas cost per turn — what's your number? Drop it in the comments.

Top comments (0)