Designing with AI

Designing with AI

MCP For Software Engineers

MCP July 2026 Update: Stateless Core, Extensions, and Tasks

Issue #67 | All you need to know about the updated MCP spec - stateless core, extensions, task runtime, current SDK support and integration considerations.

Victor Dibia, PhD's avatar
Victor Dibia, PhD
Aug 10, 2026
∙ Paid
The July 2026 MCP spec update introduces a stateless core, an extensions framework,

In March 2025 I wrote a post titled No, MCPs Have Not Won (Yet). I wrote it in response to usability and implementation gaps I hit while trying to integrate MCP into AutoGen - an agent framework I was building heavily at the time. The reference MCP servers at the time were limited, the documentation assumed servers running in Claude Desktop or similar, and the moment I tried to bundle everything into a real, deployed agent, there were clear friction points.

MCP has changed between then and now.

In particular, fast forward to July 28, 2026, there’s a fifth revision of the spec - this post reviews the key updates.TL;DR

The key updates in the 2026-07-28 spec:

  • Stateless core. MCP servers are now ordinary HTTP services - any request can hit any instance, so you can load-balance, scale, and run them serverless without sticky sessions. The initialize handshake and protocol-level sessions are gone; every request carries its own version and capabilities.

  • Extensions framework. A thin core plus optional, opt-in extensions, each negotiated per request and governed by a proposal process. Three are official today: Tasks, MCP Apps, and Enterprise-Managed Authorization.

  • Tasks. A first-class extension for long-running work: the server returns a durable handle and the client polls, instead of holding a connection open.

  • Multi Round-Trip Requests (MRTR). Mid-call input, like asking the user to confirm something during a tool call, without keeping a connection open.

  • Auth hardening. A proper OAuth resource-server model, and a move away from dynamic client registration.

  • Deprecations. Sampling, Roots, Logging, and the legacy HTTP+SSE transport are on a deprecation path.

  • SDK Parity. Today only the C# sdk have all updates implemented with others in progress.

This post is builds on concepts from the Designing Multi-Agent Systems book. Chapter 12 covers MCP and agent communication in more depth, with complete implementation code.

Stateless core

The 2025 protocol opened every connection with an initialize handshake and carried a session, identified by an Mcp-Session-Id header. Because that session lived on one instance, every request in a conversation had to be routed back to it (sticky routing). A side effect of this this is that you could not spread traffic freely across a pool of servers. A restart could lose the session state, and both ends carried the complexity of setting it up and tearing it down. A call looked like this:

POST /mcp
Mcp-Session-Id: 3f9a1c...
Content-Type: application/json

{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "render_report", "arguments": {"title": "Q3", "sections": 8}}}

The 2026 spec removes the session. There is no initialize handshake. Every request is self-contained: it carries its own protocol version and client capabilities in _meta, and surfaces the method and target as headers so a gateway can route it without reading the body.

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

{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "render_report", "arguments": {"title": "Q3", "sections": 8},
   "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
             "io.modelcontextprotocol/clientCapabilities": {}}}}

Because nothing is held between calls, any request can land on any instance. Servers expose a server/discover method to advertise what they support, and clients opt in to change notifications through a single subscriptions/listen stream. Netlify’s Sean Roberts summarized the practical effect: the stateless core makes MCP “a first-class HTTP workload with no session management to work around.” As of writing, stateless transport ships in the Python and TypeScript SDKs (both v2.0), and in the Go, C#, and Rust SDKs.

Extensions

Rather than growing the core protocol every time a new capability is needed, MCP now has a thin core plus a set of optional, opt-in extensions. Each is identified by a reverse-DNS name and negotiated per request: the client declares which extensions it understands, and the server only uses one if the client asked for it.

from mcp.server.mcpserver import MCPServer
from mcp.server.apps import Apps   # an extension (interactive UI)

mcp = MCPServer("reports", extensions=[Apps()])

So far, there are official extensions as at time of writing: Tasks, MCP Apps (interactive UI rendered inline in a conversation), and Enterprise-Managed Authorization (zero-touch OAuth through an organization's identity provider, starting with Okta). New capabilities can be proposed as extensions can will go through a Specification Enhancement Proposal

While the idea of extensions provides surface for MCP to evolve .. I think it also comes with a real tradeoff. A protocol's appeal/promise is that anything conformant just works. However, once behavior lives in extensions, "supports MCP" only guarantees the core, and everything else is negotiated (weakens the promise). IMO, for things to work well, three current practices may make this all still worthwhile: the core stays mandatory and functional, so a client that does not understand an extension degrades to plain behavior instead of breaking; negotiation makes any gap explicit; and official extensions are reviewed.
It will be interesting to see how all of this plays out.

Tasks

Tasks is the change I care about most. In Part 2 I showed how to build long-running and interactive tools by combining progress notifications, cancellation, and server-initiated requests supported in previous versions of the MCP spec. It worked, but it was a set of primitives you had to assemble yourself, and it depended on holding a connection open for the life of the operation. On a stateless, load-balanced server, that assumption no longer holds.

Tasks replaces the assembly with one model. When a client that declared the Tasks extension calls a slow tool, the server can answer with a durable task handle instead of blocking. The client then polls for the result:

→ tools/call render_report {title: "Q3", sections: 8}
← CreateTaskResult   { taskId: "t_abc", status: "working", pollIntervalMs: 2000 }
→ tasks/get { taskId: "t_abc" }        (repeat, honoring pollIntervalMs)
← { status: "working" }
→ tasks/get { taskId: "t_abc" }
← { status: "completed", result: { content: [{type: "text", text: "# Q3 ..."}] } }

The task handle is durable, and the client drives the polling, so the work survives a disconnect and does not depend on any one server instance. In the Python SDK the server side is just the tool plus the Tasks extension, and on the client side a normal call_tool hides the polling behind the same typed call a fast tool would use:

# server: render_report is a normal tool; Tasks() lets the server defer it
@mcp.tool(description="Render a multi-section report.")
def render_report(title: str, sections: int) -> str:
    ...  # minutes of work

# client: call_tool polls tasks/get to the final result for you
from mcp.client import Client, TasksExtension

async with Client(target, extensions=[TasksExtension()]) as client:
    result = await client.call_tool("render_report", {"title": "Q3", "sections": 8})

This Python API is in an open pull request, not yet in the released v2.0.0. If you want a Tasks runtime in a shipping SDK today, C# and Rust have one. The wire protocol above is final either way, so you can build against it now.
Learn more on the tasks extension here https://modelcontextprotocol.io/extensions/tasks/overview

Multi Round-Trip Requests (MRTR)

The other half of a long-running tool is interaction. A tool request like render_report may need to stop and ask “this will be 20000 pages, are you sure?” before it spend the compute required. In 2025 that was a server-initiated request sent back over the open stream. A stateless server has no open stream to send it on.

Instead of the server reaching back to the client, the server returns an InputRequiredResult, and the client answers by retrying the same request with the response attached:

→ tools/call render_report {title: "Q3", sections: 200}
← { resultType: "input_required",
    inputRequests: { confirm: {message: "Render a 200-section report?", ...} } }
→ tools/call render_report {title: "Q3", sections: 200,
    inputResponses: { confirm: {confirmed: true} }}
← { resultType: "complete", ... }

Every result now carries a resultType, either "complete" or "input_required", and older servers that omit it are treated as complete. Any instance can pick up the retry, because the request carries everything needed to continue. MRTR is in the released 2.0 SDKs.

Auth hardening

The authorization story got the most detailed rework, and it is the direct answer to the token-handling concerns I raised in 2025. An MCP server is now modeled as a standard OAuth 2.0 resource server: it advertises its authorization server through protected resource metadata, and clients get tokens from your identity provider rather than the server holding credentials. Two changes stand out. Clients must validate the iss (issuer) parameter on authorization responses per RFC 9207, which closes a class of mix-up attacks. And dynamic client registration is deprecated in favor of Client ID Metadata Documents, where a client identifies itself with a hosted metadata URL instead of pre-registering with every server. The server stops being a place where credentials and client records pile up.

What’s deprecated

Several features I used in earlier posts are now on a deprecation path. Sampling, where a server asks the client to run a model completion on its behalf, is deprecated. Roots and Logging are deprecated as well. The stream resumability I highlighted in my article on agent-to-agent communication on MCP has been removed; a broken stream now means reissuing the request. Elicitation still exists as a concept, but the mechanism is now MRTR. The legacy HTTP+SSE transport (the old two-endpoint SSE setup, distinct from Streamable HTTP) is deprecated too, in favor of Streamable HTTP. Deprecated features keep working through a minimum twelve-month window, so nothing breaks overnight, but new work should not adopt them.

These removals shed the protocol’s early workarounds. The session model, resumable streams, and server-initiated callbacks were early (less defensible) answers in 2024 and 2025, and the stateless design makes most of them unnecessary.

If you are building today

While the 2026-07-28 spec revision is finalized, SDK support (aka how easily you can implement these things in your favorite programming language) does tend to be uneven: as of writing (August 2026), which language you are in decides what you can use:

  • The C# and Rust SDKs are the most complete. Their stable releases ship all of it: stateless transport, a full Tasks runtime with a task store, the extensions and Apps API, and MRTR.

  • The Python and TypeScript SDKs (both at 2.0) ship the stateless core and MRTR, but the concrete Tasks runtime is not in the released version yet. In Python it is an open pull request; in TypeScript the tasks work is mid-extraction.

  • The Go SDK ships the stateless core and MRTR, but no Tasks runtime.

  • The Kotlin, Java, Swift, and PHP SDKs are behind; the Java and Swift releases predate the final spec.

See this interactive view on sdk support

A reasonable default for a new server is Streamable HTTP, stateless, with authorization modeled as an OAuth 2.0 resource server behind your identity provider, and extensions negotiated rather than assumed. Whether Tasks is available out of the box depends on your SDK. Two more things to budget for. The change is wire-incompatible in both directions, so existing servers need real refactors, not a version bump. And large binary data still has no clean path through MCP: it rides inline in JSON as base64, with no standardized resumable upload yet, though proposals are open.

The reality of spec adoption - what should your team do

In reality, I find that adopting a spec update like this is slower and messier than one might expect. I have also found that many teams have built their own extensions and workarounds to address several of these issues (especially auth and resumption), and they will be reasonably skeptical about tearing that out for even a clearly improved spec.

User's avatar

Continue reading this post for free, courtesy of Victor Dibia, PhD.

Or purchase a paid subscription.
© 2026 Substack Inc · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture