MCP 2.0 is the biggest revision of the Model Context Protocol since its November 2024 launch. The 2026-07-28 specification removes the session layer entirely: no initialize handshake, no session ID header. Every request now carries its own protocol version and client capabilities, which means any server instance can answer any request — and your MCP server finally scales like an ordinary web service.
Key takeaways
The 2026-07-28 MCP spec is stateless: the initialize handshake and Mcp-Session-Id header are gone (SEP-2567, SEP-2575). Plain round-robin load balancing now works.
Mid-request callbacks (sampling, elicitation, roots queries) are replaced by Multi Round-Trip Requests: the server returns a result, and the client makes a follow-up call.
MCP Apps and Tasks graduated from experiments to official extensions. MCP Apps lets a server render interactive HTML inside a sandboxed iframe.
OAuth issuer (iss) validation is now mandatory, closing a class of authorization mix-up attacks. But auth must now run on every request, not once per session.
Python servers: upgrade to mcp 2.x, rename McpError to MCPError in your except clauses, and pin mcp>=1.28,<2 if you are not migrating yet.
What is MCP 2.0, exactly?
Model Context Protocol (MCP) is the open standard — introduced by Anthropic in November 2024 and now hosted by the Agentic AI Foundation under the Linux Foundation — that lets any AI application connect to tools and data through one protocol. It shipped its biggest revision as the finalized 2026-07-28 specification, and the official Python and C# SDKs both released 2.0 versions on the same day (Microsoft announced the C# SDK 2.0 on July 28; the Python SDK shipped 2.0 in parallel, per the July ecosystem coverage).
Unlike a product launch, this arrived quietly: a finalized spec and a short announcement. One well-read Medium explainer from October 2026 noted that most builders missed it for exactly that reason. The ecosystem has spent the months since catching up — and an October count cited in that coverage found servers and clients still speaking five different spec revisions at once. That fragmentation is the reason a migration guide matters now, not back in July.
Why did MCP go stateless?
The old design grew out of long-lived local connections. A client opened a conversation with an initialize handshake, received a session ID, and pinned every subsequent call to that session — and, over HTTP, typically to one specific server process. That is a reasonable model for stdio pipes on your laptop. It is a poor model for a fleet of MCP servers behind a cloud load balancer.
In the 2026-07-28 revision, the whole exchange is removed. Each request carries its own context in _meta under namespaced keys (io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities, with clientInfo recommended), and a new server/discover method lets a client fetch a server's capabilities whenever it needs them. Microsoft's App Service guidance made the same point from the operations side: the stateless pattern teams previously had to hand-roll is now how the protocol works, so scaling an MCP server looks like scaling a normal web app. Any instance can answer any call, so a plain round-robin load balancer is enough to scale horizontally.
The trade-off is where security lives. With no session, there is no "authenticate once, trust the pipe" shortcut: authorization checks must happen on every single request. OAuth issuer (iss) validation is now mandatory on servers, which closes an entire attack class — authorization mix-up, where a client is tricked into tying an authorization response to the wrong identity server. The spec's core security stance is otherwise unchanged: the host app must ask the user before calling a tool, and a tool's own description should be treated as untrusted unless the server is trusted.
What breaks when you upgrade?
These are the changes that show up as errors if you upgrade a client or server without reading the changelog:
Sessions are gone. The initialize handshake and Mcp-Session-Id header are removed. In the C# SDK, HttpServerTransportOptions.Stateless now defaults to true; legacy SSE endpoints and stateful-only options trigger compiler warnings (MCP9004 and MCP9006).
Routing headers are required. HTTP requests must send Mcp-Method and Mcp-Name headers, so gateways can route without opening the request body.
Results carry a required resultType field. This is part of the round-trip machinery described below.
Error codes moved. Resource-not-found is now the standard JSON-RPC code -32602 instead of MCP's own -32002, and protocol-specific codes were renumbered into the -32020 to -32099 range.
Removals: ping and logging/setLevel are gone, and SSE resumability is removed.
Roots, Sampling, Elicitation reworked. Servers can no longer call back to the client mid-request (see the next section).
Tasks left core. The experimental Tasks API graduated into its own official extension with a proper lifecycle and an opt-in package.
Python SDK rename: McpError became MCPError. Update your except clauses.
Auth: Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents, and clients must validate the authorization server's iss.
There is also finally a grown-up deprecation policy: you get at least twelve months' notice before anything is removed from the spec, with a 90-day floor reserved strictly for active security issues. Nothing in the 2026-07-28 cohort — Roots, Sampling, Logging, Dynamic Client Registration — can be removed before July 2027. If you are not ready to migrate this month, the safe line is mcp>=1.28,<2: that branch stays on security fixes only.
What replaces sampling, elicitation and roots?
This is the single biggest conceptual rewrite. In a stateless protocol, a server cannot hold a request open and wait — so the old pattern where the server, mid-request, sent its own requests back to the client (sampling the model, eliciting input, querying roots) had to die. Its replacement is Multi Round-Trip Requests (SEP-2322).
The idea is simple: when a server needs something from the client to finish, it returns a result carrying a resultType that describes the outstanding need. The client reads it and makes a follow-up call. Each round trip is a complete, independent request, which is what lets the rest of the protocol stay stateless.
In practice, an elicitation flow that used to be one request with an embedded callback becomes two requests:
# Request 1: tool call
POST /mcp
Mcp-Method: tools/call
Mcp-Name: book-appointment
# Server cannot ask the client a question mid-request.
# It returns: { "resultType": "elicitation-required",
# "prompt": { "requestedSchema": {...} } }
# Request 2: independent follow-up with the answer
POST /mcp
Mcp-Method: elicitation/complete
{ "appointmentTime": "2026-11-05T10:00:00Z" }
The sketch above is illustrative, not a literal wire capture, but it shows the structural change: callbacks become round trips. If your server relied on sampling or roots, this is the rewrite to plan for.
What are MCP Apps and Tasks?
Two features graduated from experiments to official extensions with namespaced identities and independent release cycles.
MCP Apps is the one to watch. A server can now send back interactive HTML that the host renders inside a sandboxed iframe. Instead of a tool call dumping a wall of text into the model's context, you get a real interface: dashboards, forms, calculators, whatever the server needs to show — rendered inline, not described in text. Tasks covers long-running jobs you check on later, with a proper lifecycle, instead of holding a connection open.
A note of caution, from the security side. MCP Apps draws UI that comes from the server, which creates a new phishing surface: fake "Allow" buttons, lookalike login boxes, plugin-drawn forms the user mistakes for the host app's own. This is being discussed in the builder community right now as the freshest tool-poisoning variant — a natural extension of the attacks we demonstrated in our October article on MCP tool poisoning, where hidden tool descriptions exfiltrated canary tokens. The spec's rule still applies: treat a tool's description as untrusted unless you trust the server. With Apps, treat its pixels that way too.
Is MCP 2.0 more secure?
It is more secure by construction in two places: mandatory OAuth issuer validation, and a spec text that finally matches how production deployments actually run. But it is also more demanding. Security that used to be established once per session must now be re-established on every request — token validation, scope checks, and consent are per-call costs. The ecosystem's fragmentation into five spec revisions at once is itself a risk: a client and server negotiating across revisions can silently fall back to older behaviour.
What is unknown: how hosts will handle the MCP Apps phishing surface in practice, and whether the per-request auth overhead changes the economics of high-throughput agent fleets. Those answers will come from deployments over the next year, not from the spec. Our earlier guides — hardening an MCP setup for AI coding assistants, and the tool-poisoning demonstration — still apply; 2.0 changes the plumbing, not the threat model.
How do I migrate a Python server today?
A minimal 2.0 server with the official Python SDK looks like this (FastMCP defaults to the new behaviour):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
def get_forecast(city: str) -> str:
"""Current weather for a city. Replace with a real API call."""
return f"Sunny, 21 C in {city}"
if __name__ == "__main__":
# Streamable HTTP, stateless by default in 2.0:
# any instance can serve any request behind your load balancer.
mcp.run(transport="streamable-http")
And the migration checklist for an existing server:
Upgrade the package: pip install -U "mcp>=2". If you are not migrating yet, pin mcp>=1.28,<2 for security fixes.
Rename the exception:
# MCP 1.x
try:
...
except McpError as e: # old name
...
# MCP 2.0
except MCPError as e: # new name
...
Delete session logic: remove your initialize handling and any session-ID tracking. Add a server/discover path if clients need capability lookup.
Send routing headers: ensure every HTTP request carries Mcp-Method and Mcp-Name.
Rework sampling/elicitation/roots: move these to the round-trip pattern — return a result with resultType, and let the client follow up.
Fix error codes: map missing resources to -32602, and check any code that parsed the old -32002.
Move Tasks to the extension package if you used the experimental Tasks API.
Validate iss on OAuth tokens — now mandatory, and it is the change that closes a real attack class.
Mark July 2027 on the calendar: that is when the deprecation clock runs out on the old cohort. The protocol finally behaves like standard web traffic — stateless requests, routable headers, cacheable results. The migration is mostly mechanical, and the payoff is a server that scales behind any load balancer you already know how to operate.
Originally published on LogicStack. Run the code examples right in your browser with our free interactive Python and SQL playgrounds — no setup, no sign-up.
Top comments (0)