Blog
MCP after the 2026-07-28 spec: what the stateless core changes for builders
- MCP
- AI agents
- Engineering
The Model Context Protocol got its biggest revision so far on 28 July 2026. The headline is a stateless core: the handshake is gone, the session is gone, and every request now carries what the server needs to answer it.
If you run a small MCP server, or pay a vendor for one, this is mostly good news. There's less infrastructure to run and less to break. It also moves state to a place where the model can see it, and that part deserves a careful read.
I've checked every change below against the release post, the changelog and the deprecation registry, and the SEP numbers are the ones those pages give. I have not migrated a production server to this revision yet, so read the checklist as a reading of the spec and test it against your own SDK.
Key takeaways
- No initialize handshake and no Mcp-Session-Id. Each request carries its protocol version and client capabilities in _meta, so any server instance can answer it.
- State that spans calls now travels as a handle in tool arguments. The model can see it, so treat every handle as untrusted input and check ownership on each call.
- Streamable HTTP requests must carry Mcp-Method and Mcp-Name headers, and list results carry ttlMs and cacheScope.
- Roots, Sampling, Logging and Dynamic Client Registration are deprecated. The earliest they can be removed is the first revision released on or after 2027-07-28.
- Buying a server? Ask the vendor for a request log, the server/discover output and how handles are tied to a user.
Before and after, in plain words
Before. A client opened a connection and ran a handshake: initialize, then initialized. That's where the two sides agreed on a protocol version and told each other what they could do. Over HTTP the server could hand back an Mcp-Session-Id, and every later request had to find its way to whatever was holding that session. In practice that meant sticky routing or a shared session store.
After. There's no handshake and no session header. Each request stands alone and brings its protocol version and the client's capabilities in a field called _meta. The server reads it, answers and keeps nothing. Any request can land on any instance behind an ordinary round-robin load balancer.
For a small team, this means a server with no protocol session can run on serverless or edge platforms, restart whenever it likes, and scale without a session store. The price is that nothing is remembered for you any more. If your server needs to carry something from one call to the next, you now do that explicitly. More on that under handles.
Timeline, for planning: the release candidate was locked on 21 May 2026, SDK maintainers had ten weeks to validate it, and the final spec was published on 28 July 2026. It contains breaking changes.
What changed, one item at a time
Sessions and the handshake are gone. The initialize exchange is removed (SEP-2575), and so are protocol-level sessions and the Mcp-Session-Id header (SEP-2567). Every request carries its protocol version and client capabilities in _meta, and clients should also identify themselves there. A request for a version the server doesn't speak gets an UnsupportedProtocolVersionError. Deploys get simpler, because there are no sessions to drain or migrate.
A discovery call replaces the handshake's "what can you do". There's a new server/discover RPC (SEP-2575). Servers must implement it. Clients may call it before anything else, but don't have to. As a buyer, this is useful: you can ask a server what it supports and compare that with the sales deck.
Method and tool names move into HTTP headers. Streamable HTTP requests must include Mcp-Method and Mcp-Name (SEP-2243). A gateway, rate limiter or firewall can route and meter on those headers without parsing JSON bodies. Servers reject requests where the headers and the body disagree, so check that your proxy passes both through untouched.
List results say how long they're good for. Results from tools/list, prompts/list, resources/list, resources/read and resources/templates/list now carry ttlMs and cacheScope (SEP-2549). The first is a freshness hint in milliseconds. The second is "public" or "private" and says whether a shared intermediary may cache the response. Servers should also return tools in a stable order. Together that lets clients cache the tool catalogue and keeps the model's prompt cache from being invalidated by a reshuffled list.
Servers ask for input by returning, and the client retries. Servers used to send their own requests to the client mid-call, for elicitation, sampling or the list of roots. That needed a stream held open. Multi Round-Trip Requests (SEP-2322) replace it: the server returns a result marked input_required with the questions it needs answered, and the client retries the original call with the answers attached. Every result now has a resultType field, either complete or input_required. This is how a tool asks "delete 3 files?" on a stateless server.
Authorization is tighter. Three changes here. Authorization servers should return the iss parameter from RFC 9207, and clients must validate it before redeeming a code (SEP-2468), which closes a mix-up attack between authorization servers. Clients must keep credentials keyed by the issuer that minted them and never reuse them with another one (SEP-2352). And clients set application_type during Dynamic Client Registration (SEP-837), which stops authorization servers rejecting localhost redirects for desktop and CLI apps.
Dynamic Client Registration is on its way out. DCR is deprecated in favour of Client ID Metadata Documents, CIMD for short. It still works for authorization servers that don't support CIMD yet.
Tasks become an extension. Tasks were an experimental part of the core protocol. They now live in an official extension, io.modelcontextprotocol/tasks (SEP-2663). The blocking tasks/result method is replaced by polling with tasks/get, there's a new tasks/update for input from the client, and tasks/list is removed. If you built on the experimental version, this is a rewrite of that part.
Notifications move to one opt-in stream. The old HTTP GET endpoint and resources/subscribe are replaced by subscriptions/listen, a single stream where the client chooses which change notifications it wants. Resuming a broken stream is gone too. If a response stream breaks, the client sends the request again with a new id.
Trace context has fixed key names. The spec now documents the W3C trace context keys traceparent, tracestate and baggage inside _meta (SEP-414). One trace can follow a tool call from the host application through the client and server into whatever the server calls next.
Roots, Sampling and Logging are deprecated. They keep working, and new implementations shouldn't adopt them (SEP-2577). The suggested replacements: pass files or directories as tool parameters in place of Roots, call your model provider's API directly in place of Sampling, and log to stderr or use OpenTelemetry in place of Logging. A new lifecycle policy (SEP-2596) guarantees at least twelve months between deprecation and removal. For these three, and for DCR, the registry gives the earliest removal as the first revision released on or after 2027-07-28. The older HTTP+SSE transport is on the deprecated list as well, with a shorter window, so look that one up in the registry if you still serve it.
SDKs and hosts. The four Tier 1 SDKs, TypeScript, Python, Go and C#, supported the new revision on release day, and the Rust SDK supports it in beta. Anthropic said on the same day that support would roll out across Claude products. I haven't checked each Claude surface since, so confirm that your client speaks 2026-07-28 before you switch off the old path. Cloudflare's post says the new spec removes the need for its McpAgent class and points to a migration guide towards createMcpHandler, which has also moved into the official TypeScript SDK.
The handle warning: your state is now visible to the model
This is the part I'd read twice.
With sessions gone, a server that needs state across calls creates a handle in one tool and expects it back as an ordinary argument on the next call. The release candidate post uses a shopping basket as its picture: one tool returns a basket id, and the model passes that id to the tool that adds an item. The MCP maintainers say they prefer this to hidden session state, because the model can see the handle and pass it from tool to tool.
I think they're right about that. But look at where the state now lives. A session id sat in a transport header that the model never saw. A handle sits in the conversation. The model reads it and can repeat it, and so can anything else that gets text into that conversation: a web page the agent fetched, a document it summarised, the output of another tool.
A hypothetical example. A file tool returns a shortlist handle, and a second tool deletes "everything in shortlist X". If the handle is all that tool checks, then any text that persuades the model to call delete with that handle gets files deleted. Scope the handle to reading the shortlist, and put deletion behind its own confirmation. The new input_required round trip is built for that.
So I'd treat a handle the way you treat an id in a URL. It tells the server which thing is meant, and it's never the proof that the caller is allowed to touch it. The list below is my own advice. None of it is spec text.
- Check ownership on every call. Tie the handle on the server to the authenticated user or token that created it, and reject it for anyone else.
- Don't rely on the client name in _meta for this. That's a label the client writes about itself.
- Make handles unguessable and short-lived. Random, not sequential, with an expiry.
- Keep them narrow. A handle for one basket shouldn't open every basket, and a handle used for reading shouldn't authorise a delete.
- Validate them like any other model-supplied argument, because that's what they are.
- Log each use with the trace id, so misuse can be found afterwards.
Migration checklist
This is the order I'd go in for a server that was built against 2025-11-25. Your SDK's migration notes come first where they disagree with me.
- Upgrade to an SDK version that speaks 2026-07-28 and read its migration notes.
- Find every place that reads or writes Mcp-Session-Id or keeps per-session data in memory. Each one becomes either nothing or an explicit handle.
- Stop depending on initialize. Read the protocol version and client capabilities from _meta on every request, and return the version error for versions you don't support.
- Implement server/discover and make sure its output matches the tools you really expose.
- Check that Mcp-Method and Mcp-Name reach the server through your proxy or gateway, and that they match the body.
- Add ttlMs and cacheScope to list and read results. Return tools in a stable order.
- Replace server-initiated elicitation and sampling with the input_required round trip.
- On the client side: validate iss, key stored credentials by issuer, set application_type if you still use DCR, and plan the move to CIMD.
- If you used experimental Tasks, port to the extension: tasks/get polling and tasks/update.
- Replace the GET stream and resources/subscribe with subscriptions/listen if you send change notifications.
- Stop adding new uses of Roots, Sampling and Logging, and put 2027-07-28 in the calendar as the earliest date they can be removed.
- Review every handle against the list in the previous section.
- Test it: send two calls of one flow to two different instances, restart between them, and connect an older client if you still need to serve one.
What to ask a vendor
If you're buying an MCP server or a platform that hosts them, you can't read the code. You can ask for evidence. A vendor that has done the work can answer these the same day, with a log.
- Which spec revision does your server speak today: 2026-07-28, 2025-11-25, or both? If both, for how long?
- Can you show one raw request, with the Mcp-Method and Mcp-Name headers and the _meta block?
- What does server/discover return, and how do you keep it in step with the tools?
- Which state do you carry across calls, and through which handles?
- How is a handle tied to a user, how long does it live, and how is it revoked?
- Which of Roots, Sampling, Logging and Dynamic Client Registration do you still depend on, and when will that end?
- Do you validate the issuer per RFC 9207, and are stored credentials kept per issuer?
- How do long-running operations work: the Tasks extension, or something of your own?
- Which actions ask the user for confirmation, and is that done with the input_required round trip?
A prompt for a first-pass audit
If you maintain a server, paste this into a coding agent opened in its repository. It reads and reports and changes nothing. Paste the changelog text in with it, so the agent works from the spec and not from memory.
Treat the output as a list of things to verify. If you'd like someone to go through it with you, this is the work I do under agentic platforms, or just write to me. For what an MCP server looks like from the user's side, I walk through one in my post on Claude, Higgsfield and Katana.
Goal: Audit this MCP server codebase against the 2026-07-28 MCP specification and list the gaps, most serious first.
Inputs: This repository. The SDK and version it uses: [name and version]. Where it is hosted: [platform]. The 2026-07-28 changelog, pasted below this prompt.
Constraints: Read only. Do not change, create or delete files. Work only from the pasted changelog and the code. Do not rely on memory for spec details, and do not cite a SEP number that is not in the pasted text. If something cannot be decided from the code, say what you would need to see.
Definition of done: A list of gaps ordered by risk. Each gap names the file and function, the changelog item it relates to, and the smallest fix in one sentence. Cover at least: use of Mcp-Session-Id or per-session memory; reliance on the initialize handshake; server/discover; Mcp-Method and Mcp-Name headers; ttlMs and cacheScope on list and read results; server-initiated sampling, elicitation or roots requests; experimental Tasks methods; resources/subscribe or the GET stream; use of Roots, Sampling or Logging; issuer validation and credential storage if this code is also a client. Then a separate list of every handle or id the server returns from a tool and later accepts as an argument, stating for each whether the server checks that the caller owns it.Frequently asked questions
Do I have to migrate now?
Nothing was switched off on 28 July. The deprecated features keep working, and the earliest they can be removed is the first revision released on or after 2027-07-28. But sessions and the handshake are removed in this revision, not deprecated, so a client and a server on different revisions depend on their SDKs for backward compatibility. Check what your SDK does there, and plan the move inside the twelve months.
What breaks first if I ignore the new spec?
Anything that assumes a session. Code that waits for initialize, or stores data under an Mcp-Session-Id, has nothing to hold on to when the other side speaks 2026-07-28. Next are HTTP requests without the Mcp-Method and Mcp-Name headers, and anything built on the experimental Tasks methods.
Are handles safe?
They're as safe as the check behind them. A handle travels in tool arguments, so the model sees it and so does anything that can put text in front of the model. Use it to say which thing is meant, and decide whether the caller may touch that thing from the authenticated user, on every call.
What should a client cache?
Whatever the server marks as cacheable, for as long as ttlMs says. The tool list is the obvious win, because it's fetched often and changes rarely. cacheScope says whether a shared intermediary may cache the response: public means yes, private means no.
I only use MCP servers inside Claude or another client. Does this affect me?
Not directly. The client and the server vendors do the migration, and you may not notice anything. If a connector you depend on comes from a small vendor, it's fair to ask them which revision they support and when they plan to move.
Sources
- MCP blog: The 2026-07-28 Specification blog.modelcontextprotocol.io
- MCP specification 2026-07-28: key changes (changelog) modelcontextprotocol.io
- MCP specification 2026-07-28: deprecated features registry modelcontextprotocol.io
- MCP blog: The 2026-07-28 MCP Specification Release Candidate blog.modelcontextprotocol.io
- Claude blog: Bringing MCP 2026-07-28 to Claude claude.com
- Cloudflare blog: The next generation of MCP blog.cloudflare.com
Talk to me
Building an AI product and need someone senior to own the technical side? Book a free 30-minute call.