<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yaksh Parikh</title>
    <description>The latest articles on DEV Community by Yaksh Parikh (@yaksh_parikh_b5cf49b5dfc6).</description>
    <link>https://dev.to/yaksh_parikh_b5cf49b5dfc6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4075196%2Fbd043434-f10f-4c08-b283-f61530c9c308.png</url>
      <title>DEV Community: Yaksh Parikh</title>
      <link>https://dev.to/yaksh_parikh_b5cf49b5dfc6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yaksh_parikh_b5cf49b5dfc6"/>
    <language>en</language>
    <item>
      <title>MCP Goes Stateless: What the 2026-07-28 Spec Rewrite Means for Developers</title>
      <dc:creator>Yaksh Parikh</dc:creator>
      <pubDate>Wed, 12 Aug 2026 19:54:28 +0000</pubDate>
      <link>https://dev.to/yaksh_parikh_b5cf49b5dfc6/mcp-goes-stateless-what-the-2026-07-28-spec-rewrite-means-for-developers-43kj</link>
      <guid>https://dev.to/yaksh_parikh_b5cf49b5dfc6/mcp-goes-stateless-what-the-2026-07-28-spec-rewrite-means-for-developers-43kj</guid>
      <description>&lt;p&gt;Sessions are gone, the handshake is gone, and server-initiated requests work differently. Here is what actually changed, and what it costs to migrate.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Updated August 2026 · 10 min read · Sourced from the spec diff &amp;amp; SEPs&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sessions and the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header are gone — no sticky routing needed&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;initialize&lt;/code&gt; handshake is removed; version negotiation happens per request&lt;/li&gt;
&lt;li&gt;Server-initiated requests (sampling, elicitation, roots) become client-driven retries via Multi Round-Trip Requests&lt;/li&gt;
&lt;li&gt;SSE streams no longer resume — a broken stream means a fresh request, and repeat-safe tools are now your responsibility&lt;/li&gt;
&lt;li&gt;Roots, Sampling, and Logging are deprecated (12-month window), not removed&lt;/li&gt;
&lt;li&gt;Python SDK v2 requires FastMCP→MCPServer, snake_case fields, and pinning &lt;code&gt;mcp&amp;lt;2&lt;/code&gt; if you are not ready&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most protocol changes look simple on paper. The real question is not what changed in the spec, but how that change reshapes the systems that depend on it. With &lt;strong&gt;MCP stateless&lt;/strong&gt; design now formalized in the 2026-07-28 release, developers need to rethink how they handle context, retries, connections, and deployment.&lt;/p&gt;

&lt;p&gt;Stateful systems are hard to scale and harder to operate. Every session held in a server's memory is a liability the moment you restart a pod, route around a failed node, or scale horizontally under load. The Model Context Protocol carried that burden from its first revision — and rather than patch around it, the maintainers removed it.&lt;/p&gt;

&lt;p&gt;This is not a cosmetic API update. The &lt;strong&gt;MCP 2026-07-28 spec&lt;/strong&gt; removes protocol-level sessions, deletes the initialization handshake, replaces server-initiated requests with a new pattern, deprecates three long-standing features, and rewrites large parts of the SDK surface. Everything below is traceable to the &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;official changelog&lt;/a&gt;, a numbered Spec Enhancement Proposal, or the &lt;a href="https://py.sdk.modelcontextprotocol.io/v2/migration/" rel="noopener noreferrer"&gt;SDK migration guide&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SCOPE NOTE&lt;/strong&gt;&lt;br&gt;
This article is a technical explainer written from the &lt;a href="https://github.com/modelcontextprotocol/specification/compare/2025-11-25...2026-07-28" rel="noopener noreferrer"&gt;specification diff&lt;/a&gt;, the SEP discussions, and the official SDK migration guide. Where a described pattern is an implementation choice rather than a protocol requirement, it says so explicitly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why the MCP stateless shift matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sessions were tied to a server instance
&lt;/h3&gt;

&lt;p&gt;Under earlier revisions, a client opened a connection, ran an &lt;code&gt;initialize&lt;/code&gt; handshake, and received an &lt;code&gt;Mcp-Session-Id&lt;/code&gt;. Every subsequent request carried that ID, and the server was expected to remember the negotiated protocol version, the client's capabilities, and whatever state the connection had accumulated.&lt;/p&gt;

&lt;p&gt;That model works on a laptop. In the cloud it forces sticky routing: every request from a client must land on the same instance that holds its session.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2026-07-28 revision removes the constraint
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;REMOVED&lt;/strong&gt;&lt;br&gt;
Protocol-level sessions and the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header are gone from the &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" rel="noopener noreferrer"&gt;Streamable HTTP transport&lt;/a&gt; entirely. List endpoints — &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;resources/list&lt;/code&gt;, &lt;code&gt;prompts/list&lt;/code&gt; — no longer vary per connection. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567" rel="noopener noreferrer"&gt;SEP-2567&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The operational payoff, as the maintainers described it in the &lt;a href="https://blog.modelcontextprotocol.io/posts/sdk-betas-2026-07-28/" rel="noopener noreferrer"&gt;SDK beta announcement&lt;/a&gt;, is that an MCP server can now sit behind an ordinary round-robin load balancer — no sticky sessions, no shared session store.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tradeoff is explicit
&lt;/h3&gt;

&lt;p&gt;Statelessness does not mean state disappears. It means the &lt;em&gt;protocol&lt;/em&gt; stops managing it for you. Servers that need cross-call state now issue &lt;strong&gt;explicit, server-minted handles&lt;/strong&gt; that the client passes back as ordinary tool arguments. The state becomes visible to the model rather than hidden in a connection object.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed between stateful and stateless MCP?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;2025-11-25 and earlier&lt;/th&gt;
&lt;th&gt;2026-07-28&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connection setup&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;initialize&lt;/code&gt; handshake required&lt;/td&gt;
&lt;td&gt;No handshake; &lt;code&gt;server/discover&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Session identity&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Mcp-Session-Id&lt;/code&gt; header&lt;/td&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol version&lt;/td&gt;
&lt;td&gt;Negotiated once per connection&lt;/td&gt;
&lt;td&gt;Sent per request in &lt;code&gt;_meta&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-call state&lt;/td&gt;
&lt;td&gt;Implicit, server-held&lt;/td&gt;
&lt;td&gt;Explicit handles as tool arguments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server → client requests&lt;/td&gt;
&lt;td&gt;Direct callbacks&lt;/td&gt;
&lt;td&gt;Multi Round-Trip Requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change notifications&lt;/td&gt;
&lt;td&gt;HTTP GET stream + &lt;code&gt;resources/subscribe&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;subscriptions/listen&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream recovery&lt;/td&gt;
&lt;td&gt;SSE resumability via &lt;code&gt;Last-Event-ID&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Removed; client re-issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancing&lt;/td&gt;
&lt;td&gt;Sticky routing required&lt;/td&gt;
&lt;td&gt;Any instance serves any request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key technical changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  01 — The handshake is gone
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; / &lt;code&gt;notifications/initialized&lt;/code&gt; handshake has been removed. Every request now carries its own protocol version and client capabilities in &lt;code&gt;_meta&lt;/code&gt;, using the keys &lt;code&gt;io.modelcontextprotocol/protocolVersion&lt;/code&gt; and &lt;code&gt;io.modelcontextprotocol/clientCapabilities&lt;/code&gt;. Clients &lt;em&gt;should&lt;/em&gt; identify themselves per request via &lt;code&gt;io.modelcontextprotocol/clientInfo&lt;/code&gt;, and servers &lt;em&gt;should&lt;/em&gt; return &lt;code&gt;io.modelcontextprotocol/serverInfo&lt;/code&gt; in each result's &lt;code&gt;_meta&lt;/code&gt;. A version mismatch returns &lt;code&gt;UnsupportedProtocolVersionError&lt;/code&gt;. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575" rel="noopener noreferrer"&gt;SEP-2575&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A request under 2026-07-28:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_ticket"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Checkout returns 500"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"io.modelcontextprotocol/protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"io.modelcontextprotocol/clientInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.4.0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Illustrative shape based on the keys named in the changelog — check the published schema for the normative definition.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  02 — &lt;code&gt;server/discover&lt;/code&gt; replaces negotiation
&lt;/h3&gt;

&lt;p&gt;Servers &lt;strong&gt;must&lt;/strong&gt; implement &lt;code&gt;server/discover&lt;/code&gt;, which advertises supported protocol versions, capabilities, and identity. Clients may call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575" rel="noopener noreferrer"&gt;SEP-2575&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  03 — Server-initiated requests become Multi Round-Trip Requests
&lt;/h3&gt;

&lt;p&gt;This is the change most likely to affect application logic. Previously a server could call back to the client mid-request — for sampling, elicitation, or &lt;code&gt;roots/list&lt;/code&gt;. With no persistent back-channel, that is no longer possible.&lt;/p&gt;

&lt;p&gt;Instead, the server returns an &lt;code&gt;InputRequiredResult&lt;/code&gt; with &lt;code&gt;resultType: "input_required"&lt;/code&gt;, whose &lt;code&gt;inputRequests&lt;/code&gt; field carries what it needs. The client answers by &lt;strong&gt;retrying the original request&lt;/strong&gt; with &lt;code&gt;inputResponses&lt;/code&gt; attached. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322" rel="noopener noreferrer"&gt;SEP-2322&lt;/a&gt; · &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr" rel="noopener noreferrer"&gt;pattern docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2iqavxx8xobnkriwdw4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2iqavxx8xobnkriwdw4y.png" alt="Diagram of the multi round-trip request cycle showing the client-server retry pattern" width="800" height="401"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fig 1 — The multi round-trip request cycle. Neither leg depends on hitting the same instance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every result now carries a required &lt;code&gt;resultType&lt;/code&gt; field: &lt;code&gt;"complete"&lt;/code&gt; for ordinary results, &lt;code&gt;"input_required"&lt;/code&gt; for interim ones. Clients &lt;strong&gt;must&lt;/strong&gt; treat a missing &lt;code&gt;resultType&lt;/code&gt; from an older server as &lt;code&gt;"complete"&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  04 — Notifications move to &lt;code&gt;subscriptions/listen&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The HTTP GET endpoint and &lt;code&gt;resources/subscribe&lt;/code&gt; / &lt;code&gt;resources/unsubscribe&lt;/code&gt; are replaced by a single long-lived POST-response stream. Clients opt in to specific notification types — &lt;code&gt;toolsListChanged&lt;/code&gt;, &lt;code&gt;promptsListChanged&lt;/code&gt;, &lt;code&gt;resourcesListChanged&lt;/code&gt;, &lt;code&gt;resourceSubscriptions&lt;/code&gt; — and the server tags each notification with &lt;code&gt;io.modelcontextprotocol/subscriptionId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Request-scoped notifications such as &lt;code&gt;notifications/progress&lt;/code&gt; and &lt;code&gt;notifications/message&lt;/code&gt; continue to flow on the response stream of the request they belong to, not on the &lt;code&gt;subscriptions/listen&lt;/code&gt; stream. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575" rel="noopener noreferrer"&gt;SEP-2575&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  05 — Streams no longer resume
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;REMOVED&lt;/strong&gt;&lt;br&gt;
SSE stream resumability and message redelivery are gone — both the &lt;code&gt;Last-Event-ID&lt;/code&gt; header and SSE event IDs. &lt;strong&gt;A broken response stream loses the in-flight request, and the client must re-issue it as a new request with a new request ID.&lt;/strong&gt; &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575" rel="noopener noreferrer"&gt;SEP-2575&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This deserves emphasis because it cuts against the intuition that statelessness makes failure handling free. It does simplify &lt;em&gt;routing&lt;/em&gt; — any instance can serve the retry — but it shifts recovery responsibility to the client, and it means a re-issued request may execute a tool a second time. The spec does not define an idempotency mechanism; designing tools so that a repeat call is safe is now an application-level concern.&lt;/p&gt;
&lt;h3&gt;
  
  
  06 — Features removed and deprecated
&lt;/h3&gt;

&lt;p&gt;Removed outright: &lt;code&gt;ping&lt;/code&gt;, &lt;code&gt;logging/setLevel&lt;/code&gt;, and &lt;code&gt;notifications/roots/list_changed&lt;/code&gt;. Log level is now set per request via &lt;code&gt;io.modelcontextprotocol/logLevel&lt;/code&gt; in &lt;code&gt;_meta&lt;/code&gt;, and servers must not emit &lt;code&gt;notifications/message&lt;/code&gt; for requests that did not opt in.&lt;/p&gt;

&lt;p&gt;Deprecated — still functional, but not for new implementations. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" rel="noopener noreferrer"&gt;SEP-2577&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Suggested replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Roots&lt;/td&gt;
&lt;td&gt;Pass directories via tool parameters, resource URIs, or server config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sampling&lt;/td&gt;
&lt;td&gt;Integrate directly with the LLM provider API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging&lt;/td&gt;
&lt;td&gt;Log to stderr (stdio), or use OpenTelemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tasks moved out of the core protocol into an official &lt;a href="https://modelcontextprotocol.io/docs/extensions/overview" rel="noopener noreferrer"&gt;extension&lt;/a&gt;, &lt;code&gt;io.modelcontextprotocol/tasks&lt;/code&gt;, with polling via &lt;code&gt;tasks/get&lt;/code&gt; and a new &lt;code&gt;tasks/update&lt;/code&gt;. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663" rel="noopener noreferrer"&gt;SEP-2663&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;YOU HAVE TIME&lt;/strong&gt;&lt;br&gt;
The project adopted a &lt;a href="https://modelcontextprotocol.io/community/feature-lifecycle" rel="noopener noreferrer"&gt;feature lifecycle and deprecation policy&lt;/a&gt; defining Active, Deprecated, and Removed states with a &lt;strong&gt;minimum twelve-month deprecation window&lt;/strong&gt;. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596" rel="noopener noreferrer"&gt;SEP-2596&lt;/a&gt; That window is not, however, a compatibility guarantee across mismatched client and server revisions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  07 — Smaller changes worth knowing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;List and read results now require &lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; via a &lt;code&gt;CacheableResult&lt;/code&gt; interface, letting clients cache and reduce polling. &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549" rel="noopener noreferrer"&gt;SEP-2549&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Servers &lt;em&gt;should&lt;/em&gt; return tools from &lt;code&gt;tools/list&lt;/code&gt; in a deterministic order, to improve client caching and LLM prompt-cache hit rates.&lt;/li&gt;
&lt;li&gt;OpenTelemetry trace context propagation is documented for &lt;code&gt;_meta&lt;/code&gt; — &lt;code&gt;traceparent&lt;/code&gt;, &lt;code&gt;tracestate&lt;/code&gt;, &lt;code&gt;baggage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Resource-not-found changes from &lt;code&gt;-32002&lt;/code&gt; to &lt;code&gt;-32602&lt;/code&gt; to align with JSON-RPC.&lt;/li&gt;
&lt;li&gt;On auth: authorization servers should include &lt;code&gt;iss&lt;/code&gt; per &lt;a href="https://datatracker.ietf.org/doc/html/rfc9207" rel="noopener noreferrer"&gt;RFC 9207&lt;/a&gt; and clients must validate it &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468" rel="noopener noreferrer"&gt;SEP-2468&lt;/a&gt;; credentials must be keyed by issuer and never reused across authorization servers &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2352" rel="noopener noreferrer"&gt;SEP-2352&lt;/a&gt;; and &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;RFC 7591&lt;/a&gt; Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Architectural implications for developers
&lt;/h2&gt;

&lt;p&gt;The protocol changes above land differently depending on which layer of your system you own. These are the seven areas most likely to need a decision.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What changes for you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Session management&lt;/td&gt;
&lt;td&gt;Nothing to manage. There is no session object, no session ID, and no session lifecycle to clean up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context propagation&lt;/td&gt;
&lt;td&gt;Version, capabilities and client identity travel per request in &lt;code&gt;_meta&lt;/code&gt;, so the client library must construct that envelope on every call.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retries and recovery&lt;/td&gt;
&lt;td&gt;No resumability. A dropped stream means re-issuing with a new request ID, and the protocol defines no idempotency key — safe repeat execution is your design problem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth and authorization&lt;/td&gt;
&lt;td&gt;Validated per request. Credentials are bound to their issuer and must not be reused across authorization servers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observability&lt;/td&gt;
&lt;td&gt;Protocol logging is deprecated. Use stderr or OpenTelemetry, with trace context carried in &lt;code&gt;_meta&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State persistence&lt;/td&gt;
&lt;td&gt;Server-minted handles are the protocol-level answer. Where those handles resolve to is entirely your choice.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-instance deployment&lt;/td&gt;
&lt;td&gt;No sticky routing, no shared session store, no session migration on scale-up. Any instance can serve any request.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The through-line is that responsibility moved rather than disappeared. Each row above describes something the protocol used to do implicitly that an application now does explicitly — which is more code, but code you can see, test, and reason about.&lt;/p&gt;
&lt;h2&gt;
  
  
  What this means in code: Python SDK v2
&lt;/h2&gt;

&lt;p&gt;The protocol rewrite forced a matching SDK rewrite. The &lt;a href="https://py.sdk.modelcontextprotocol.io/v2/migration/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt; documents every breaking change; these are the ones almost every project hits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin before you migrate.&lt;/strong&gt; &lt;code&gt;pip install mcp&lt;/code&gt; now installs 2.x.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# pyproject.toml&lt;/span&gt;

&lt;span class="c"&gt;# Before&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["mcp=&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.28&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="c"&gt;# Not ready to migrate — stay on v1&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["mcp&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="c"&gt;# Migrating&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["mcp&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;FastMCP&lt;/code&gt; is now &lt;code&gt;MCPServer&lt;/code&gt;&lt;/strong&gt;, and transport parameters moved off the constructor onto &lt;code&gt;run()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (v1)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stateless_http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streamable-http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After (v2)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.mcpserver&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;streamable-http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stateless_http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fields are snake_case.&lt;/strong&gt; The JSON wire format is unchanged, but Python attribute access is not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;inputSchema&lt;/span&gt;

&lt;span class="c1"&gt;# After
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;input_schema&lt;/span&gt;

&lt;span class="c1"&gt;# If you serialize yourself, you now need by_alias
&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;by_alias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# camelCase wire format
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lowlevel handlers moved from decorators to constructor parameters.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (v1)
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@server.list_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_list_tools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# After (v2)
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_list_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ServerRequestContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PaginatedRequestParams&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ListToolsResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_list_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;handle_list_tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Other high-frequency breaks:&lt;/strong&gt; &lt;code&gt;McpError&lt;/code&gt; → &lt;code&gt;MCPError&lt;/code&gt;; &lt;code&gt;httpx&lt;/code&gt; and &lt;code&gt;httpx-sse&lt;/code&gt; replaced by &lt;code&gt;httpx2&lt;/code&gt;; &lt;code&gt;streamablehttp_client&lt;/code&gt; removed; the WebSocket transport removed; resource URIs are &lt;code&gt;str&lt;/code&gt; rather than &lt;code&gt;AnyUrl&lt;/code&gt;; and on 2026-era connections, server-initiated sampling, elicitation, and roots raise &lt;code&gt;NoBackChannelError&lt;/code&gt;. Full list in the &lt;a href="https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0" rel="noopener noreferrer"&gt;v2.0.0 release notes&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GOOD NEWS&lt;/strong&gt;&lt;br&gt;
A v2 server still answers the legacy &lt;code&gt;initialize&lt;/code&gt; handshake alongside &lt;code&gt;server/discover&lt;/code&gt;, so upgrading your server does not strand clients still on 2025-11-25.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How do I migrate to MCP 2026-07-28?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2fpo2k2damhn109pfjpi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2fpo2k2damhn109pfjpi.png" alt="7-step MCP migration checklist: pin mcp&lt;2, audit connection-scoped assumptions, move cross-call state to explicit handles, rewrite server-initiated calls as MRTR, handle retries for broken streams, apply SDK renames, test against both protocol eras" width="800" height="517"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Fig 2 — Pin first. Everything else can happen on your own schedule.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Pin first, migrate later.&lt;/strong&gt; Before the stable release, &lt;a href="https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0a1" rel="noopener noreferrer"&gt;84% of the 10,000+ PyPI packages depending on &lt;code&gt;mcp&lt;/code&gt; declared no upper bound&lt;/a&gt; — meaning a routine rebuild can pull a project onto v2 unintentionally. Add &lt;code&gt;&amp;lt;2&lt;/code&gt; to every manifest today, then migrate deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Audit connection-scoped assumptions.&lt;/strong&gt; Look for anything that assumes a request follows a previous request on the same connection: in-memory dictionaries keyed by session or connection ID, middleware attaching state to a connection rather than a request, caches scoped to a socket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Externalize what remains.&lt;/strong&gt; The spec's answer is server-minted handles passed back as tool arguments. Where that handle needs to resolve to real data, most teams will put it in a shared store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Implementation pattern, not mandated by the spec
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve_handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Resolve a server-minted handle from a shared store.

    The spec requires the handle; the storage choice is yours.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp:handle:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Rewrite server-initiated calls.&lt;/strong&gt; Any place your server called back to the client needs to become an &lt;code&gt;input_required&lt;/code&gt; result plus a client retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Design for repeat execution.&lt;/strong&gt; Since a broken stream means the client re-issues the request, mutating tools should tolerate being called twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Test against both eras.&lt;/strong&gt; Run your suite against a 2025-11-25 client and a 2026-07-28 client. The SDK supports in-process testing by passing an &lt;code&gt;MCPServer&lt;/code&gt; instance directly to &lt;code&gt;Client&lt;/code&gt;, so this needs no deployed infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assuming "stateless" means "no state."&lt;/strong&gt; It means the protocol no longer manages state. Your application still can — the difference is that the state is now explicit and visible rather than hidden in a connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing the implicit dependencies.&lt;/strong&gt; A codebase with no session object can still be stateful: per-client cached connections, in-process rate-limit counters, and module-level dictionaries all break the moment two requests land on different instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating deprecation as removal.&lt;/strong&gt; Roots, Sampling, and Logging still work through at least a twelve-month window. Do not rip them out in a panic; do stop building new features on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assuming version compatibility is automatic.&lt;/strong&gt; A 2026-07-28 server may not work with older clients, and vice versa. The &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/deprecated" rel="noopener noreferrer"&gt;deprecated features registry&lt;/a&gt; tracks exactly what is in which state — check it rather than guessing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring the ecosystem layer.&lt;/strong&gt; If your agent reaches MCP through an adapter rather than directly, the adapter is a third moving part. On &lt;code&gt;langchain-mcp-adapters&lt;/code&gt;, &lt;a href="https://github.com/dleehr" rel="noopener noreferrer"&gt;Dan Leehr&lt;/a&gt; &lt;a href="https://github.com/langchain-ai/langchain-mcp-adapters/issues/578" rel="noopener noreferrer"&gt;opened an issue&lt;/a&gt; on July 21, 2026, asking directly whether the library is being tested against the v2 SDK — as of this writing, still open. On IBM's &lt;code&gt;mcp-context-forge&lt;/code&gt;, the &lt;a href="https://github.com/IBM/mcp-context-forge/issues/5559" rel="noopener noreferrer"&gt;migration epic&lt;/a&gt; opened by &lt;a href="https://github.com/jonpspri" rel="noopener noreferrer"&gt;jonpspri&lt;/a&gt; describes the work as a major version upgrade requiring comprehensive code changes and thorough testing, scoped across nine phases with a total estimate of 11–16 weeks. Check your adapters before your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is MCP 2026-07-28 backward compatible with older clients?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Partially, and the compatibility runs in one direction more than the other. A 2026-07-28 server still answers the legacy &lt;code&gt;initialize&lt;/code&gt; handshake alongside &lt;code&gt;server/discover&lt;/code&gt;, so upgrading your server does not strand clients still on 2025-11-25. Going the other way, a client that speaks 2026-07-28 falls back to the &lt;code&gt;initialize&lt;/code&gt; handshake when it reaches an older server, so old servers and new clients keep interoperating too. What is &lt;em&gt;not&lt;/em&gt; guaranteed is behavior that depends on features removed outright — a client relying on SSE stream resumability or the old subscription model will not find it on a 2026-07-28 server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need to migrate immediately?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. The project adopted a &lt;a href="https://modelcontextprotocol.io/community/feature-lifecycle" rel="noopener noreferrer"&gt;feature lifecycle and deprecation policy&lt;/a&gt; with a &lt;strong&gt;minimum twelve-month deprecation window&lt;/strong&gt; &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596" rel="noopener noreferrer"&gt;SEP-2596&lt;/a&gt;, so Roots, Sampling, and Logging keep working during that period. The more urgent action is defensive, not migratory: pin &lt;code&gt;mcp&amp;lt;2&lt;/code&gt; in your manifest now, since &lt;code&gt;pip install mcp&lt;/code&gt; installs 2.x by default and an unpinned rebuild can pull your project onto v2 without anyone deciding to migrate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What replaces server-initiated sampling and elicitation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multi Round-Trip Requests. Previously a server could call back to the client mid-request for sampling, elicitation, or &lt;code&gt;roots/list&lt;/code&gt;. With no persistent back-channel, the server instead returns an &lt;code&gt;InputRequiredResult&lt;/code&gt; (&lt;code&gt;resultType: "input_required"&lt;/code&gt;) describing what it needs, and the client retries the original request with &lt;code&gt;inputResponses&lt;/code&gt; attached &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322" rel="noopener noreferrer"&gt;SEP-2322&lt;/a&gt;. See Fig. 1 above for the full cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does removing sessions mean my server can't keep any state?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No — it means the &lt;em&gt;protocol&lt;/em&gt; no longer manages that state for you. Servers that need cross-call state issue explicit, server-minted handles that the client passes back as ordinary tool arguments &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567" rel="noopener noreferrer"&gt;SEP-2567&lt;/a&gt;. Where that handle resolves to is entirely an implementation choice — the spec only requires the handle mechanism, not a specific storage backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens to Roots, Sampling, and Logging?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They are deprecated, not removed &lt;a href="https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" rel="noopener noreferrer"&gt;SEP-2577&lt;/a&gt;. They remain fully functional during the deprecation window, but new implementations should not build on them. The suggested replacements: pass directories via tool parameters, resource URIs, or server config instead of Roots; integrate directly with the LLM provider API instead of Sampling; and log to stderr or OpenTelemetry instead of the Logging feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will &lt;code&gt;pip install mcp&lt;/code&gt; break my existing project?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only if your dependency is unpinned. &lt;code&gt;pip install mcp&lt;/code&gt; now installs 2.x, and before the stable release, &lt;a href="https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0a1" rel="noopener noreferrer"&gt;84% of the 10,000+ PyPI packages depending on &lt;code&gt;mcp&lt;/code&gt; declared no upper bound&lt;/a&gt; — meaning a routine rebuild can pull a project onto v2 unintentionally. Add &lt;code&gt;mcp&amp;gt;=1.28,&amp;lt;2&lt;/code&gt; (or similar) to your manifest today if you are not ready to migrate; v1.x remains in maintenance mode and continues to receive security fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The 2026-07-28 revision is the largest change to MCP since launch, and the direction is coherent: push state out of the protocol, make it explicit where it survives, and let servers behave like ordinary stateless HTTP services.&lt;/p&gt;

&lt;p&gt;For most teams the practical sequence is short. Pin &lt;code&gt;mcp&amp;lt;2&lt;/code&gt; everywhere today so nothing migrates by accident. Read the &lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;changelog&lt;/a&gt; against your own server surface. Then migrate deliberately, starting with the SDK renames and finishing with the pieces that genuinely change behaviour — multi round-trip requests and stream retries.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-mcp-roadmap/" rel="noopener noreferrer"&gt;2026 roadmap&lt;/a&gt; suggests this is a foundation rather than a preview of more churn, and the new lifecycle policy exists specifically to prevent another abrupt rewrite. That makes this migration a one-time cost worth paying early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/changelog" rel="noopener noreferrer"&gt;MCP 2026-07-28 changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;Official release announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/" rel="noopener noreferrer"&gt;Release candidate post — design rationale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/specification/compare/2025-11-25...2026-07-28" rel="noopener noreferrer"&gt;Full specification diff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr" rel="noopener noreferrer"&gt;Multi Round-Trip Requests pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http" rel="noopener noreferrer"&gt;Streamable HTTP transport&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/docs/extensions/overview" rel="noopener noreferrer"&gt;Extensions overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/deprecated" rel="noopener noreferrer"&gt;Deprecated features registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk/releases/tag/v2.0.0" rel="noopener noreferrer"&gt;Python SDK v2.0.0 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://py.sdk.modelcontextprotocol.io/v2/migration/" rel="noopener noreferrer"&gt;Python SDK v1 to v2 migration guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Every protocol claim in this article is sourced from the official changelog, a numbered Spec Enhancement Proposal, or the Python SDK migration guide.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>python</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
