Stateless MCP: replacing Mcp-Session-Id with handles
The MCP 2026-07-28 spec removes protocol-level sessions. How to find the hidden state in your server and replace it with explicit handles.
The MCP 2026-07-28 spec removes sessions from the protocol layer. The Mcp-Session-Id header is gone from Streamable HTTP, and list endpoints no longer vary per connection. A server that needs to keep something between two calls now does it explicitly, with identifiers it mints itself that travel as ordinary tool arguments.
The code change is simple. The problem is that most servers do not know where they store state.
Why sessions were removed
MCP was designed for one specific case: a local application talking to a local process over stdio, with a persistent connection. One handshake, both sides remember each other, everything is fine.
That model falls apart the moment you deploy behind a load balancer, inside a cluster, or across regions. The usual fix was sticky sessions, sometimes a Redis store, sometimes a gateway that opens the request body to extract the session ID before routing. Three ways of making infrastructure carry a problem the protocol created.
The 2026-07-28 revision removes the cause. The protocol is stateless, so the infrastructure can be too. Plain round-robin is enough.
Where your code still reads a session
This is the step everyone rushes, and it is 80% of the real work.
State almost never announces itself as a variable called session. Look for these shapes instead:
Pagination cursors. list_items returns a first page and keeps the offset server-side. The second call only works if it lands on the same instance.
Multi-step workflows. Tool A prepares something, tool B finalises it. In between, the intermediate object lives in memory.
User context cached at handshake. Tenant, plan, permissions resolved once and reused. With no handshake, that no longer exists.
Chunked uploads. A partial buffer is state by definition.
Background subscriptions and resources. They held state, they are not on the main request path, nobody thinks to check them. This is the classic miss.
Memoised expensive results. An in-process cache is fine as an optimisation. It becomes state the moment correctness depends on it.
Run this search on the server, but also on your in-house clients, your gateways and your deployment config. A static audit proves nothing, it gives you the list of places to inspect. Real compatibility is verified at runtime.
The handle pattern
The replacement the spec points to is simple: your server mints an opaque identifier, returns it in the response, and the client passes it as an argument on a later call.
How it works:
- The tool that creates state writes it to shared storage under a key it generates
- It returns that key to the client as a normal return value
- The next tool accepts that key as a parameter declared in its schema
- It resolves the key, verifies it belongs to the caller, and does the work
Three rules that keep you out of trouble:
Handles are opaque and unguessable. No auto-incrementing IDs, no encoded file paths. A random identifier resolved server-side.
Handles carry their owner. On resolution, verify the current caller is the one who obtained it. A handle sitting in a conversation history must be worthless to anyone else. This is the most obvious flaw in the pattern, and the one that turns up most often in audits.
Handles expire. Pick a TTL per state type. A pagination cursor lives minutes. A configuration draft might live an hour. A partial upload lives as long as the upload. Plan for explicit revocation too.
Two concrete cases
Pagination. Before, the server kept the offset. After, it returns an opaque next_cursor that encodes or references the position, and the tool accepts an optional cursor parameter. The easiest case, and often one that needs no storage at all if the cursor is signed.
Multi-step workflow. Before, prepare_export built an object in memory and run_export found it again. After, prepare_export writes the object to the database, returns an export_id, and run_export takes export_id as a required argument. The hidden benefit: the workflow becomes resumable, observable and testable, which it was not while it lived in a process.
That is the point worth keeping. This refactor is not a protocol tax. It makes your server better.
Removing sticky sessions
Once state is externalised, the infrastructure part is short:
- Drop session affinity at the load balancer
- Drop body inspection at the gateway if you added it for routing
- Drop the shared session store if that was its only job
- Route on the
Mcp-Methodheader instead of on content
Do this only after externalisation, not before. Otherwise you spend the week debugging intermittent errors.
The test that matters
There is one way to verify a stateless server: run a real multi-step scenario, round-robin across at least two instances, and kill an instance mid-scenario.
If the workflow completes, externalisation is done. If it fails, hidden state remains, and you know exactly which call.
This test belongs in CI, not in a one-off manual run. Both failure modes of this migration, sticky affinity and dead sampling paths, stay silent until production. In development, a single instance hides the first one.
FAQ
Can I keep my Redis store?
Yes, but for handles, not sessions. The difference is not cosmetic: a handle is referenced explicitly by the request, so any instance can resolve it. A session is implicit, so it forces affinity.
What about state that genuinely needs to live a long time?
That is no longer a protocol problem, it is a domain entity. Give it a table, a lifecycle, and a tool to list it.
What if my clients still send Mcp-Session-Id****?
A server answering both revisions can accept it on the old one. On the new revision the header has no meaning and must not become a bypass path.
Not sure where the hidden state is
That is exactly what the audit looks for. Send me your repository and within 24 business hours you get the list of places your server still assumes a session, ranked by risk, with a quote for the fix.
Ready to check? Get my MCP server audited →
Related reading
Got an MCP server to audit?
Five minutes to describe it. I reply within one business day with a first written diagnosis, free of charge.
Get my MCP server audited