MCP's 2026-07-28 spec broke my tool-calling setup
Model Context Protocol's 2026-07-28 spec ships a stateless core, drops Roots, Sampling, and Logging, and hardens auth to OAuth 2.0 — what to migrate first.
The 2026-07-28 Model Context Protocol specification is the first MCP release with real breaking changes — a stateless core, three deprecated features, and an authorization model that assumes OAuth 2.0 from the start. It followed a ten-week release-candidate window that closed in May, long enough for SDK maintainers to validate it against real workloads before it shipped. If you wired up tool-calling by hand before MCP existed — which is what I did for the AI lead-gen agent I built at SalesRook — this is the point where "eventually migrate" turns into an actual decision.

Stateless core: what breaks in a running agent
The biggest structural change is that MCP dropped its session-oriented model for a stateless core built on Multi Round-Trip Requests and header-based routing, with cacheable list results. If your client leaned on the server remembering anything between calls — which tool list was already sent, what context a long-lived connection had accumulated — that assumption is gone. State now has to live in the request itself or in your own application layer, not in the protocol.
This is the one change that would have hurt if I'd built against MCP's session model in the first place. I didn't — I never had MCP, so tool state already lived in my own dispatch table and the model's context, not in a protocol-level session. That wasn't a deliberate bet on where the spec would land; it's just where a hand-rolled implementation ends up by default. Worth knowing before you migrate: if your MCP client currently depends on session persistence, that's the first thing to rip out, not the auth changes.
What the wire format actually looks like
Concretely, two things disappear and one thing takes their place. The initialize / notifications/initialized handshake is gone, and so is the Mcp-Session-Id header that Streamable HTTP used to hand out and expect back on every subsequent call. In their place, every request now carries its own protocol version and capabilities inline, in _meta:
POST /mcp HTTP/1.1
Mcp-Method: tools/call
Mcp-Name: send_followup_email
Content-Type: application/json
{
"method": "tools/call",
"params": {
"name": "send_followup_email",
"arguments": { "lead_id": "8841", "template": "warm-intro" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": { "elicitation": true },
"io.modelcontextprotocol/clientInfo": { "name": "salesrook-agent", "version": "3.2.0" }
}
}
}No Mcp-Session-Id to mint, store, or expire — a request from any client instance is now servable by any server instance behind a plain load balancer, because nothing about how to answer it lives outside the request itself. Two required headers come along for the ride: Mcp-Method and Mcp-Name, which exist so an ordinary HTTP proxy or API gateway can route and log MCP traffic without parsing the JSON-RPC body. If a tool needs a custom header of its own — an idempotency key, a tenant ID — it declares that in its input schema and the client sends it via x-mcp-header, rather than the two sides agreeing on a side channel out of band.
If you're running a hand-rolled client, this is genuinely closer to how you'd have built HTTP tool-calling anyway — self-describing requests, no server-side session table to garbage-collect. It's the MCP clients that leaned hardest on the old session model that have real rework here, not the ones that never had one.
Multi Round-Trip Requests: the real replacement for server-initiated calls

The part of the spec I initially skimmed past was Multi Round-Trip Requests (MRTR), and it turns out to be the mechanism that actually replaces Roots, Sampling, and interactive elicitation — not just a footnote next to the deprecation table below. The old protocol let a server reach back over an open connection and ask the client for something mid-call: roots/list, sampling/createMessage, an elicitation prompt. That only works if there's a connection to reach back over, and there no longer is one.
MRTR replaces the server-initiated push with a retry pattern. Instead of asking mid-call, a server that needs more information returns a result with resultType: "input_required" and an inputRequests field describing exactly what it's missing. The client resolves that on its own terms — calling the model directly, prompting a person, reading a config value — and then retries the original request, this time with inputResponses attached. Every ordinary result now carries a resultType too ("complete" for the normal case), so a client can tell the difference between "here's your answer" and "ask me again with this."
This is a cleaner shape than the old one, honestly. A retried request is just an HTTP request — it survives a dropped connection, a different pod picking it up, a client that wants to log the whole exchange as two ordinary request/response pairs instead of a stateful conversation. It also explains why Roots and Sampling could be deprecated outright rather than replaced in kind: the server-initiated call they depended on is gone as a protocol primitive, not just discouraged.
Roots, Sampling, and Logging are gone
Three protocol features got deprecated outright, each replaced by something outside the protocol:
| Old MCP feature | Replaced by |
|---|---|
| Roots | Tool parameters — the client passes scope/context directly as arguments |
| Sampling | Direct provider API calls — the server calls the model itself |
| Logging | stderr / OpenTelemetry — structured observability instead of protocol messages |
In practice, Roots and Sampling were rarely-implemented optional features to begin with — most tool-calling setups, hand-rolled or not, never used them. Logging is the one that actually matters if you were piping protocol-level log messages into anything. Moving it to OpenTelemetry is the right call — it means MCP traces can finally live in the same observability pipeline as everything else instead of a protocol-specific side channel — but it's a real rewire if you had something depending on the old message format.
Caching gets a real contract, not just a suggestion
Something that's easy to miss next to the bigger stateless story: tools/list, resources/list, prompts/list, resources/read, and resources/templates/list now return a required ttlMs and cacheScope alongside their results. ttlMs is a plain freshness hint in milliseconds — how long a client can reuse this without asking again. cacheScope is either "public" or "private", and controls whether a shared intermediary (a gateway, a proxy) is allowed to cache the response for more than one caller.
Combine that with servers now being expected to return tools/list in a deterministic order, and the practical effect is that a well-behaved MCP client can stop re-fetching the tool catalog on every call and start treating it like any other cacheable HTTP resource — which also means a stable ordering for whatever you're using for prompt caching on the model side. Small change, but it's the kind of thing that shows up as a real latency number once you measure it, not just a spec bullet point.
Auth now assumes OAuth 2.0
Six Spec Enhancement Proposals hardened the authorization spec to align with OAuth 2.0 and OpenID Connect deployment practices. The spec itself doesn't hand you a finished implementation — it tightens what a compliant one has to do. If your current tool auth is an API key pasted into a header, that's the piece to replace first; it's the part furthest from where the spec is now pointing, and the part most likely to fail a security review of anything calling itself MCP-compliant six months from now.

Two of those hardening changes are worth naming specifically, because they show up as actual code rather than policy language. The first is resource indicators (RFC 8707): a client now tells the authorization server up front which MCP server the token is for, and the token comes back scoped to that resource. That closes the "confused deputy" problem, where a token issued for one server gets replayed against a different one it was never meant to talk to. The second is issuer verification (RFC 9207): the authorization server includes an iss parameter on the redirect back to the client, and the client is required to check it against the issuer it originally recorded before it redeems the code. Skip that check and you've reopened the exact redirect-based hijack the parameter exists to close.
There's also a smaller, easy-to-miss change to client registration: Dynamic Client Registration (RFC 7591) is now deprecated in favor of Client ID Metadata Documents — a client publishes its own metadata at a URL instead of registering itself individually with every authorization server it talks to. It still works for backward compatibility, but it's not the pattern to build new clients against.
None of this is exotic if you've built OAuth flows before. It's exotic if your current "auth" is a static API key sitting in an environment variable, which — for a lot of hand-rolled tool-calling setups, mine included until recently — it was.
Migrate now, or wait?
My actual answer, not a hedge: it depends on which side of the protocol you're on.
- Building a new integration today — adopt the 2026-07-28 spec directly. There's no reason to build against a session model that's already deprecated. Beta SDKs are out for Python, TypeScript, Go, and C#.
- Already running hand-rolled tool-calling in production, with retries, timeouts, and guardrails already solid — there's no urgency to rip that out for MCP compliance alone. The spec changing doesn't make your existing setup wrong.
- The actual trigger to migrate isn't architectural purity, it's compatibility — the moment you need to talk to a third-party MCP server that only speaks the new spec. At that point you're migrating on their timeline, not yours, so it's worth having the Roots/Sampling/Logging replacements mapped out in advance rather than discovering them mid-integration.
Before migrating anything, check four things: your SDK version actually targets 2026-07-28 and not the release candidate, since the wire format has real differences between the two; your auth flow assumes a short-lived, resource-scoped token rather than a static key; nothing in your integration still calls Roots, Sampling, or Logging directly, even though they'll keep working for now; and if you're relying on Mcp-Session-Id anywhere in your own code — logging, rate limiting, debugging — that's dead the moment you upgrade.
What's next
I haven't run the 2026-07-28 SDK against the SalesRook agent yet — that's the natural follow-up once I've done it for real, with actual before/after numbers instead of spec-reading. Top of the list: whether the stateless core changes latency or cost in either direction, and whether MRTR's retry-the-whole-request pattern gets expensive on tool calls with large payloads attached. If either moves the needle, that's worth its own post.