Protocols · October 18, 2026 · 9 min read

MCP drops sessions: what the 2026-07-28 revision changes for agent builders

The newest Model Context Protocol revision removes the initialize handshake, protocol sessions, and server-initiated requests. Here is what that means for the servers you host, the clients you build, and the migration path in between.

If you built a Model Context Protocol server or client in 2025, the version you learned has a beginning, a middle, and an end. The client opens a connection, sends initialize, gets back a session, and then trades messages inside that session until someone hangs up. The current revision of the spec, dated 2026-07-28, throws most of that out. There is no handshake, no protocol-level session, and no channel for a server to send its own requests to the client. Every request now stands on its own. This article walks through what changed, why it matters if you run MCP servers on serverless platforms or behind load balancers, and how to move an existing project across without stranding the clients that still speak the older protocol.

What the old shape looked like

It helps to remember what Streamable HTTP did before. Under the 2025-11-25 transport spec, a server could assign a session ID in an MCP-Session-Id header on its reply to initialize, and the client then had to send that header on every later request. Clients could open a standing stream with HTTP GET to hear from the server at any time, servers could push JSON-RPC requests down those streams, and a dropped stream could be resumed by sending Last-Event-ID. A client that was done was expected to send HTTP DELETE to end its session.

All of that works, but it assumes the same server process remembers you between requests. That is easy when one process serves one desktop app over stdio. It gets awkward when your server is a function that spins up per request, or a fleet of containers behind a load balancer that does not care which one you reached last time. Somebody has to store the session, or every request has to land on the same box.

The headline change: every request carries its own context

The 2026-07-28 changelog lists the break plainly. It removes protocol-level sessions and the Mcp-Session-Id header from Streamable HTTP, and it removes the initialize and notifications/initialized handshake. Instead, every request carries its protocol version and client capabilities in its _meta field, under keys such as io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities. Clients should identify themselves on each request with io.modelcontextprotocol/clientInfo, and servers should identify themselves in each result.

The versioning page sums up the new model in one line: there is no negotiation handshake, and the server accepts or rejects each request on its own. If a client asks for a version the server does not implement, the server answers with an UnsupportedProtocolVersionError (code -32022) whose data lists the versions it does support, and the client should pick one from that list and try again. A client that wants to know up front can call server/discover, a new method every server must implement, which returns the supported versions, the capabilities, and the server's identity. The discovery page is careful to say clients may call it first but do not have to.

On the wire, a tool call to a modern HTTP server looks like this, adapted from the Streamable HTTP spec:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"get_weather","arguments":{"location":"Seattle, WA"},
  "_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
   "io.modelcontextprotocol/clientInfo":{"name":"ExampleClient","version":"1.0.0"},
   "io.modelcontextprotocol/clientCapabilities":{}}}}

Notice the new headers. The Mcp-Method header is required on every request, and Mcp-Name is required on tools/call, resources/read, and prompts/get. They exist so gateways, load balancers, and logging tools can route and inspect traffic without parsing the JSON body. Because a mismatch between header and body would let one component act on one value while another acts on a different one, servers must reject any request whose headers disagree with its body, returning 400 with a HeaderMismatch error (code -32020). Servers can also mark individual tool parameters with x-mcp-header so their values get mirrored into Mcp-Param-* headers, which the spec illustrates with a SQL tool whose region argument becomes Mcp-Param-Region: us-west1. If you ever wanted to route requests by tenant or region at the edge, this is the hook.

Servers no longer call clients; they ask and wait for a retry

The older protocol let a server interrupt a tool call to ask the client for something: an LLM completion through sampling, a form filled in by the user through elicitation, or the list of roots the client was willing to share. Those were server-initiated JSON-RPC requests, which is exactly the kind of traffic that needs a standing connection.

The new Multi Round-Trip Requests pattern replaces them, and the spec labels it a breaking change. When a server needs more input, it finishes the current request with an InputRequiredResult whose resultType is "input_required" and whose inputRequests map holds what it needs, such as an elicitation/create asking for a GitHub username. The client gathers the answers and sends the original request again, with a new request ID, the same parameters, and an inputResponses map keyed to match. The spec's stated goal is to handle these exchanges without a shared storage layer across server instances or stateful load balancing.

If the server needs to remember where it was, it can hand the client an opaque requestState string, and the client must echo it back without inspecting or changing it. This is where a careless server could get hurt, and the spec is blunt about it. Servers must treat requestState as attacker-controlled input. If it influences authorization, resource access, or business logic, servers must protect its integrity with something like an HMAC or authenticated encryption and reject anything that fails verification. To limit replay, the spec says servers should bind the state to the authenticated user, give it a short expiry, and tie it to the originating method and a digest of its key parameters. It also warns that those measures do not make state single-use, so anything like a one-time redemption still needs a server-side check. In practice that means a signed, short-lived token, the same discipline you would apply to a password-reset link.

Change notifications get one opt-in stream

Long-lived streams did not disappear entirely; they just became explicit. The HTTP GET endpoint and the old resources/subscribe method are replaced by subscriptions/listen, a single POST whose response is an SSE stream that stays open. The client passes a filter naming the notification types it wants, such as toolsListChanged or a list of resource URIs, and the server must not send types the client did not ask for. Progress updates and log messages tied to a specific request still travel on that request's own response stream, not on the listen stream.

Two related changes are worth knowing about. Stream resumability is gone: the changelog says a broken response stream loses the in-flight request, and the client must send it again as a new request with a new ID. That makes the idempotency advice in our guide to handling tool errors in agent loops more important, not less, because a dropped connection mid-write now always means a retry. Cancellation got simpler at the same time. On Streamable HTTP, closing a request's response stream is itself the cancellation signal, and the server must treat it that way, while stdio still uses a notifications/cancelled message.

Caching hints, and a nod to prompt caching

Without sessions, clients need another way to avoid refetching the tool list on every turn. The new caching rules require servers to attach a ttlMs freshness hint and a cacheScope of "public" or "private" to complete results from server/discover, the list methods, and resources/read. A ttlMs of zero means the result is immediately stale, and the semantics are modeled on HTTP's max-age. Results produced by a multi round-trip retry must never be cached, since they depend on inputs outside the cache key.

The changelog also adds a small recommendation that pays off directly in your model bill: servers should return tools from tools/list in a deterministic order, to enable client-side caching and improve LLM prompt cache hit rates. Tool definitions sit near the top of every prompt, so if your server shuffles them, every turn can miss the provider's prompt cache. We covered why that matters in the real cost of tool calling.

Living with two eras at once

The spec calls 2026-07-28 and later the "modern" era and anything with an initialize handshake, through 2025-11-25, the "legacy" era. Its compatibility matrix is the most practical part of the revision. A modern client talking to a legacy-only server fails, and so does a legacy client talking to a modern-only server, because legacy clients have no way to move forward. What works is "dual-era" code on at least one side. A dual-era client on HTTP sends a modern request first and, if it gets a 400 without a recognized modern error in the body, falls back to initialize. On stdio it probes with server/discover and falls back on anything that is not a recognized modern answer. A dual-era server picks its behavior from how each client opens: modern _meta means stateless handling, while initialize selects legacy semantics for that process or session.

For anyone hosting a public server, the safe reading is to serve both eras for now. You cannot control which clients your users run, and the legacy clients already in the wild will not learn the new protocol on their own.

What this looks like in the TypeScript SDK

The official TypeScript SDK shows how much of this is opt-in. Its guide to supporting revision 2026-07-28 states that nothing in its v2 packages puts a 2026-07-28 byte on the wire by default. A client keeps doing the 2025 handshake unless you pass versionNegotiation with mode: 'auto', which probes with server/discover and falls back for older servers, or a pin to the new version, which refuses to fall back. On the server side, createMcpHandler(factory) serves the new revision per request and, by default, also serves 2025-era traffic statelessly from the same endpoint. For stdio, the guide says to replace server.connect(new StdioServerTransport()) with serveStdio(() => buildServer()).

That distinction has already tripped people up. An open issue on the SDK repository reports that following the v1-to-v2 upgrade guide and its codemod exactly produces a server that compiles, passes tests, and answers the Inspector, yet only speaks the 2025 era, because the codemod keeps the old connect call. The reporter's point is that the result looks finished and is silently legacy-only. If you upgraded recently, check which entry point your server actually uses.

A migration path, step by step

Start by finding out what your server speaks today: send it a server/discover request with a 2026-07-28 _meta block and see whether you get a discovery result or an error. If you rely on Mcp-Session-Id to remember things between calls, move that state into explicit handles your tools return and accept as ordinary arguments, which is what the changelog recommends, or into a signed requestState if it belongs to a single multi-step request. If your tools use sampling or elicitation, rewrite those paths to return input_required and expect a retry, and treat roots, sampling, and logging as features to avoid in new code, since this revision deprecates all three. Return your tool list in a stable order with a sensible ttlMs. Finally, keep serving the legacy era alongside the modern one until your own logs show that the clients you care about have moved.

None of this changes what MCP is for. If you are new to the protocol, our plain-English guide to MCP servers still covers the basics. What changes is the plumbing, and the new plumbing is far friendlier to the way most remote servers are actually deployed: many small, forgetful processes behind a URL, each able to answer any request it receives.

Sources

MCP 2026-07-28 key changes, Streamable HTTP (2026-07-28), Versioning and compatibility, Multi Round-Trip Requests, Discovery, Subscriptions, Caching, Transports (2025-11-25), TypeScript SDK: supporting 2026-07-28, and TypeScript SDK issue 2977.

Newsletter

The daily agent toolkit.

New tools, MCP servers and frameworks, in one short email every morning. Free, no spam. Sign up and get the free AI agent cost cheat sheet (PDF).