
Photo by Taylor Vick on Unsplash.
MCP has just had the kind of change that makes a protocol feel less like a neat developer convenience and more like real infrastructure.
The 2026-07-28 revision removes protocol-level sessions from remote MCP. The
old initialize handshake is gone. The Mcp-Session-Id header is gone. A tool
call is now meant to be a self-contained HTTP request that can arrive at any
healthy instance of a server.
That is a breaking change. It is also, for most teams, good news.
But let us be precise before the internet turns this into another slogan: MCP becoming stateless does not mean every MCP application has become stateless. It means the protocol transport no longer carries hidden per-client state for you. Your shopping basket, browser session, work item, conversation checkpoint, or long-running job may still have state. It simply needs to live somewhere deliberate, and be named plainly.
For the average server builder, that is the heart of this release. For the DevOps engineer, it changes what a “scalable MCP deployment” looks like. And for teams with working older servers, it is not an instruction to burn down the house. It is an instruction to understand which house you are in.
The old shape: a server that remembered you by where you landed
Under the previous Streamable HTTP model, a client first called initialize.
The server returned an Mcp-Session-Id; the client then sent that header on
later calls. In practical terms, a request had to find its way back to the
right in-memory session, or every server replica had to share session state.
That sounds ordinary until you deploy more than one instance.
A load balancer only sees incoming requests. It does not naturally know that the tool call arriving now belongs to the process that handled an earlier handshake. So teams reached for sticky sessions, a shared session store, or a gateway that understood enough MCP traffic to route it specially. None of those are inherently wrong. They are useful when the application genuinely needs them. But they were an expensive tax for a server whose tool call was otherwise just “look this up, do this work, return the result.”
The new spec removes that transport-level burden. Its changelog makes the new
rule unusually explicit: sessions and Mcp-Session-Id are removed, and
cross-call state should use server-minted handles passed as normal tool
arguments. The specification changelog
is worth reading for that one line alone.
Here is the practical difference:
| Before | Now |
|---|---|
Start a session with initialize | Send a self-contained request |
Keep Mcp-Session-Id on later calls | Carry protocol and client metadata in _meta on every request |
| Route a client back to its session | Route a request to any healthy replica |
| Hidden transport state | Explicit application handles when state is needed |
The wire format has also become more useful to the infrastructure around it.
Mcp-Method and Mcp-Name are required HTTP headers on POST requests. That
means a gateway, rate limiter, or observability layer can make a decision based
on a declared operation rather than opening every JSON body and peering into
the traffic like a man searching a sack for lost coins.
Stateless protocol does not mean stateless product
This distinction is where most of the confusion will be.
Imagine a travel-planning MCP server. A user asks it to create an itinerary, then later to add a hotel, then later to delete a reservation. The server still needs durable state. It may need authorization. It may need a database record. It may need to remember an unfinished approval flow.
What changes is how that state travels between calls.
Instead of relying on a transport session that only one replica understands, the first tool returns an explicit handle:
{
"itinerary_id": "itr_4f0d2"
}
The next tool accepts that handle as an ordinary argument:
{
"name": "add_hotel",
"arguments": {
"itinerary_id": "itr_4f0d2",
"hotel_id": "hotel_987"
}
}
The model can now see the thing it must carry forward. Any replica can load it from the application datastore. The identifier can be audited, authorized, expired, and passed deliberately between tools. The state did not disappear; it moved out of the transport’s pocket and into the application contract.
This is a healthier design for many servers. It does mean that a server that quietly stored real business state only in an MCP session needs a proper migration. Do not expect a framework switch alone to complete that work. Identify what the session was holding, decide its real owner, and make the relevant handle part of the tool schema.
What new server builders should do
If you are beginning a remote MCP server today, build for the new architecture by default. The checklist is not long, but each item matters:
- Treat every tool call as independently routable. Do not assume the next call reaches the same process, pod, or container.
- Make application state explicit. Return opaque IDs such as
job_id,document_id, orbrowser_id, then accept them as later inputs. Validate ownership and expiry on the server; an opaque handle is not a permission slip. - Use
server/discover. It replaces the old handshake as the way a client can learn a server’s identity, versions, and capabilities before a normal request. - Move per-request metadata into
_meta. Protocol version, client capabilities, client identity, trace context, and request-level log preferences no longer arrive once at session start. - Design tool lists for caching. List and resource-read results now include
ttlMsand acacheScope. Return tools in a deterministic order and choose a cache lifetime you can actually honour. - Use the new multi-round-trip pattern for missing input. If a server
needs a user confirmation or more information mid-call, it returns an
input_requiredresult. The client gathers the answer and retries the original request with the response. Do not depend on a free-floating server-initiated request. - Test failure as well as success. A broken response stream loses the in-flight request; the client must retry it with a new request ID. Tool operations that can change money, data, or infrastructure should therefore have an application-level idempotency story.
The last point deserves a second look. Stateless transport makes scaling
cleaner, but it does not magically make duplicate requests harmless. If
create_invoice or delete_cluster can be retried after a stream breaks, the
application must recognise and safely handle repetition. There is no wisdom in
removing session affinity only to introduce accidental double execution.
What changes for existing servers?
Nothing needs to switch overnight.
The specification authors have published this as a foundational breaking revision, but they have also introduced a formal lifecycle policy: deprecated features remain available for at least twelve months before they can be removed. The old protocol will live on in real clients, SDKs, tutorials, and production estates for a while. That is normal. Working systems have a habit of refusing fashionable rewrites, and rightly so.
The sensible approach is a migration lane, not a migration panic:
- Keep a working stateful endpoint compatible for the clients that use it.
- Upgrade the SDK or framework and confirm which protocol versions it can negotiate.
- Add a stateless path for new remote deployments and new clients.
- Audit code that relies on
initialize,Mcp-Session-Id, session-local server state,ping, or the old server-initiated request model. - Add conformance tests and an end-to-end check through the actual load balancer or gateway, not just a local single-process test.
GitHub is a useful early example of the operating consequence. Its MCP server now supports the new specification, removed Redis-backed initialization sessions, and replaced deep packet inspection with the new MCP HTTP headers. That is not merely protocol compliance; it removes database work from every call and makes the deployment simpler to operate. GitHub’s announcement is unusually candid about the three changes.
Framework users should still read their framework’s release notes instead of assuming a decorator makes the transport concern vanish. FastMCP, for example, has a stateless HTTP mode designed for multi-worker deployments; its production guide shows that stateless mode removes the need for session affinity when running multiple workers. FastMCP’s HTTP deployment guide shows the shape of that switch. The exact configuration is framework and version specific. The design principle is not.
The DevOps change: MCP can use more ordinary infrastructure
This is perhaps the biggest quiet win in the release.
For a genuinely stateless MCP transport, the deployment playbook starts to look like the one platform teams already know:
- run multiple replicas;
- put them behind an ordinary round-robin load balancer;
- scale on the signals that matter;
- trace a request across the gateway, server, and downstream APIs;
- cache stable discovery and list results where their declared scope permits;
- keep application state in the durable store that owns it.
You may still need sticky routing for another part of the product. You may still need a shared store for OAuth tokens, jobs, rate limits, or browser automation. Stateless MCP does not repeal the laws of distributed systems. It does remove one very particular reason to make the transport sticky.
It also gives gateways a cleaner job. Because the method and tool name are
declared in headers, a gateway can apply per-tool policies, observability, and
rate limits without treating all MCP POST bodies as opaque lumps or inspecting
them deeply. The spec additionally standardises W3C trace context in _meta,
so a tool call can be followed from host, through gateway and server, to a
downstream dependency in one trace.
Where MCPLambda fits
MCPLambda was built around the very practical problem that an MCP server is not only a Python file or a TypeScript function. It still has to be deployed, secured, observed, configured, and kept available when a real client calls it.
The move toward a stateless protocol does not make that job disappear. It makes one class of deployment much simpler. New stateless servers can scale behind ordinary infrastructure without carrying protocol sessions between replicas. At the same time, plenty of useful MCP servers will remain on the older stateful model while their SDK, client, dependencies, or migration cost catch up. MCPLambda supports both stateful and stateless deployments, and will support the new protocol as that transition unfolds.
That compatibility matters. “Stateless is the new default” is not the same as “stateful is obsolete tomorrow.” A platform that can serve the new model while keeping working legacy servers on their feet gives teams room to migrate with judgment rather than ceremony.
There is a second problem that has nothing to do with HTTP sessions: tool configuration churn. A client may be configured with one MCP endpoint, but the team behind it may need to add, remove, replace, or reorganise the underlying servers and tools. MCPLambda Gateways provide a stable virtual MCP endpoint for that. Keep one client configuration; change the servers and tools behind the gateway as your system evolves.
That is especially helpful during this protocol transition. A gateway gives you a controlled entry point while you move individual services between versions, swap an implementation, or tighten policy around a sensitive tool. The clients do not need a new configuration every time the plumbing changes underneath them. A good gateway should let the architecture grow without making every user become the release manager.
If you are deploying or migrating MCP servers, MCPLambda is built for that operating reality.
The other changes worth knowing about
Stateless transport is the headline, but it is not the only change worth planning for.
Extensions, in plain English
An extension is an optional, separately versioned capability that sits beside the core MCP protocol. Think of the core protocol as the common language for calling tools and reading resources. An extension adds a specialised ability, such as an interactive user interface or a long-running job, without requiring every MCP client and server to implement it.
Before a server uses one, the client and server declare which extensions they support. If both sides support it, they can use the richer behaviour. If not, the server should keep using the ordinary MCP path. This is useful for teams deploying new features across a mixed estate, because they do not have to wait for every client in the world to upgrade. The new extensions framework also lets these capabilities evolve on their own schedule instead of turning every improvement into a breaking change to the core protocol.
MCP Apps: an interface inside the chat
An MCP App is an interactive HTML interface that an MCP server can provide to a supporting chat client. Instead of returning only a paragraph or a block of JSON, a tool can show a chart, a dashboard, a form, a map, a live log view, or an approval screen directly inside the conversation.
The server declares the tool and the HTML interface it is associated with. The host, meaning the chat client, renders that interface in a sandboxed iframe and passes data between the interface and the server. A user can interact with the form or chart, and the interface can ask the host to call MCP tools on the user’s behalf. Those calls still follow the host’s normal audit and consent path. MCP Apps are not a replacement for a normal web app. They are a way to keep a focused interaction in the same place as the agent conversation.
They are also progressive enhancement. A server should still return a useful text or structured-data result for clients that do not support MCP Apps. The client gets a richer experience when it can render the interface; everyone else can still use the underlying tool.
Tasks: a durable receipt for slow work
Some MCP tools finish in milliseconds. Others start a deployment, run a CI pipeline, process a large file, wait for a human approval, or call an external job system. Holding an HTTP request open for minutes is fragile, especially when the client disconnects or a proxy times out.
The Tasks extension lets a server return a durable task handle instead of
blocking until the work is done. The client can use that ID to poll for status,
provide more input if the task asks for it, cancel it where possible, and read
the final result later. In the new design, those operations are
tasks/get, tasks/update, and tasks/cancel.
For a first-time deployer, the important part is where that durability lives: the task cannot exist only in a process’s memory. Store the task ID, status, progress, result, and expiry in infrastructure that survives a restart and can be read by any replica. And only return task handles to clients that explicitly advertise support for them. The Tasks overview has a useful implementation walkthrough. Teams using the experimental tasks API from the previous spec should plan a focused migration, because its lifecycle has changed.
- Schemas are more expressive. Tool input and output schemas can use full
JSON Schema 2020-12 features, while
structuredContentcan be any JSON value. Bound schema-validation depth and time; do not auto-fetch external$refURLs. - Authorization has tightened. OAuth and OpenID Connect deployments need to pay attention to issuer validation, application types, and issuer-bound client credentials.
- Roots, Sampling, and Logging are deprecated, not immediately removed.
The replacements are ordinary tool parameters/resource URIs or server
configuration, direct model-provider integration, and
stderror OpenTelemetry respectively.
Do not try to absorb all of that in one refactor. Start with the transport and state model. Then schedule the extensions, authentication, and schema work according to what your server actually exposes.
Build for the architecture you mean
The best way to read this change is not “MCP has stopped supporting state.” It is: MCP has stopped pretending transport state is application state.
That separation is overdue. A protocol should make the common thing easy: receive a request, execute a tool, return a result, and let any healthy instance do the same. When a product needs continuity, it should express that continuity openly, with a handle, a database record, a task ID, an authorization boundary, and a lifecycle somebody can reason about.
For new builders, this is a better foundation. For operators, it means fewer special rules around a load balancer. For existing teams, it is a well-marked road, not a cliff edge.
And that is the thing worth carrying forward: make your server’s real state plain, make its transport boring, and let the infrastructure do what it was made to do.
Comments