MCP went stateless: 5 changes the 2026 spec makes to your server
The Model Context Protocol release candidate locked on May 21, 2026. It ships as version
2026-07-28 on July 28, and the headline change rewrites how every server handles a
request: MCP is stateless at the protocol layer. No initialize handshake, no
Mcp-Session-Id, no sticky routing. Any request can land on any instance.
If you run an MCP server behind a load balancer, this removes the one piece of infrastructure that made horizontal scaling annoying: the shared session store. Here are the five changes that matter and the exact transport edits you need before clients start sending the new version.
1. The stateless core removes the handshake
The 2025 transport opened with an initialize/initialized exchange. The
server minted a session, handed back an Mcp-Session-Id, and the client echoed it on
every later request. That session had to live somewhere both the load balancer and the server
could reach, so you ran sticky sessions or a shared store.
// 2025 transport: stateful, sticky-routed
// 1. client opens a session
POST /mcp { "method": "initialize" }
// server replies with a session id the client must echo forever
HTTP/1.1 200 Mcp-Session-Id: 7f3a...
// 2. every later request must hit the SAME instance
POST /mcp Mcp-Session-Id: 7f3a... { "method": "tools/call" }
// load balancer needs sticky sessions or a shared session store
The 2026 transport deletes all of it. Protocol metadata that used to live in the session now
rides in a _meta field on every request. Clients read capabilities through a new
server/discover method instead of reading them once at connection time. The result:
no session object to lose, no instance to pin to.
// 2026-07-28 transport: stateless, any instance answers
// no handshake, no session id, metadata rides on each request
POST /mcp
Mcp-Method: tools/call
Mcp-Name: dns_lookup
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": { "name": "dns_lookup", "arguments": { "domain": "botoi.com" } },
"_meta": { "protocolVersion": "2026-07-28" }
}
// route on the header, serve from any pod, no shared session store 2. Routing moves into request headers
Because there is no session to inspect, the spec adds Mcp-Method and
Mcp-Name request headers so a proxy can route without parsing the JSON-RPC body. A
tools/call for dns_lookup carries both in the header, and your edge can
send heavy tools to a bigger pool without deserializing anything.
POST /mcp HTTP/1.1
Host: your-server.example.com
Content-Type: application/json
Mcp-Method: server/discover
Mcp-Name: capabilities
{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": {},
"_meta": { "protocolVersion": "2026-07-28" }
}
You can confirm a deployed server speaks the new transport by inspecting what it returns to a
plain request. The botoi headers endpoint shows the response status and headers for any URL, so
you can check an MCP endpoint's Allow and CORS headers without writing a client:
curl -X POST https://api.botoi.com/v1/headers \
-H "Content-Type: application/json" \
-d '{"url": "https://api.botoi.com/mcp"}' {
"data": {
"url": "https://api.botoi.com/mcp",
"status": 405,
"headers": {
"allow": "POST, OPTIONS",
"content-type": "application/json",
"access-control-allow-origin": "*"
}
}
} 3. State survives through explicit handles
Stateless transport does not mean stateless tools. A long-running job still needs to track progress. The spec's answer is the explicit handle pattern: a tool call returns an ID, the model passes that ID back on the next call, and the state lives in your data store keyed by the handle. Any instance can serve the follow-up because the request carries the key.
// Explicit handle pattern: state lives in your store, not the protocol.
// A long-running tool returns an id; the model passes it back next call.
app.post("/mcp", async (req, res) => {
const { method, params, _meta } = req.body;
if (params.name === "report_start") {
const handle = crypto.randomUUID();
await kv.put(`report:${handle}`, JSON.stringify({ status: "running" }));
return res.json({ result: { handle } }); // hand the id back to the model
}
if (params.name === "report_status") {
const row = await kv.get(`report:${params.arguments.handle}`);
return res.json({ result: JSON.parse(row) }); // any pod can answer
}
}); This is the same shape a REST API uses. The request carries everything the server needs, so you scale by adding pods, not by synchronizing sessions. The botoi MCP server already runs this way: stateless, one fresh handler per request, the API key forwarded in headers.
4. Tasks, Extensions, and MCP Apps graduate
Three features formalize in this revision:
- Tasks moved from an experimental core feature to an extension with a cleaner
lifecycle. Clients drive progress with
tasks/get,tasks/update, andtasks/cancelrather than the old polling design. - Extensions now carry reverse-DNS identifiers, negotiate through capability maps, version independently, and follow their own Specification Enhancement Proposal track. You ship a capability without waiting for a core release.
- MCP Apps let a server return interactive HTML rendered in a sandboxed iframe. Every UI action goes through the same audit path as a tool call, so a button click is logged and permission-checked like any other operation.
5. Authorization hardens around RFC 9207
Six SEPs tightened the OAuth 2.0 and OpenID Connect alignment. The change you cannot skip:
validate the iss parameter per RFC 9207. Without it, an attacker can inject an
authorization code minted by one server into a flow for another, a class of cross-server attack
that hits any deployment running more than one MCP server behind shared auth. The spec also
clarifies refresh-token rotation and warns against silent scope accumulation across consent
screens.
A formal deprecation policy ships alongside the spec. Features now move through Active, Deprecated, and Removed stages with a minimum 12-month window between deprecation and removal. Your 2025 server will not break on July 28; it enters the deprecation clock instead.
Your migration checklist
- Drop the session store. Stop minting and reading
Mcp-Session-Id. Read protocol metadata from_metaon each request. - Add routing headers. Emit and accept
Mcp-MethodandMcp-Nameso your proxy routes without parsing the body. - Convert sessions to handles. Return an ID from stateful tools and key your store on it. Any instance answers the follow-up.
- Add the iss check. Validate the authorization-server issuer per RFC 9207 on every token exchange.
- Pin to
2026-07-28. Build against the RC now; it is feature-frozen and matches the final spec.
Botoi runs a stateless MCP server with 49 curated tools today, so you can study a production
stateless transport instead of guessing. Connect it to Claude Code, Cursor, or VS Code from the
MCP setup page, or
inspect any endpoint's response with /v1/headers from the
interactive docs.
Frequently asked questions
- When does the stateless MCP specification take effect?
- The release candidate locked on May 21, 2026, and the final specification publishes on July 28, 2026, carrying the version string 2026-07-28. The RC is feature-frozen, so anything you build against it now will match the final spec. Servers on the older 2025 revisions keep working under the new 12-month deprecation policy, but the stateless transport is where every new client will land.
- What exactly is removed in the stateless core?
- The initialize/initialized handshake and the Mcp-Session-Id header are gone. A server no longer holds a session object between requests. Protocol metadata that used to live in the session now travels in the _meta field on every request, and clients fetch capabilities with a new server/discover method instead of reading them once at connection time.
- Do I have to rewrite my MCP server?
- Only the transport layer. Tool handlers, schemas, and descriptions are untouched. You drop the session store, stop reading Mcp-Session-Id, read protocol metadata from _meta per request, and add Mcp-Method and Mcp-Name request headers so a load balancer can route without inspecting the body. A server that was already stateless behind the scenes needs almost no work.
- How does state survive if the protocol is stateless?
- Through explicit handle patterns. A tool call returns an ID, and the model passes that ID back on the next call. The state lives in your data store keyed by the handle, not in a protocol session. This is the same pattern REST APIs have used for years: the request carries everything the server needs to act, so any instance can serve it.
- What changed in authorization?
- Six Specification Enhancement Proposals tightened the OAuth 2.0 and OpenID Connect alignment. The headline requirement is validating the iss parameter per RFC 9207 to stop authorization-code injection across servers, plus clearer rules on refresh-token rotation and scope accumulation. If you ship an authenticated MCP server, the iss check is the one change you cannot skip.
Try this API
HTTP Headers Inspector API — interactive playground and code examples
More guide posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.