Product launch · XiaoHu explains

Anthropic ships a new MCP spec that finally lets you run servers in the cloud

It's the biggest change since MCP shipped — but July 28 doesn't flip a switch. Existing implementations keep talking to each other.
The one-minute version
  • MCP servers had a nagging flaw: the client had to shake hands first to get a session ID, and every request after that had to land on the same machine. Adding servers didn't help, and serverless was off the table.
  • The new spec throws that rule out. The handshake and the session ID are gone; every request carries its own identity, any machine can take it, and a plain load balancer is all you need.
  • When a server needs to ask the user something mid-flight — "this will cost money — confirm?" — it now takes two round trips: return "I'm missing something," let the client ask, then resend the original request.
  • State didn't disappear. It moved into the open: the server hands out a handle and lets the model pass it back as a parameter.
  • A batch of features got cut or put on probation. The handshake, session IDs, and SSE resumption are gone outright; Roots, Sampling, Logging, and the old HTTP+SSE transport get at least 12 more months.
  • An outside security analysis flags the trade: the protocol closes off session hijacking and its cousins, but pushes the security burden onto developers — one request can now spin up an expensive task and walk away.
  • The most useful takeaway: July 28 is not a switch. Your current servers and clients won't break, and new clients fall back to the old handshake when they meet an old server.
⚑ Primary material comes from the official MCP launch blog and Anthropic's same-day announcement; for protocol details, the spec text and changelog are authoritative. Performance and impact numbers from ecosystem companies are their own claims. The security section draws on third-party work, sourced at the end.
Where it starts

What old MCP got stuck on: sessions pinned a server to one machine

On July 28, 2026, MCP published its fifth spec revision, 2026-07-28, converting the protocol core from a stateful bidirectional connection into stateless request/response, and introducing a formal extension framework and deprecation policy. Anthropic announced the same day that Claude products would roll it out across the board.

MCP (Model Context Protocol) connects AI assistants to outside tools and data. Anthropic open-sourced it in late 2024, and the whole industry runs on it now. The four first-tier SDKs — TypeScript, Python, Go, and C# — pull close to 500 million downloads a month combined, with TypeScript and Python each past a billion cumulative downloads.

≈500MMonthly downloads across the four first-tier SDKs
950+MCP servers listed in the Claude connector directory
~20%Of honeycomb.io's monthly interactive queries come from agents

What this revision sets out to fix is one very specific engineering knot: deploying a single MCP server to the cloud at scale is hard.

Think of it this way

Old MCP is a phone call. You dial in (the initialize handshake), the operator gives you an extension (Mcp-Session-Id), and every word after that is tied to that extension. Want a different operator? Sorry — only the original one remembers what you just said.

New MCP is a shipped package. Every parcel carries the full sender, recipient, and contents on the label. Whoever picks it up can handle it, no introductions required.

The phone-call model created three knock-on problems.

First, adding machines doesn't help. The second machine has never heard of your extension, so a request routed there falls flat. To scale, you either make the load balancer remember which machine each user belongs to (a sticky session), or put every machine behind a shared store like Redis to look up sessions.

Second, serverless and edge nodes are out. Those environments are built on the premise that every invocation is fresh and forgets everything afterward, which is fundamentally at odds with remembering a session.

Third, gateways in the middle have no idea what you're doing. Everything lives in the JSON body, so any load balancer, rate limiter, or firewall that wants to route or throttle by "which tool is this calling" has to crack the JSON open and read it. That's expensive, and plenty of gateways simply don't bother.

Before · with sessions
Load balancer
(must remember who goes where)
Node 1
idle
Node 2
only one
Node 3
idle
Every request from this client
must come back to Node 2
Now · stateless
Load balancer
(round-robin is fine)
Node 1
Node 2
Node 3
Any machine can take
any request
Our diagram: where one client's requests land under the old spec versus the new one
Mechanics

How the stateless core actually works

So how does the new spec untie the knot? Three moves.

The handshake is gone

The initialize and notifications/initialized opening ritual has been deleted outright, and the Mcp-Session-Id HTTP header went with it. Everything the handshake used to exchange — protocol version, who the client is, what the client supports — now rides along with each request inside the _meta field. Servers, for their part, are encouraged to report their own identity in the _meta of every result they return.

HTTP headers · all a gateway needs to read
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
_meta in the body · every request introduces itself
io.modelcontextprotocol/protocolVersion
Required
Which spec revision this request speaks
io.modelcontextprotocol/clientCapabilities
Required
What the client supports. Servers must not use a capability the client didn't declare, and must return a missing-capability error (-32021) if they try
io.modelcontextprotocol/clientInfo
Recommended
Client name and version. The spec is explicit: this is self-reported, the protocol doesn't verify it, and it's meant for display, logs, and debugging — never for security decisions
io.modelcontextprotocol/logLevel
Optional
How verbose the server should log for this request. Without this field, the server must not emit log messages
What a 2026-07-28 request looks like: identity moves from "stated once at handshake" to "stated every time"
The full example from the launch blog
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"},
 "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}
One call to the search tool with the argument "otters." The whole packet carries its own context, so any machine it lands on can finish it alone.

There's a new way to ask a server what it can do

A new server/discover method reports which protocol versions a server supports, what its capabilities are, and who it is. Note the asymmetry: servers must implement it, clients don't have to use it. A client can query it before doing anything else, or skip it and fire off a request — both are perfectly legal. That's a real break from the old initialize, which was a mandatory opening ritual. This is just an ordinary query.

The spec says it flatly: a connection is not a session

Straight from the spec

A server must not rely on earlier requests over the same connection to infer context — that includes capabilities, protocol version, and client identity. Each request supplies these itself in _meta.

An open connection — even a long-lived local stdio process — is not a conversation. A client may push completely unrelated requests down the same pipe, and a server may not treat "connection identity" as "session identity."

Worth noting: a long-lived connection like subscriptions/listen doesn't break statelessness either. It's still an ordinary request/response — the "response" just happens to be a notification stream that stays open. Its state belongs to that request, not to the connection underneath.

The stateless-core demo from the launch blog (original video, source: official MCP blog)
Easy to misread

The key point: state didn't vanish, it moved into the open

Here's where people get it wrong. No sessions at the protocol layer doesn't mean your application has to be stateless.

If your server genuinely needs to remember things across calls, the official approach is to emit an explicit handle from a tool and let the model pass it back as an ordinary parameter on later calls.

Before · state hidden in transport
  • The session ID lived in the transport layer, invisible to the model
  • State followed the connection — drop the connection, lose the state
  • Switch machines and everything is lost
  • Hard to debug, because the state wasn't in your application code
Now · the handle is out in the open
  • The server returns a handle from a tool — just an ordinary return value
  • The model can see it and thread it between different tools on its own
  • It travels as a tool argument, independent of any connection
  • State belongs to your application layer, where you can inspect and debug it

The launch blog's argument is that this beats session state buried in the transport, precisely because the model can see it:

We've found this works better than hiding session state in the transport layer — the model can see the handle and thread it between tools itself.

Official MCP launch blog, 2026-07-28

There's a trade here worth naming. State management didn't go away; it moved from "the transport quietly handles it for you" to "you handle it explicitly in your application." You get transparency, debuggability, and horizontal scaling. You also get the bill.

Core mechanism

MRTR: asking the user something now takes two round trips

If a server can no longer speak first, how does it ask the user a question mid-flight?

Picture the situation. A tool is halfway through and needs the user to confirm something: Supabase's MCP server is about to create a new project and needs to ask "this will incur charges — confirm?", or a SQL statement will delete data and needs a nod first.

The old way was for the server to send a request backwards down that always-open bidirectional stream — to ask a question (elicitation/create), borrow the client's model for a quick inference (sampling/createMessage), or ask where the file directories are (roots/list). All of it assumed the stream stayed open, and that stream is exactly what going stateless tears out.

The new way is MRTR (Multi Round-Trip Requests). Instead of sending a request, the server returns an unfinished result with its question packed inside; the client asks the user and resends the original request with the answer attached. Click through the five steps below:

Client
(Claude and friends)
MCP server
Step 1 · client → server
{"id":1, "method":"tools/call",
 "params":{"name":"create_project", …}}

The client fires off a tool call as usual, request ID 1. Nothing new here.

Step 2 · server → client
{"result":{
  "resultType":"input_required",
  "inputRequests":{"confirm":{…}},
  "requestState":"a signed blob"}}

The server realizes it's short on information and returns a result — note, not a request. resultType is set to input_required, and inputRequests holds the question it wants to ask.

requestState is an opaque blob it hands the client for safekeeping, encoding how far it got. That way the server stores nothing itself.

Step 3 · client-side

The client shows the user the question: "Creating this project will incur charges. Confirm?" The user confirms.

The server sits this step out entirely — it can even be recycled or swapped for another machine meanwhile. The spec says the client must not inspect, parse, or modify what's inside requestState, and must not assume anything about it. Hold it as-is, nothing more.

Step 4 · client → server (possibly a different one)
{"id":2, "method":"tools/call",
 "params":{"name":"create_project", …,
  "inputResponses":{"confirm":{…}},
  "requestState":"returned untouched"}}

The client packs the answer into inputResponses, resends the entire original request, and returns requestState untouched.

The request ID must be new — 2 here. The spec is explicit about this, because as far as the protocol is concerned these are two independent requests.

Step 5 · server → client
{"result":{"resultType":"complete", …}}

The server now has what it needs, does the work, and returns the final result.

Not one step in that round trip depends on the same machine or the same connection.

Our diagram: the five MRTR steps, drawn from the "basic flow" section of the spec

Supabase's product lead says the pattern unlocked something they'd wanted for a while:

Asking the user has been on our roadmap for a while, but it was awkward because the Supabase MCP runs stateless. MRTR changes that. Our tools can now check with the user before acting — the cost of creating a project, say, or a query that will delete data.

Inian Parameshwaran, product lead, Supabase

Two costs

First, the whole original request gets resent. The server may have to redo the earlier work, unless it encodes its intermediate results into requestState too.

Second, requestState is a live grenade. It passes through the client's hands before coming back, and the spec is unusually firm about it:

What the spec demands of requestState

A server must treat requestState as attacker-controlled input. If it can affect authorization, resource access, or business logic, the server must protect its integrity with something like HMAC or AEAD — a wax seal nobody else can forge — and reject anything that fails verification. There's exactly one exemption: when tampering can do no worse than fail that one request.

Replay protection only rises to a "should." The mechanism is to embed three things in the protected blob and check each one: who (reject if someone else presents it), how long it's valid (reject if expired), and which request it came from (the method name plus a digest of the key arguments — reject on mismatch).

The spec adds a candid caveat: these measures narrow the replay window and block cross-user, cross-request reuse, but they do not guarantee single use. If you truly need one-shot semantics — a redemption code, say — you have to enforce that server-side yourself.

The shared store the server no longer needs has become the cryptographic signing it now has to get right.

MRTR also has a limited scope: it works only on prompts/get, resources/read, and tools/call. On any other request, a server must not return input_required.

Routing

Gateways can finally read MCP (plus a security rule the launch blog skipped)

State is handled. That leaves the older problem: the gateways in between never knew what you were calling.

The new spec adds required HTTP headers to every POST. Mcp-Method rides on every request and names the method being called. Mcp-Name shows up only when a specific target is named — on tools/call, resources/read, and prompts/get — and identifies which tool, resource, or prompt. Together with the always-required MCP-Protocol-Version, those few lines are all a gateway, rate limiter, or firewall needs.

Before · gateway cracks the packet
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"execute_sql","arguments":{"query":"SELECT * FROM …","region":"us-east-1"},"_meta":{…}}}

Finding out which tool this calls means parsing the entire JSON body. For a high-throughput gateway that overhead doesn't pay off, so many skip it entirely.

Now · read two headers
Mcp-Method: tools/call
Mcp-Name: execute_sql

Routing, rate limiting, metering, and authorization by method and tool name all happen at the header layer. The body stays sealed.

It sounds like pure convenience, but it comes with a hard validation rule the launch blog doesn't mention:

Servers must check headers against the body

A server must verify that the values in Mcp-Method and Mcp-Name match the corresponding values in the request body, and return a HeaderMismatch error (code -32020) when they don't.

Why it's non-negotiable: without the check, an attacker can put a harmless tool name in the headers to get past the gateway while the body calls something dangerous. The gateway reads the header and waves it through; the server reads the body and executes. That gap is the attack.

The spec also has a word for middle layers: if the protocol version is too old, or the headers are missing entirely, reject the request rather than trust an unvalidated header.

There's a companion feature called x-mcp-header: a server can mark a tool parameter as "mirror this into an HTTP header," named Mcp-Param-{name}, which makes routing by tenant and similar dimensions easy. It's optional for servers but mandatory for clients. Remember it — it comes back in the security section.

The money clause

Tool lists are finally cacheable (and upstream prompt caches survive)

Statelessness brings one convenient side effect: list results no longer vary by connection, so they can be cached.

Results from five methods — tools/list, prompts/list, resources/list, resources/read, and resources/templates/list — must now carry two fields:

ttlMs
How many milliseconds this data stays fresh. Clients cache accordingly instead of asking over and over.
cacheScope
public or private — decides whether shared proxies along the way can cache on everyone's behalf.

There's a lower-tier requirement that's worth far more money: servers should return tool lists in a deterministic order.

That one needs unpacking. The tool list gets spliced into the model's prompt, and prompt caching matches from the beginning, character by character. Change one character at the front and the entire cache behind it is invalidated and rebilled. If your tool list comes back in a different order each time, the upstream cache never hits.

Stable order
search
create_file
run_query
✓ Prefix unchanged, cache hit
Random order
run_query
search
create_file
✗ Prefix changed, billed again
Our diagram: how tool-list ordering hits the upstream prompt cache. Same tools, only the order differs

A small "keep the order stable" rule, real money saved.

Checklist

What got deleted, and what got a stay of execution

This is the part to check yourself against: which of your things will break. The changelog has 9 major changes, 12 minor ones, and 4 deprecations. The two lists below are the ones to watch.

Deleted
Gone in the new revision
  • The initialize / notifications/initialized handshake
  • The Mcp-Session-Id header and protocol-level sessions
  • The HTTP GET endpoint, resources/subscribe, and resources/unsubscribeReplaced by one subscriptions/listen stream, with clients subscribing per notification type
  • ping, logging/setLevel, notifications/roots/list_changedLog level now rides per request as logLevel in _meta
  • SSE resumption (the Last-Event-ID header and SSE event IDs)Break the stream and the in-flight request is lost; the client must resend the whole thing with a fresh request ID
  • tasks/result and tasks/listReplaced by tasks/get polling
  • notifications/elicitation/complete and elicitationId in URL-mode elicitation
  • Server-initiated requests as a patternAll of it moves to MRTR
Stay of execution
Still works, guaranteed 12 months
  • Roots (telling a server which directories to look at)Pass them via tool arguments, resource URIs, or server config
  • Sampling (a server borrowing the client's model)Call the model provider's API directly instead
  • Logging (protocol-level logs)Write to stderr, or use OpenTelemetry
  • The old HTTP+SSE transportSoftly deprecated back in 2025-03-26, now formally deprecated; migrate to Streamable HTTP
  • The thisServer and allServers values of includeContextOmit it, or use none
  • Dynamic Client Registration (DCR)CIMD is the recommended path; DCR stays for compatibility and will be removed in a future revision

Error codes moved too

Resource-not-found changed from -32002 to the standard -32602 (invalid params), aligning with the JSON-RPC spec. Three new codes were renumbered: -32020 header/body mismatch, -32021 missing required capability, -32022 unsupported protocol version. The spec also drew a boundary: -32000 through -32019 is a legacy zone that implementations claimed for themselves before there was a policy, so no new codes may be assigned there and new implementations shouldn't use them; -32020 through -32099 is reserved for the spec.

If your client code hardcodes -32002, this one's on you — but don't overcorrect. The spec also requires clients to keep accepting -32002 from older servers; you just stop sending it yourself.

The deprecation policy is itself new here

Deprecation used to run on unwritten understanding. Now it's in writing: three feature states and one minimum window.

Active
In use. Implement per the spec
SEP
accepted
Deprecated
Fully functional, but don't build new things on it. A replacement must be documented
at least
12 months
Removed
Cut from the spec. Still findable in older revision docs
Our diagram: the three feature states, per the spec's feature lifecycle and deprecation policy

The 12 months runs from the release of the revision that marked the feature deprecated, not from when the proposal was finalized. Two things come with it. There's a single "pending removal" page, so you don't have to piece the picture together from old changelogs. And first-tier SDKs are required to surface deprecations idiomatically in their next release — TypeScript's @deprecated, C#'s [Obsolete], Java's @Deprecated, Go's Deprecated: comments — with a downgrade process for SDKs that don't.

New division of labor

Extensions became an official channel

With the core slimmed down, where do new capabilities go?

Adding anything to MCP used to mean stuffing it into the core protocol — Tasks lived there as an "experimental feature." This revision establishes a formal extension framework: the core stays lean, new capabilities ship as extensions, and clients and servers declare which extensions they support in their capabilities.

Tasks
Long-running work. Instead of blocking, the server returns a task ID and the client polls progress with tasks/get.
MCP Apps
Servers can render interactive UI directly in the conversation, inside a sandboxed iframe — no tab switching.
EMA enterprise auth
Admins enable access org-wide in one shot through the company identity system (Entra, Okta), and users connect automatically on first login.
Core protocol 2026-07-28
Stateless request/response. Extensions build on top; the core stops changing for individual features
Our diagram: how the core protocol and the three official extensions divide the work

Tasks was contributed by AWS and is one of the first official extensions; AWS says the new spec and its stateless core are already available in Amazon Bedrock AgentCore. The mechanism deserves a closer look. When a server decides a request will run long, it returns a task ID instead of a final result, and that ID is a durable handle — the client can disconnect, restart, and keep polling with the same ID. Tasks have five states:

working
input_required
completed
failed
cancelled

The three dark ones are terminal — once reached, they don't change. If the task needs to ask the user something, it moves to input_required and the client answers with the new tasks/update. No second connection, no server-initiated messages.

Authorization

Four places where authorization got tighter

Deletions and additions covered. One more area the spec singles out: over the past year, implementers spent more time on authorization than anything else. Four things changed.

Validate iss (RFC 9207)
The authorization server should include "who I am" in the callback; whenever that value appears, the client must check it against the issuer it recorded before exchanging the authorization code for a token. This closes off authorization-server mixup attacks, where your code gets tricked into being redeemed at a different authorization server.
Send application_type at registration
This fixes a pothole plenty of people have hit: when desktop and CLI tools run OAuth, the authorization server assumes a web app by default, rejects localhost callbacks, and throws a redirect_uri error.
Bind credentials to the issuer
Clients must store credentials separately per issuer and must not reuse them against a different authorization server. Change the authorization server and you re-register.
DCR retires, CIMD takes over
Connecting an app to an authorization server used to require dynamic registration (DCR). With CIMD (Client ID Metadata Documents), the app publishes its identity metadata at a public URL and the authorization server reads it directly — no registration step.

If you've ever wondered why your CLI client's OAuth flow throws a redirect_uri error, this is probably why.

Official MCP launch blog, on the application_type change
The other side

Third-party security analysis: the burden shifts to developers

All of the above is the official account. Before the spec formally shipped, a third party studied the new design as an attack surface: Akamai published an analysis in June, and SecurityWeek reported its conclusions.

Their read: the new spec genuinely removes several classes of vulnerability, while opening a few new attack surfaces — ones that depend heavily on how developers implement them.

Closed at the protocol layer
  • Session hijacking — no sessions, nothing to hijack
  • Server-initiated prompts — servers can no longer start a request; they wait to be asked
  • Stronger auth standards — the four tightenings from the previous section
Newly opened
  • Predictable state objects and tracking identifiers: hijack someone's in-flight workflow, read another agent's data, trigger cross-tenant privilege escalation
  • x-mcp-header leakage: a developer who accidentally maps an API key, token, or personal data into an HTTP header pushes that secret straight into the headers, where every load balancer, proxy, and logging system along the path can see it
  • Protocol desync: mismatched headers and body bypass header-based security controls. This is exactly what the spec's mandatory check is meant to stop — and whether it does depends on whether the server implementer took it seriously
  • MCP Apps drag the browser's old problems in: stored cross-site scripting (XSS), for one
  • Tasks is a fire-and-forget denial-of-service channel: creating a task is cheap for the client and expensive for the server. One request can spin up work that eats CPU, memory, and database storage, and the attacker disconnects immediately

With the protocol moving to a stateless model and introducing rich UI applications and asynchronous tasks, the critical security boundaries now depend entirely on how developers implement them.

Maxim Zavodchik, senior director of threat research, Akamai, via SecurityWeek

Their bottom line is that these changes amount to more than incremental improvement. The revision fundamentally reshapes where security responsibility lands: decisions the protocol used to enforce are increasingly handed to MCP server developers and platform operators.

The report adds an equally important qualifier: the MCP protocol itself has not become more fragile. What grew is the attack surface of the MCP servers built on the new spec.

What to do

So do I need to act now

After all those breaking changes, the practical question. The answer runs opposite to the "biggest breaking update ever" impression.

The one thing to remember

July 28 is not a switch. Existing clients and servers won't break today and won't break on July 28. That date is when the spec text was published, not when the old protocol gets turned off.

There are three concrete compatibility paths.

One, new clients yield to old servers automatically. A client speaking 2026-07-28 that meets a server capable only of 2025-11-25 or older falls back to the original initialize handshake, and the two keep talking. Going the other way, an old server that receives a request with no protocol-version header can treat it as 2025-03-26.

Two, major SDK versions are a separate matter. Python and TypeScript both shipped v2, each a breaking major release, and when you upgrade is your call — nothing to do with July 28. The TypeScript SDK's v1.x will keep getting bug and security fixes for at least 6 months after v2 goes final, and Python's v1.x branch continues receiving critical bug fixes and security patches.

Three, upgrading the SDK doesn't necessarily change what you say on the wire — and the four SDKs behave differently, which is easy to trip over:

TypeScript / Go
Upgrading the SDK doesn't change which revision your server speaks. To speak 2026-07-28, you opt in explicitly during wiring.
Python / C#
Upgrading gets you there: a Python v2 server answers both revisions on the same endpoint, and the C# preview's HTTP transport is stateless by default.

All four first-tier SDKs (TypeScript, Python, Go, C#) supported the new spec on day one; the Rust SDK has beta support. What actually needs work falls into the categories below — run down the list:

✅ MCP 2026-07-28 migration checklist
🧰 Quick start · MCP 2026-07-28 spec and SDKs
PriceFree, open source
Getting startedInstall the new SDK for your language. TypeScript and Go need an explicit opt-in during wiring to speak 2026-07-28; a Python v2 server answers both revisions on one endpoint; the C# preview's HTTP transport is stateless by default
Source
The 2026-07-28 SpecificationOfficial Model Context Protocol blog·blog.modelcontextprotocol.io·2026-07-28
Editorial note
The demo video is original material from the official launch blog. All diagrams — the old/new routing comparison, request structure, the five MRTR steps, the gateway comparison, the cache illustration, the three lifecycle states, and the extension stack — were drawn by us. For protocol details the spec text and changelog are authoritative; where the launch blog and the spec disagree, the spec wins. The security section draws on Akamai's analysis as reported by SecurityWeek on 2026-06-26; we were unable to obtain Akamai's original, so that material is secondhand. Performance figures from ecosystem companies are their own claims, independently unverified.