MCP's 2026-07-28 Spec: What Stateless Means for Your Server
The biggest Model Context Protocol update since launch removes sessions and the initialize handshake. What changed, what is deprecated, and a migration checklist.
Usama Munawar4 min read

What changed
The 2026-07-28 revision of the Model Context Protocol is the largest since the protocol launched. The headline: MCP is now stateless. There is no session handshake and no session id, so an MCP server behaves much more like an ordinary HTTP API and scales like one.
It replaces the 2025-11-25 revision. The four Tier-1 SDKs (TypeScript, Python, Go and C#) already support it. If you run an MCP server, or connect agents to one, this is what changes.
The big changes, in plain terms
1. No sessions
Protocol-level sessions and the Mcp-Session-Id header are gone from the Streamable HTTP transport. The list endpoints (tools/list, resources/list, prompts/list) no longer vary per connection. If your server needs state across calls, it now hands out explicit, server-minted handles that the client passes back as ordinary tool arguments. State becomes something you design on purpose, not something hidden in the connection.
2. No initialize handshake
The initialize and notifications/initialized exchange is removed. Every request carries its protocol version and client capabilities in _meta:
io.modelcontextprotocol/protocolVersionio.modelcontextprotocol/clientCapabilities
Clients should also send clientInfo, and servers should identify themselves in each result's _meta. A version mismatch returns UnsupportedProtocolVersionError.
Servers must implement a new server/discover call that advertises supported protocol versions, capabilities and identity. Clients can call it first to pick a version.
3. Multi Round-Trip Requests
Servers used to send their own requests mid-call (roots/list, sampling/createMessage, elicitation/create). That needed a live bidirectional connection, which is exactly what stateless servers do not have. The replacement is Multi Round-Trip Requests: the server returns an InputRequiredResult (resultType: "input_required") describing what it needs, and the client retries the original request with inputResponses. Every result now carries a required resultType field, "complete" for ordinary results.
4. Subscriptions and streams
resources/subscribe and the HTTP GET endpoint are replaced by a single subscriptions/listen stream, where clients opt in to specific change notifications. SSE stream resumability (Last-Event-ID) is removed: if a response stream breaks, the client re-issues the request with a new request id.
5. Caching and routing
- Results from the list and read calls must now include
ttlMs(a freshness hint) andcacheScope(publicorprivate), so clients can cache instead of polling. - Servers should return tools in a deterministic order, which also helps LLM prompt caching.
- Streamable HTTP POST requests must carry
Mcp-MethodandMcp-Nameheaders, so gateways and load balancers can route without parsing bodies.
6. Auth and schema
- Authorization servers should include the
issparameter (RFC 9207), and clients must validate it before redeeming a code. - Client credentials are bound to the issuer that created them.
- Dynamic Client Registration (RFC 7591) is deprecated in favour of Client ID Metadata Documents.
- Tool
inputSchemaandoutputSchemanow accept any JSON Schema 2020-12 keywords.
7. Extensions and deprecations
Tasks (long-running work) moved out of the core into an official extension, io.modelcontextprotocol/tasks. Roots, Sampling and Logging are deprecated, along with the old HTTP+SSE transport. Deprecated features stay for at least twelve months under the new lifecycle policy. Suggested replacements: pass paths through tool parameters instead of Roots, call your LLM provider directly instead of Sampling, and log to stderr or OpenTelemetry instead of the Logging feature.
Old and new at a glance
| Before (2025-11-25) | Now (2026-07-28) |
|---|---|
| Session id header, per-connection state | No sessions; explicit handles in tool arguments |
initialize handshake | Version and capabilities in _meta on every request |
| Server-initiated requests | Multi Round-Trip Requests (input_required) |
resources/subscribe, GET stream | subscriptions/listen |
| Experimental tasks in core | Tasks extension |
| Sampling, Roots, Logging | Deprecated |
Migration checklist for server authors
- Upgrade your SDK to a release that supports 2026-07-28, and keep answering older clients while they catch up.
- Remove session assumptions. Anything stored per connection (a cursor, a cart, an open file) needs an explicit handle that you issue and the client returns.
- Implement
server/discover. - Replace server-initiated requests with the multi round-trip pattern.
- Add
ttlMsandcacheScopeto list and read results, and make tool order deterministic. - Re-check authorization: issuer validation, the issuer-bound client credentials, and a Client ID Metadata Document if you register clients.
- Stop using Sampling, Roots and Logging in new code.
- Test with a real client, then with an old one.
What this means in practice
Stateless servers are easier to run: any instance can answer any request, so you can put them behind a normal load balancer, scale them horizontally and deploy without draining sessions. The cost is that state is now your job. For a Laravel server, laravel/mcp already speaks this revision; I walk through building one in Laravel MCP server. For agents that also use phone lines, see AI voice agents on Asterisk.
Need an MCP server built or migrated?
See AI integration, or email devusamaworks@gmail.com or WhatsApp +92 303 8004684 for a free 30-minute call.
A question about this article?Ask me on WhatsApp


