If you run a Model Context Protocol (MCP) server behind a load balancer today, you probably have a sticky-routing rule, a shared session store, or both. The 2026-07-28 revision of the protocol removes the reason for all of that. It also removes a few things your client code may quietly depend on, and it moves some security work from the protocol onto you.
This guide covers what changed, what breaks, and a sensible order for migrating. Everything below comes from the official specification pages listed at the end. Where we are interpreting rather than quoting, we say so.
The short version
The headline change is that MCP is now stateless. Protocol-level sessions and the Mcp-Session-Id header are gone from the Streamable HTTP transport. The initialize and notifications/initialized handshake is gone too. Every request now carries its own protocol version and client capabilities in _meta, and servers must implement a new server/discover call so clients can find out what a server supports.
The practical upshot is that any instance behind a plain round-robin load balancer can answer any request.
What changed
| Area | Before (2025-11-25) | Now (2026-07-28) |
|---|---|---|
| Sessions | Mcp-Session-Id header, per-connection state | Removed. Cross-call state uses explicit server-minted handles passed as normal tool arguments |
| Handshake | initialize then notifications/initialized | Removed. Version and capabilities travel in _meta on each request |
| Discovery | Learned during initialize | server/discover (servers MUST implement) |
| Server-initiated requests | roots/list, sampling/createMessage, elicitation/create sent to the client | Multi Round-Trip Requests (MRTR); servers MUST use this pattern |
| Change notifications | HTTP GET endpoint, resources/subscribe | subscriptions/listen, a long-lived POST-response stream, opt-in per type |
| Stream recovery | SSE resumability via Last-Event-ID | Removed. A broken stream loses the in-flight request; the client re-issues it with a new request ID |
| Removed methods | ping, logging/setLevel, notifications/roots/list_changed | |
| Tasks | Experimental, in core | Moved to the io.modelcontextprotocol/tasks extension, redesigned around polling with tasks/get and tasks/update |
A few smaller changes are easy to miss and will bite in testing. Results from tools/list, prompts/list, resources/list, resources/read and resources/templates/list must now carry ttlMs and cacheScope. Streamable HTTP POST requests must send Mcp-Method and Mcp-Name headers. The resource-not-found error code changes from -32002 to -32602. Every result now needs a resultType field, though clients must treat a missing one from older servers as "complete".
Why stateless matters operationally
What the removal of sessions means for your deployment: the load balancer no longer needs to remember which instance owns a client.
Session affinity works fine until it doesn't. Sticky routing makes deployments awkward (draining a node means dropping sessions), makes autoscaling uneven, and forces a shared store if you want any resilience. Removing sessions lets you treat MCP servers like any other stateless HTTP service: scale horizontally, roll deployments freely, and let your gateway route on headers instead of inspecting bodies.
The trade-off is that state does not disappear; it moves. If your tools relied on "the connection remembers what I did earlier", you now need an explicit handle. The spec's guidance is that servers needing cross-call state mint a handle and pass it as an ordinary tool argument. That is more verbose for the model and the client, but it is also testable, loggable and survives a restart.
Removing stream resumability has a cost too. A dropped connection mid-call means the request is lost and must be re-issued. For long-running work, that pushes you toward the tasks extension, where the client polls instead of holding a stream open. If you have tool calls that run for minutes, this is probably your largest design change.
Server-initiated requests become Multi Round-Trip Requests
This is the most conceptually different piece. Previously a server could pause mid-tool-call and send the client an elicitation/create or sampling/createMessage request. That required the server to hold the call open and remember it, which a stateless server cannot do.
Under MRTR the server instead ends the request by returning a result with resultType: "input_required", carrying inputRequests (what it needs) and optionally an opaque requestState. The client gathers the input, then retries the original request with inputResponses and the exact requestState echoed back. The retry has a new JSON-RPC id and is a fully independent request, so any instance can handle it. The spec allows this on tools/call, resources/read and prompts/get only.
A multi round-trip request. The retry can land on a different instance, which is why the state has to travel with it and has to be verified.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
}
}
},
"requestState": "<AEAD-protected blob>"
}
}That example is adapted from the spec's own MRTR page. The part that matters most for security is the last field.
Security: what the protocol now does, and what it hands to you
Two things get safer by construction. With no Mcp-Session-Id, there is no session token to steal and replay. And servers can no longer push unexpected requests at clients at arbitrary times, since server-to-client requests now only travel inside a response to a client request.
Three things become your responsibility.
Treat requestState as attacker-controlled. It round-trips through the client, so a malicious or compromised client can modify it. The spec says servers MUST treat it as attacker-controlled input, MUST protect its integrity (HMAC or AEAD) if it influences authorization, resource access or business logic, and MUST reject state that fails verification. To limit replay, servers SHOULD bind the state to the authenticated principal, a short expiry, and an identifier for the originating request. The spec also warns that these measures do not guarantee single use; if something must be redeemed at most once, you enforce that server-side. In practice: sign it, put the user ID and a TTL inside it, and keep a consumed-token record for anything with side effects.
A requestState checklist drawn from the spec's server requirements. Bounding replay is not the same as guaranteeing single use.
Do not trust _meta for authorization. Protocol version and client capabilities now arrive in client-supplied _meta on every request. That is fine for negotiation. It is not a credential. This is our reading rather than spec text, but it follows directly from the fact that the client controls those fields, and security researchers at Akamai raise the same class of risk.
Be careful with state handles. If you mint handles for cross-call state, make them unguessable and bind them to the caller. A sequential or predictable ID invites one user to read or hijack another's workflow. Again this is our inference from the new design.
Authorization changes
Authorization is still optional in MCP overall, and stdio servers are told to take credentials from the environment instead. For HTTP-based servers that do use it, the revision tightens several things.
- MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728), and clients MUST use it to discover the authorization server.
- Clients MUST send the RFC 8707
resourceparameter in both authorization and token requests, and servers MUST validate that access tokens were issued for them as the intended audience. Servers MUST NOT accept or pass through tokens meant for other resources. - Client ID Metadata Documents are now the preferred registration route. Dynamic Client Registration (RFC 7591) is deprecated but kept for backward compatibility.
- Authorization servers SHOULD include the
issparameter in authorization responses (RFC 9207), and clients MUST validate a presentissagainst the issuer they recorded before redeeming the code. The spec says a future revision is expected to make server inclusion a MUST. - Clients MUST key stored credentials by issuer and re-register if the authorization server changes.
- Access tokens go in the
Authorizationheader on every request and MUST NOT appear in the query string.
One nuance worth stating: the spec bases its flow on the OAuth 2.1 IETF draft (draft-ietf-oauth-v2-1-13), not a finished standard. Some write-ups describe OAuth 2.1 as simply "required"; the accurate reading is that the spec profiles the draft for HTTP transports.
What is deprecated
Roots, Sampling and Logging are deprecated. They keep working during the deprecation window (the new lifecycle policy sets a minimum of twelve months), but new code should not adopt them. The spec suggests passing paths via tool parameters or resource URIs instead of Roots, calling LLM provider APIs directly instead of Sampling, and using stderr or OpenTelemetry instead of Logging. The old HTTP+SSE transport is formally reclassified as Deprecated as well, so move to Streamable HTTP if you haven't.
A practical migration order
- Inventory session dependence. Search for
Mcp-Session-Id, any per-connection caches, and any tool that assumes earlier calls on the same connection. Replace them with explicit handles. - Add
server/discoverand the new_metahandling. Keep serving older clients during the transition; clients are told to treat a missingresultTypeas"complete", which helps compatibility in one direction. - Rewrite elicitation and sampling flows as MRTR. Design
requestStatefirst: what goes in it, how it is signed, how long it lives. - Rework long-running tools around the tasks extension instead of long-held streams.
- Add caching hints (
ttlMs,cacheScope) and returntools/listin a deterministic order. - Audit auth. Publish Protected Resource Metadata, validate token audience, stop accepting tokens minted for other services, and plan the move off Dynamic Client Registration.
- Test with no affinity. Run at least three instances behind round-robin and kill one mid-flow. If anything breaks, you still have hidden state.
When to migrate now, and when to wait
There is no single right answer. If you run MCP servers at any scale behind a load balancer, the operational gain is real, and migrating early lets you shed session infrastructure. If you have a small internal stdio server used by one team, much of this barely touches you, and waiting for your SDK to ship support is reasonable. We have not verified the state of individual SDKs' support for this revision, so check your SDK's release notes before planning dates.
Frequently asked questions
Does the new MCP spec require OAuth? No. Authorization is optional. When an HTTP-based server supports it, the spec says implementations SHOULD follow the authorization specification.
Can old and new MCP clients talk to the same server? The changelog has some compatibility provisions, such as treating a missing resultType as complete, but it does not promise full interoperability across the handshake removal. Test against your actual client versions.
What replaces sampling and elicitation? They still exist as request types, but servers must now deliver them through the MRTR pattern. Sampling itself is deprecated.
Do I still need sticky sessions? Not for protocol reasons. You may still have application-level reasons if your own code holds state in memory.
Sources
- MCP specification 2026-07-28: Key Changes
- MCP authorization specification
- MCP Multi Round-Trip Requests
- Akamai: new MCP specification security teams must prepare
Need help with this?
If you are connecting AI agents to internal systems or running MCP servers in production, our AI & Automation team designs and migrates agent integrations, including auth, observability and scaling. For the bigger picture on where agents fit, read AI agents for business, or book a call to talk through your setup.

