Back to blog
Artificial Intelligence

MCP 2026-07-28: the protocol goes stateless (and what you have to change)

MCP 2026-07-28: the stateless protocol

By the Kiwop team · Digital Agency specialised in Software Development and applied Artificial Intelligence · Published on 29 July 2026 · Last updated: 29 July 2026

TL;DR — The fifth revision of MCP shipped on 28 July 2026 and it is the biggest change since the protocol was born. Sessions and the `initialize` handshake are gone: every request now travels alone, carrying its version and capabilities inside `_meta`. The calls the server used to initiate (sampling, elicitation, roots) move to the multi round-trip pattern. New mandatory headers let intermediaries route without opening the body, list results become cacheable, and authorization gets stricter. If your server is local stdio, there is no rush. If it is remote and keeps a session, the redesign starts today. Deprecated features have twelve months.

MCP 2026-07-28: the stateless protocol

Some protocol updates fit in a changelog line. Others change how the software that speaks them gets deployed. Revision 2026-07-28 of Model Context Protocol is the second kind. It does not add one more feature to a protocol you already knew: it takes away its memory. And when a protocol stops remembering, what changes is not the API, it is the architecture of everything running underneath.

Back in April we published our comparison of MCP, WebMCP and A2A and said the next relevant milestone would be Streamable HTTP consolidating as the remote default and legacy SSE being retired. Both happened, and a fair bit more arrived along the way. This article is the technical read of what exactly changes, what breaks, and what a team with MCP servers already in production has to do about it. With the specification in hand, not the press release summary.

What shipped on 28 July

MCP was born in November 2024 as a way for a model to talk to external tools without every integration becoming a bespoke adapter. Since December 2025 Anthropic no longer governs it alone: the protocol lives under the Agentic AI Foundation, inside the Linux Foundation, with Anthropic, OpenAI, Google, Microsoft, AWS, Block, Cloudflare and Bloomberg among its members. That shared governance explains the tone of this revision, which reads far more like an infrastructure standard than a vendor API.

Revision 2026-07-28 is the fifth of the specification and arrives with two process changes that matter as much as the technical ones. First: there is finally a formal feature lifecycle policy, with three states (Active, Deprecated, Removed) and a minimum twelve-month window before anything disappears. Second: there is a public registry of deprecated features with the earliest date each one can be removed. For anyone maintaining software in production that is worth as much as the protocol itself, because it turns planning into something you can put on a roadmap.

The core change: from session to self-contained request

Until this revision, speaking MCP meant opening a conversation. The client sent initialize, the server answered with its capabilities, the client confirmed with notifications/initialized, and from then on both sides took the context as given: which version was being spoken, what each side could do, who was who. Over HTTP that conversation materialised as an Mcp-Session-Id header the server issued and the client echoed on every call.

All of that is gone.

Before and after: the MCP session versus the self-contained request

What disappears

The initialize handshake and its notifications/initialized are gone. The Mcp-Session-Id header is gone, and with it the concept of a protocol-level session. The GET endpoint of the Streamable HTTP transport is gone, which is where the server used to open its own channel to speak on its own initiative. resources/subscribe and resources/unsubscribe are gone. ping, logging/setLevel and notifications/roots/list_changed are gone. And SSE stream resumability is gone: there is no Last-Event-ID and no event identifiers any more, so a broken stream loses the in-flight request and the client has to reissue it as a new request with a new identifier.

It is a long list, and it is not accidental. Everything that left shared one property: it assumed the connection meant something.

What replaces it

Now every request explains itself. What used to be established once at the beginning travels in the _meta field of each call, under keys reserved by the specification:

protocolVersion and clientCapabilities are required on every request: if either is missing the server must reject it with -32602 and, over HTTP, a 400 Bad Request. clientInfo is recommended unless you have a reason not to identify yourself. On the server side, the recommendation is to include io.modelcontextprotocol/serverInfo in the _meta of every result. It is worth reading the specification's note on both fields carefully: they are self-reported, nobody verifies them, and they must not drive security decisions or change behaviour.

Version negotiation also changes in nature. There is no moment of agreement any more: the client declares on each request which version it speaks and the server accepts or rejects each one independently. If it does not support it, it answers with UnsupportedProtocolVersionError (code -32022) and a list of the versions it does support, so the client can retry with a compatible one.

For clients that prefer to know up front there is a new method, server/discover, returning supported versions, capabilities and identity in a single call. Servers must implement it. Clients may or may not use it: firing the call you care about and handling the version error if it shows up is perfectly valid.

Why this is good news

The practical consequence is that an MCP server no longer needs memory between calls, and that changes everything about deployment. With no session there is no need for sticky routing at the balancer, no shared storage across instances, no long-lived processes. An MCP server becomes what any platform team has known how to operate for years: a stateless HTTP endpoint that scales horizontally, deploys to serverless or the edge, and goes up and down without dragging anything along.

The specification is explicit that not even an open stdio process should be read as a conversation: the client may interleave unrelated requests over the same transport, and the server must not use process identity as a proxy for continuity. If your server does need real state between calls (a working session, a cart, an analysis context), the correct route is now explicit: issue an identifier yourself and have the client pass it as an ordinary tool argument.

Multi Round-Trip Requests: the end of server-initiated calls

The second big change is the one that will touch the most code in servers that do anything interesting.

Until now, when a server needed something from the client mid-operation (a user answer via elicitation, a model completion via sampling, a directory list via roots) it opened its own JSON-RPC request in the opposite direction. That forced an open channel and, in practice, meant the same instance had to serve the whole operation.

The new pattern is called Multi Round-Trip Requests and it flips the flow: the server does not ask, the server ends the request by asking. It returns a result with resultType: "input_required" carrying an inputRequests map with what it needs, plus an optional opaque requestState. The client gathers that information and retries the original request with the answers in inputResponses and the requestState echoed back untouched.

Multi Round-Trip Requests: the server ends by asking, the client retries

Three details that are easy to skim past and then cost you an afternoon of debugging. The JSON-RPC id of the retry must be different from the initial request, because they are independent requests. requestState is opaque to the client, which must not inspect or modify it, but from the server's point of view it is attacker-controlled input: if it influences authorization or business logic it must be protected with HMAC or AEAD, given a short expiry, bound to the authenticated principal and to the originating request, and rejected when verification fails. And InputRequiredResult may only be returned on prompts/get, resources/read and tools/call, nowhere else.

As a side effect, every result now carries a mandatory resultType field, with "complete" for the ordinary case. If you talk to a server on an earlier revision that omits it, treat it as "complete".

The rest of the changes you will notice

Mandatory headers for routing. Every POST over Streamable HTTP must carry Mcp-Method with the method, and Mcp-Name with the tool name or resource URI on tools/call, resources/read and prompts/get. It sounds like paperwork until you think about who sits in front of your server: a balancer, a gateway or a WAF can now route, meter and apply policy without opening the JSON body. The server is required to validate that header and body match, and to reject with 400 and HeaderMismatch (-32020) when they do not. There is even a mechanism to mirror specific tool parameters into headers (x-mcp-header), designed for things like routing by region.

Cacheable lists. Results from tools/list, prompts/list, resources/list, resources/read and resources/templates/list now carry ttlMs and cacheScope (public or private). The first is a freshness hint so the client can cache instead of polling; the second says whether a shared intermediary may store it. And there is a small recommendation with a large effect: return tools from tools/list in a deterministic order, so the model's prompt does not change between calls and the LLM prompt cache hits more often.

Subscriptions. What used to be a GET is now requested with subscriptions/listen, a request whose response is a stream that stays open. The client picks what it subscribes to (toolsListChanged, promptsListChanged, resourcesListChanged, resourceSubscriptions) and the server tags every notification with io.modelcontextprotocol/subscriptionId. Progress and log notifications still travel on the response stream of the request they belong to, not here.

Authorization. Three tightenings. Authorization servers should include the iss parameter per RFC 9207, and the client must validate it against the recorded issuer before redeeming the code. Credentials are bound to the issuer that minted them: keyed by it, never reused with another, and re-registered if the authorization server changes. And Dynamic Client Registration (RFC 7591) is deprecated in favour of Client ID Metadata Documents, though it stays available for authorization servers that do not support CIMD yet.

Errors. There is finally an allocation policy: -32000 to -32019 is legacy ground nobody new should use, and -32020 to -32099 belongs exclusively to the specification. The three new codes are HeaderMismatch (-32020), MissingRequiredClientCapability (-32021) and UnsupportedProtocolVersion (-32022). On top of that, resource-not-found moves from -32002 to -32602, though clients should keep accepting the old one from older servers.

Tasks out of the core. Experimental tasks leave the base protocol and become an official extension (io.modelcontextprotocol/tasks), with polling via tasks/get instead of the blocking tasks/result. In the same vein, client and server capabilities gain an extensions field to negotiate anything outside the core.

What breaks and what does not

This is the table to have in front of you before touching anything. The specification calls modern the versions that carry metadata on each request (2026-07-28 onwards) and legacy the ones that use a handshake (2025-11-25 and earlier).

The important reading is in the two rows that fail: an old client cannot talk to a modern-only server, and there is nothing it can do about it. That is why the specification asks a modern-only server to name the versions it supports in whatever error it returns to an initialize: it may be the only diagnostic that client's user ever sees.

A server that wants to serve both worlds can, and it decides by how the client opens: a request carrying modern _meta is served statelessly; an incoming initialize selects legacy mode. A client wanting the same detects the server's era per transport: on stdio, by probing with server/discover and falling back to initialize on any error that is not a recognisable modern one; over HTTP, by firing a modern request and inspecting the body of a 400 before deciding. That result is cached per server, not per request.

What you have to change, case by case

If you run MCP servers over local stdio, which covers most personal assistants and nearly everything running inside an IDE, there is no rush. Your client and your servers negotiate whatever version both speak, and the previous revision is still valid. What you should do is update the SDK when you next touch that server for some other reason, not stage a migration of its own. That is exactly what we are going to do with the servers behind the personal assistant we built with Claude Code.

If you have a remote server that keeps a session, there is architectural work here, and it is now. This is not swapping a library: it is getting state out of the process. Inventory what you keep between calls today and decide, for each item, whether it disappears (because the client now sends it in _meta), whether it becomes an explicit handle the client carries as an argument, or whether it moves to an external store keyed by that handle. Anything that depends on two calls landing on the same instance should be considered dead. The reward is real: when you are done, your server runs on serverless.

If your server opens requests to the client (sampling, elicitation or roots), you have to rebuild that flow with MRTR. It is the change that moves the most logic, because you go from a conversational model to one where each attempt is self-contained and the context travels in the requestState you sign and verify. Start there, not with the rest.

If you are starting a new MCP server, build straight on 2026-07-28. There is no reason to build on the session model today, and there is one reason not to: in twelve months you would be migrating what you just wrote.

If you maintain a client, the work is different and probably larger: you have to support both eras for quite a while, cache what each server speaks, and handle degradation sensibly. The four tier-one SDKs (TypeScript, Python, Go and C#) already support the new revision, with Rust in beta, so a good chunk of this comes for free if you stay current.

The real timeline

The best thing about the lifecycle policy is that you can plan with dates rather than rumours. Here is what is deprecated and how long it lasts:

Watch that last row, which is the genuinely urgent one. The HTTP+SSE transport had been deprecated since March 2025 and now formally enters the policy with a window of only three months. If you still have anything speaking it, that is the migration to schedule, not the others.

Being eligible for removal does not mean it goes that day: the call belongs to the maintainers at each release and may come later. But it is the date to plan against.

What we would do

Three criteria we apply to this class of change, and that apply especially well here.

Do not migrate to be current, migrate for a reason. A stdio server that works does not need touching this week. The cost of migrating without a reason is not just the time: it is the risk you introduce into something that was fine. The twelve-month window exists to be used.

What is urgent is what gets designed now. Any new MCP server, and any platform about to open its API to agents, gets designed stateless from day one. That is the decision that costs the most to reverse later, because it is not a library, it is how you spread work across instances. In our case this feeds directly into how Nexo will expose its capabilities to its clients' agents: the design is done against the new revision, not the one we already knew.

Be careful building product on experimental surfaces. We learned that with WebMCP: we built on a Chrome origin trial and a change on the browser side broke navigation on our own site, with our health checks green. MCP 2026-07-28 is not experimental, but the lesson holds: when you adopt something freshly published, know how you switch it off if it goes wrong, and check the result with hands and eyes, not just a 200.

Frequently asked questions

Does anything break today if I do nothing? No, as long as your client and your servers speak the same revision. What worked keeps working, and the previous revision is still supported. It breaks when you mix eras: an old client against a server that only speaks the new one, or the other way round.

How much time do I really have? Twelve months minimum for Roots, Sampling, Logging and Dynamic Client Registration, counted from 28 July 2026. The exception is the HTTP+SSE transport, which can go three months after its proposal is final.

Do I have to implement `server/discover`? If you write a server, yes: it is mandatory. If you write a client, no: you can fire whatever call you need and handle the version error if it appears. On stdio it is also the recommended way to detect whether the server across from you is modern or legacy.

What do I do with the state I kept in the session? Get it out of the process. What was protocol context now travels in the _meta of each request. What is application state becomes an explicit identifier you issue and the client hands back as an argument, with the real data in an external store if needed.

Does this affect WebMCP? No. WebMCP is the browser-side proposal, driven by Chrome and worked on at the W3C, and it follows its own path. It is a different layer: MCP connects the agent to server-side tools, WebMCP exposes actions from the website the user has open.

And desktop clients like Claude or ChatGPT? Products will adopt the new revision at their own pace, and none has published concrete dates. In the meantime they negotiate a version with each server, so coexistence is there by design.

Conclusion

The short read of 2026-07-28 is that MCP has stopped behaving like a conversation and started behaving like an API. Every request explains itself, the server does not remember, and everything that needed memory has been redesigned or removed. That makes the protocol duller to read and far easier to operate, which is exactly what something aspiring to be infrastructure needs.

For most teams the practical answer this week is to do nothing urgent and plan properly. For anyone with remote servers holding sessions, or about to design how their platform opens to agents, the answer is the opposite: the work starts now, and it starts by getting state out of the way.

If you are in the second group and want to pressure-test the design with someone who has already done it, let's talk. We have been building on LLMs in production for two years, we run MCP servers, and we have paid the bill for adopting things early.

Initial technical
consultation.

AI, security and performance. Diagnosis with phased proposal.

NDA available
Response <24h
Phased proposal

Your first meeting is with a Solutions Architect, not a salesperson.

Request diagnosis