MCP Grows Up: What's Changing in the 2026-07-28 Spec
TL;DR
The MCP 2026-07-28 spec — final on July 28 — removes the initialize handshake and protocol-level sessions, making every request self-contained and MCP servers trivially load-balanceable. Extensions become a formal mechanism (MCP Apps, Tasks), OAuth gets six hardening SEPs, tool schemas get full JSON Schema 2020-12, and Roots, Sampling, and Logging enter a twelve-month deprecation window.
People have started calling it “MCP 2.0”, and for once the hype label fits. The release candidate for the 2026-07-28 Model Context Protocol specification is the largest revision since MCP launched, and it lands as final next week, on July 28. If you build or operate MCP servers, this one is not optional reading — it contains genuine breaking changes.
I went through the official announcement and the draft spec, and here’s my take on what actually matters.
The big one: MCP is now stateless
The headline change is architectural. The initialize/initialized handshake is gone, and so is the Mcp-Session-Id header. Every request is now self-contained: protocol version, client info, and capabilities travel in _meta on each call, and a new server/discover method replaces the upfront capability exchange when you need it.
Why should you care? Because this kills the single most annoying part of deploying MCP remotely. Until now, a horizontally scaled MCP server needed sticky sessions or a shared session store, because the instance that issued your session ID was the only one that could serve you. With 2026-07-28, any request can land on any instance behind a plain round-robin load balancer. No Redis session store, no sticky routing, no special gateway logic.
For anyone running MCP servers on ordinary infrastructure — a few containers behind a load balancer, Cloudflare Workers, whatever — this is the difference between “MCP is a weird stateful snowflake” and “MCP is just HTTP.”
But my server needs state!
Fair. The spec’s answer is refreshingly old-school: do what HTTP APIs have always done. Mint an explicit handle from a tool (basket_id, browser_id, session_token) and let the model pass it back as a regular argument on subsequent calls.
The maintainers argue this is actually better than protocol-level sessions, and I think they’re right. Hidden transport-level state was invisible to the model. An explicit handle is something the model can reason about, compose across tools, and hand off between steps. State becomes part of the conversation instead of plumbing underneath it.
Interactive flows without persistent connections
A stateless protocol still needs a way for a server to ask the user something mid-call — “delete 3 files, really?”. The old approach held an SSE stream open. The new one is Multi Round-Trip Requests: the server returns an inputRequired result containing the questions plus an opaque requestState blob. The client collects the answers and re-issues the original call with both attached. Any instance can pick up the retry, because everything needed is in the payload.
There’s also a hard new rule: servers may only initiate requests to the client while actively processing a client request. No more theoretical possibility of a server prompting the user out of nowhere — every elicitation must trace back to something the user or their agent started.
Ops-friendly details
Three smaller changes make the traffic much nicer to operate:
The Streamable HTTP transport now requires Mcp-Method and Mcp-Name headers, so your load balancer or rate limiter can route on the operation without parsing JSON bodies. List and resource responses carry ttlMs and cacheScope, giving clients real cache semantics for things like tools/list instead of relying on a live SSE stream to hear about changes. And W3C Trace Context keys (traceparent, tracestate, baggage) are now formally pinned in _meta, so a single OpenTelemetry trace can follow a call from the host app through the SDK, the server, and everything downstream.
Extensions become a real thing
Extensions technically existed before, but had no process behind them. Now they do: reverse-DNS identifiers, negotiation via an extensions capability map, dedicated ext-* repositories with their own maintainers, and independent versioning. Two official extensions ship with this release.
MCP Apps lets servers deliver interactive HTML UIs that hosts render in a sandboxed iframe. Templates are declared ahead of time so hosts can prefetch and security-review them, and everything the UI does flows through the same JSON-RPC and consent path as a normal tool call. If you’ve seen server-rendered widgets popping up in MCP clients recently, this is that, standardized.
Tasks — the long-running-work API — graduates out of the core spec into an extension, redesigned around the stateless model. A server can answer tools/call with a task handle, and the client polls with tasks/get, updates with tasks/update, cancels with tasks/cancel. Notably, the server decides when a call becomes a task, and tasks/list is gone entirely because it can’t be scoped safely without sessions. If you shipped against the experimental 2025-11-25 Tasks API, plan a migration.
Authorization hardening
Six SEPs tighten the auth spec toward real-world OAuth 2.0 / OIDC deployments. The highlights:
Clients must validate the iss parameter on authorization responses (RFC 9207) — a cheap defense against mix-up attacks, which matter more in MCP’s one-client-many-servers world than in typical OAuth setups. Future versions will reject responses without iss, so if you run an authorization server, start emitting it now.
Clients also declare their OIDC application_type during Dynamic Client Registration, fixing the classic failure where an auth server assumes “web” and rejects a CLI client’s localhost redirect URI. Credentials get bound to the issuing server’s issuer, refresh-token behavior with OIDC-style servers is finally documented, and scope step-up plus .well-known discovery are clarified.
None of this is glamorous, but it’s exactly the kind of tightening that separates a protocol people demo from a protocol enterprises deploy.
Deprecated: Roots, Sampling, Logging
Three core features enter formal deprecation:
| Feature | Use instead |
|---|---|
| Roots | Tool parameters, resource URIs, or server config |
| Sampling | Call your LLM provider’s API directly |
| Logging | stderr for stdio; OpenTelemetry for structured observability |
These are annotation-only for now — everything keeps working in this release and in every spec version published within a year, and actual removal requires its own SEP. Still, the writing is on the wall. Sampling in particular never saw broad client support, and the replacement guidance (“just call the provider”) reflects how everyone was doing it anyway.
Full JSON Schema 2020-12 for tools
Tool inputSchema and outputSchema are lifted to full JSON Schema 2020-12. That means oneOf/anyOf/allOf, conditionals, $ref and $defs — finally. Input schemas keep the object-root requirement, output schemas are unrestricted, and structuredContent can now be any JSON value, not just an object.
One sneaky breaking change hides here: the “resource not found” error code moves from the custom -32002 to the standard JSON-RPC -32602. If your client string-matches on -32002, fix it.
The meta-story: how MCP evolves from here
To me the most important part isn’t any single feature — it’s the governance work. This release introduces a formal feature lifecycle (Active → Deprecated → Removed, with a minimum twelve months between deprecation and removal), makes extensions the default path for new capabilities to stabilize before touching the core spec, and requires every Standards Track SEP to land a conformance-suite scenario before it can go Final.
Translation: this is meant to be the last big breaking release. The stateless rework needed a clean break; everything after it is supposed to arrive through deprecation windows and opt-in extensions. That’s the maturity signal I’ve been waiting for before betting serious infrastructure on MCP.
What you should do
If you maintain an MCP server or client: the RC is locked, the final spec ships July 28, 2026, and Tier 1 SDKs are expected to support it within the ten-week validation window (so by now, most already should). Check the changelog against 2025-11-25, audit for the removed handshake and session ID, migrate any experimental Tasks usage, and stop matching on error code -32002.
And if you’ve been holding off on remote MCP deployments because of the session-affinity mess — that excuse just expired.