What breaks first
Start with the uncomfortable part, because the release-candidate period is over and the specification blog has now published 2026-07-28 as final. If you have an MCP server serving real traffic this week, here is what stops matching the spec.
- Your handshake. The
initializeandinitializedexchange is removed (SEP-2575 and SEP-2567). Any code path that waits for a handshake before accepting calls no longer has a handshake to wait for. - Your session identifier. The
Mcp-Session-Idheader is removed. Protocol-level sessions are gone entirely. - Anything hanging off that session. Negotiated capabilities, the client's declared protocol version, per-connection scratch state — all of it used to arrive once at handshake time and be looked up thereafter. Now the protocol version plus the client's identity and capabilities travel in
_metaon every single request. - Any flow that needed the server to call the client back mid-request. Server-initiated requests over an open stream are replaced by Multi Round-Trip Requests (SEP-2322).
- Your gateway config, in a good way. The
Mcp-MethodandMcp-NameHTTP headers are now mandatory (SEP-2243). - Roots, Sampling and Logging. All three are deprecated under a new formal deprecation policy, with a stated minimum window of "at least twelve months".
- The legacy HTTP+SSE transport. Deprecated, with what the specification blog calls "a year-long offramp".
None of that is a soft change you can shim around with a version flag and forget. It is a different transport model. The good news, and it is substantial, is that the model it moves to is the one ordinary web infrastructure was built for.
The specification blog itself acknowledges "migration cost, especially for developers that did depend on session identifiers". If your server stores anything keyed on Mcp-Session-Id, that key no longer exists and no drop-in replacement is provided at protocol level. Finding every one of those lookups is the first task in your migration, not the last.
The stateless shift, in engineering terms
To understand why this is worth the pain, look at what the old model forced on your deployment. Under a stateful, bidirectional protocol, a session began with a handshake and then meant something for the life of the connection. That has one hard consequence: the instance that handled the handshake is the only instance that knows what was negotiated. Every subsequent request from that client had to reach that same instance, or the state had to be shared.
In practice, teams solved this one of two ways, and both cost money. Either you configured sticky sessions on the load balancer, pinning each client to one server process — which means your fleet's capacity is bounded by your unluckiest instance, autoscaling responds badly, and rolling deploys drop live sessions. Or you externalised the session into Redis or an equivalent shared store, which buys you instance-independence at the cost of an extra hop on every request, an extra piece of infrastructure to run, and an extra failure mode to monitor.
The 2026-07-28 spec deletes the question. Because each request self-describes — protocol version, client identity, client capabilities, all carried in _meta — a server instance needs nothing it did not receive in the request it is currently handling. As the specification blog puts it: "Any request can now land on any server instance behind a plain round-robin load balancer without needing shared storage."
That sentence is the whole release, compressed. It means MCP servers become ordinary stateless HTTP services. It means a Cloudflare Worker, a Lambda, a Cloud Run container that scales to zero, or four pods behind a bog-standard Kubernetes Service all work without special handling. It means CDNs, API gateways and WAFs stop being awkward. If you built your first server following the patterns in our guide to building an MCP server with tools, resources and security, the shape of the code survives; what changes is everything you wrapped around it to keep sessions alive.
| Feature | Old stateful behaviour | 2026-07-28 behaviour |
|---|---|---|
| Connection setup | initialize / initialized exchange before any call |
Removed (SEP-2575, SEP-2567) — first call is a real call |
| Session identity | Mcp-Session-Id header carried per connection |
Removed — no protocol-level sessions |
| Protocol version & capabilities | Negotiated once at handshake, held server-side | Self-described on every request via _meta |
| Server asking the client something | Server-initiated request over an open bidirectional stream | Multi Round-Trip Requests (SEP-2322): input_required then retry |
| Gateway routing | Parse the JSON body to know what the request is | Mcp-Method and Mcp-Name headers mandatory (SEP-2243) |
| List responses | Re-fetched, no cache semantics | ttlMs and cacheScope on list and read results (SEP-2549) |
| Long-running work | Tasks in experimental core | Extension io.modelcontextprotocol/tasks (SEP-2663), poll-based |
| Client registration | Dynamic Client Registration | DCR deprecated in favour of Client ID Metadata Documents |
The before-and-after on the wire is easier to read than to describe. Simplified, and with most fields omitted:
# BEFORE (illustrative, simplified) — handshake, then a session-scoped call
POST /mcp
Mcp-Session-Id: 7f3a-...
{ "method": "tools/call", "params": { "name": "search_orders", "arguments": {...} } }
# the server looks up the session to know who this is and what was negotiated
# AFTER (illustrative, simplified) — one self-describing request
POST /mcp
Mcp-Method: tools/call
Mcp-Name: search_orders
{
"method": "tools/call",
"params": {
"name": "search_orders",
"arguments": {...},
"_meta": { "protocolVersion": "2026-07-28", "client": { ... capabilities ... } }
}
}
# no session lookup; any instance can serve this
Multi Round-Trip Requests: the subtle migration
Removing the open stream sounds clean until you remember what it was carrying. Any flow where the server needed something back from the client mid-call — a confirmation, a missing parameter, a human decision — depended on that bidirectional channel. Under SEP-2322 those flows become re-entrant instead.
The mechanics: the server returns a result with resultType set to "input_required", listing the requests it needs answered. The client gathers the answers and retries the original call, supplying them in inputResponses. Simplified again:
# Illustrative only — field names per the spec, structure abridged
# 1. Client calls the tool normally
{ "method": "tools/call",
"params": { "name": "refund_order", "arguments": { "order_id": "A-1183" } } }
# 2. Server cannot finish without an answer
{ "resultType": "input_required",
"requests": [ { "id": "confirm-1",
"prompt": "Refund ₹4,200 to the original payment method?" } ] }
# 3. Client re-sends the SAME call, now with answers attached
{ "method": "tools/call",
"params": { "name": "refund_order",
"arguments": { "order_id": "A-1183" },
"inputResponses": [ { "id": "confirm-1", "value": true } ] } }
This is the migration item most likely to bite, and the reason is not syntax. It is that your tool handler must now be safe to run more than once for the same logical operation. The first invocation runs, discovers it needs input, and returns. The second invocation runs the same code path again from the top, this time with answers in hand. Any side effect you performed before reaching the input check — a database write, a queued job, an outbound email, a payment authorisation — happens twice.
The discipline this demands is the discipline good tool design already asked for: validate first, check for required input before touching anything mutable, make writes idempotent behind a key the caller supplies. If you have read our guide to designing tools for AI agents with schemas, errors and retries, this is that argument arriving as a protocol requirement rather than a recommendation. The elicitation patterns we covered in sampling and elicitation for human-in-the-loop servers now sit behind the deprecation window, and Multi Round-Trip Requests are where those human checkpoints live going forward.
Before you touch transport code, audit every tool handler for side effects that occur before its input checks. Write the re-entrancy test first: call the tool, capture the input_required response, retry with answers, then assert that exactly one row was written and one job queued. That test is what tells you the migration is genuinely done.
Auth hardening, and the DCR to CIMD move
The authorisation changes are the part of this release with the least fanfare and the most real security value. The spec adds RFC 9207 issuer validation (SEP-2468), so a client can confirm which authorisation server actually issued a response rather than inferring it. It adds application_type during Dynamic Client Registration (SEP-837), letting the registration distinguish what kind of client is registering. And it adds issuer-bound client credentials (SEP-2352), so credentials are tied to the issuer that minted them instead of being freely replayable elsewhere.
Then the larger structural move: Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents. DCR let a client show up at an authorisation server and register itself on the spot. Convenient, and a persistent source of anxiety for anyone operating a server that had to decide whether to trust a registration it had never seen before. CIMD replaces the registration call with a document the client publishes and the server retrieves, which shifts identity from a runtime negotiation to a resolvable artefact.
Be honest about the cost: this is client-side work. If your integration registers itself dynamically today, migrating means publishing and hosting metadata documents, and coordinating with every authorisation server you talk to. It is worth doing — it is a real improvement to how MCP clients prove who they are, and it fits the enterprise-auth direction we tracked in the MCP 2026 roadmap — but it will not be a one-line change, and teams should schedule it as its own piece of work rather than folding it into the transport migration.
Every article here is written by a Verified Builder. Want your name on the next one?
AI Tech Connect lists AI engineers, founders and researchers across India and the UK — and the people hiring browse it to find them. Adding your profile is free.
Become a Verified Builder →The ops and cost case
Two changes in this release exist mainly to make MCP cheap to run at scale, and they are the ones a platform team will notice on the bill.
Header-based routing (SEP-2243). Making Mcp-Method and Mcp-Name mandatory means a gateway can see what a request is without parsing the JSON body. That unlocks the ordinary toolkit: route tools/call traffic to a different backend pool than tools/list, rate-limit a specific expensive tool by name, log and alert per method, and shed load at the edge instead of inside your application. Every one of those is trivial in nginx, Envoy, Kong or a Cloudflare Worker once the discriminator is in a header — and awkward to impossible when it is buried in a body the proxy must deserialise.
Cacheable list results (SEP-2549). ttlMs and cacheScope now appear on tools/list, prompts/list, resources/list and resources/read responses. Tool catalogues change rarely and are requested constantly; being able to declare a TTL and a caching scope moves that traffic to a CDN or a gateway cache rather than your origin. It also cuts the token cost of the agent loop itself, which is the same lever we examined in our piece on code execution with MCP to cut agent tokens — fewer redundant catalogue round-trips, less context spent restating what the model already knows.
For small fleets, which describes most teams in Bengaluru, Pune, Manchester and London running an MCP server for an internal agent, the sticky-session requirement was a quiet tax. You over-provisioned because you could not rebalance a pinned client, you ran a Redis you did not otherwise need, and you scheduled deploys around live sessions. On a two or three instance fleet in AWS Mumbai or a London region, that is a meaningful share of a modest monthly spend on infrastructure whose only job was keeping the protocol happy. Stateless removes the requirement entirely; you can scale to zero between agent runs, which is a different cost curve, not just a smaller number.
Sequence the migration: transport and _meta first, then re-entrancy in tool handlers, then caching and header routing at the gateway, then the DCR to CIMD move. The first two are correctness work with a hard deadline attached; the last two are optimisation you can land incrementally once you are on the new transport.
Extensions, Tasks, and what the deprecation policy signals
Tasks (SEP-2663) moved out of experimental core and into an extension, io.modelcontextprotocol/tasks, with poll-based tasks/get and a new tasks/update. Poll-based is the point: polling is what works when there is no open stream to push over, and it is consistent with the rest of the release. Long-running work is now something you kick off, poll and update — a model that will be familiar to anyone who has worked with task lifecycles in other agent-interop protocols.
More importantly, extensions become a first-class, versioned mechanism. That is a governance change disguised as a technical one. It gives the core spec permission to stay small and stable while capability lands in versioned modules that can iterate at their own pace — and it gives the maintainers a place to put ideas that are not yet ready to be load-bearing for every implementation.
The new formal deprecation policy is the other half of that maturity signal. Roots, Sampling and Logging are deprecated under it, with a stated minimum window of "at least twelve months", and the legacy HTTP+SSE transport gets what the blog calls "a year-long offramp". A protocol that publishes a deprecation policy alongside its breaking release is a protocol that expects to be depended on. That is a better position to build against than one that ships surprises.
"The teams who will migrate fastest are the ones who never really used sessions for anything except the handshake. If you kept your server boring, this release is mostly deletions."
— Rishi, Verified Builder · Bengaluru, IndiaWhat to do this quarter
Tier 1 SDK support for 2026-07-28 is available for TypeScript, Python, Go and C#, and the Rust SDK supports it in beta. The specification blog says SDK releases ship migration notes, so start there rather than reading the raw specification diff.
- Inventory your session dependencies. Grep for
Mcp-Session-Idand for anything keyed on it. That list is your migration scope. - Move version and capability reads to
_meta. Anything your server learned at handshake time now arrives per request. - Make every mutating tool handler re-entrant. Validate and check for required input before any side effect; make writes idempotent.
- Add the routing headers and set TTLs.
Mcp-MethodandMcp-Nameare mandatory;ttlMsandcacheScopeon list results are free wins. - Plan the CIMD work separately. Deprecated is not dead, and DCR migration deserves its own slice of the roadmap.
- Drop sticky sessions last. Once you are genuinely stateless, remove the pinning and the shared session store, and let the fleet rebalance.
There is a career read here too, and it is unusually clear. As of July 2026 the number of engineers who have actually migrated a production MCP server to a stateless transport is small, because the spec finalised this week. Re-entrant tool design, gateway-level MCP routing and CIMD-based client identity are all specific, checkable skills that hiring managers in Indian GCCs and UK agent studios are about to start asking about — and browser-side agent protocols only widen the surface where this knowledge pays. If you do this migration, write down what broke and what you would do differently. That write-up is worth more on a profile than another framework on a list. Our open-source coverage tracks the protocol as it moves.
Primary source: the Model Context Protocol specification blog at blog.modelcontextprotocol.io.