Migrating MCP Servers to Spec 2026-07-28: initialize Gone, server/discover Required
MCP 2026-07-28 breaks backward compatibility: the initialize handshake is removed, server/discover is mandatory, and subscriptions/listen replaces resources/subscribe. Migration guide with the Python SDK.
The Model Context Protocol (MCP) is the open standard through which AI clients — Claude, IDEs, agent frameworks — access external tools, resources, and prompts. On July 28, 2026, the MCP working group released the new stable revision 2026-07-28, replacing the previous version 2025-11-25. The changes are breaking: existing servers must be migrated. Clients speaking the new revision return the JSON-RPC error -32022 UnsupportedProtocolVersion on a version mismatch.
This article covers the most important breaking changes and a minimal migration path for a typical Python server implementation.
Why the Handshake Is Gone
The old initialize handshake was stateful: it tied an HTTP session ID to negotiated capabilities. This makes horizontal scaling painful — a load balancer would need to pin all requests in a session to the same instance. The new revision makes the core fully stateless: every request carries the protocol version and capabilities directly in its _meta field. A load balancer can distribute requests freely, with no session affinity required.
The Three Most Important Breaking Changes
1. initialize and notifications/initialized Are Removed
Previously, every MCP session started with a two-step handshake:
Client --> Server: initialize { protocolVersion, capabilities }
Server --> Client: InitializeResult
Client --> Server: notifications/initialized
In 2026-07-28, both methods and the Mcp-Session-Id header are gone entirely. Instead, the client sends the necessary metadata in the _meta field of every request's params:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"roots": false,
"sampling": false
},
"io.modelcontextprotocol/clientInfo": { "name": "my-client", "version": "1.0" }
}
}
}
For HTTP transports, two new required headers are added to every POST: Mcp-Method and Mcp-Name. The Mcp-Session-Id header is removed.
Server code that stores capabilities in an initialize handler and later reads them from session context must be updated: capabilities now come from params._meta and are scoped to the individual request.
2. server/discover Is Mandatory
Every server MUST implement server/discover — the only endpoint a client may call without a prior handshake:
{ "jsonrpc": "2.0", "id": 0, "method": "server/discover" }
The response contains protocolVersion, capabilities, and serverInfo. Clients that want to check the protocol version first call server/discover before any real request. If the method is missing, the server returns MethodNotFound and many clients will drop the connection.
3. subscriptions/listen Replaces resources/subscribe
resources/subscribe, resources/unsubscribe, and the HTTP GET endpoint for SSE streams have been removed. In their place is a single long-running request subscriptions/listen, through which the client subscribes to multiple event types at once:
{
"jsonrpc": "2.0",
"id": 2,
"method": "subscriptions/listen",
"params": {
"subscriptions": [
"toolsListChanged",
"resourcesListChanged",
"resourceSubscriptions"
],
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
}
}
}
SSE resume via Last-Event-ID is also gone. If a stream breaks, it cannot be resumed — the client must issue a new request with a new request ID.
Further Changes at a Glance
| Area | Old | New |
|---|---|---|
| Ping | ping method |
removed |
| Roots change notify | notifications/roots/list_changed |
removed |
| Set log level | logging/setLevel |
_meta field io.modelcontextprotocol/logLevel |
| Tasks (experimental) | in core protocol | Extension io.modelcontextprotocol/tasks |
| Result type | — | resultType: "complete" / "input_required" required field |
| List caching | — | ttlMs, cacheScope required fields on list responses |
| Roots, Sampling, Logging | active | deprecated (12-month transition window) |
On Roots, Sampling, and Logging: These features are formally deprecated but remain functional for at least twelve months. The spec recommends tool parameters or resource URIs as replacements for Roots; Sampling should move to direct LLM provider API calls; Logging to stderr (stdio) or OpenTelemetry.
Migrating with the Python SDK
The official Python SDK mcp 2.0.0 (stable, released July 28, 2026) fully supports the new spec:
pip install "mcp>=2.0.0"
If you have been using FastMCP from mcp 1.x, you switch to MCPServer. The stdio transport is started via an instance method:
from mcp.server import MCPServer
server = MCPServer("my-server", version="1.0.0")
@server.tool()
async def greeting(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
server.run() # starts the stdio transport
For HTTP deployments, streamable_http_app() returns an ASGI-compatible object that can be passed directly to uvicorn:
import uvicorn
from mcp.server import MCPServer
server = MCPServer("my-server", version="1.0.0")
# @server.tool() ... registrations here
app = server.streamable_http_app()
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
mcp 1.x is now in maintenance mode (security patches only). The TypeScript SDK v2 is also available; find the migration guide in the official repository (link below).
Three Common Pitfalls
1. server/discover missing in manual implementations. Anyone building the JSON-RPC layer by hand rather than using an SDK must register server/discover explicitly. The SDK includes it automatically; those working without an SDK often forget it — and clients then abort before the first real call.
2. Keeping session state from initialize. Code that stores capabilities or version information in a session dictionary inside an initialize handler will read stale or empty data on the next request. Capabilities now come exclusively from params._meta and are scoped to the current request only.
3. Proxy configuration for new HTTP headers. Reverse proxies such as nginx or HAProxy that filter headers or use an allowlist must be updated to pass through Mcp-Method and Mcp-Name. If those headers are missing when they reach the server, it responds with HeaderMismatch (JSON-RPC code -32020), which can look like a generic connection error in client logs.
Next Steps
The full changelog with all changes is at modelcontextprotocol.io/specification/2026-07-28/changelog. The Python SDK migration documentation is on github.com/modelcontextprotocol/python-sdk, and the TypeScript migration guide is at github.com/modelcontextprotocol/typescript-sdk. The complete specification for all MCP versions is at spec.modelcontextprotocol.io.
Note: The articles on this blog are produced with the help of AI and are editorially reviewed before publication. Editorial responsibility lies with Emre Yurtbay (see the Impressum).