Microsoft shipped v2.0 of the official MCP C# SDK at the end of July, implementing what they describe as the largest revision of the Model Context Protocol since its launch. The headline change is easy to state: the HTTP transport now runs statelessly by default. The initialize handshake and the session id header are gone; protocol version and capabilities travel with each request; and where v1 configured stateful sessions out of the box, v2 flips HttpServerTransportOptions.Stateless to true.

I read the announcement from two vantage points. I run two small MCP servers in my own publishing pipeline (a content manager and a LinkedIn publisher for this site), and I spend my days on a multi-tenant SaaS backend where the difference between stateful and stateless request paths is the difference between scaling being boring and scaling being a project. From both seats, this release reads less like an SDK update and more like an architecture statement, and it is a statement I have watched every maturing backend make. I wrote about the scaling side of it in choosing the axis you scale on.

What v2.0 actually changes#

Three coordinated changes restructure how MCP behaves over HTTP. Together they turn an MCP server from a special snowflake that needs protocol-aware plumbing into a plain web workload:

v1.x over HTTP2026-07-28 revision (SDK v2.0)
Session modelinitialize/initialized handshake, Mcp-Session-Id header, server holds per-client stateNo handshake; protocol version and capabilities travel with every request
C# SDK defaultStateful sessions out of the boxHttpServerTransportOptions.Stateless defaults to true
HTTP surfaceOpaque JSON-RPC POST bodies; routing needs deep packet inspectionMcp-Method, Mcp-Name, and Mcp-Param-* headers; load balancers, gateways, and WAFs can act on traffic directly
Server needs client inputServer-initiated requests over the session (elicitation, sampling, roots)MRTR: the tool returns InputRequiredResult with an opaque requestState; the client re-issues the call with the answers
Scaling unitSticky sessions or a shared session storeAny instance handles any request; serverless and multi-instance deployments work as-is

Compatibility is handled the way you would hope: v2 clients fall back to the legacy handshake against older servers, v2 servers still accept it from older clients, and stable v1 code keeps compiling. You can take the upgrade without taking the migration.

Every protocol relearns the same lesson#

What strikes me about this revision is not novelty; it is familiarity. HTTP itself went through this: server-side sessions gave way to tokens that carry their own claims, because a request that any instance can serve is the unit of horizontal scale. My reference point is the SaaS platform I work on, which serves roughly 2k requests per second. The only reason that number is boring to operate is that no request cares which replica answers it. Every place we ever held per-client state in process eventually turned into a scaling constraint: sticky routing rules, drain logic on deploys, capacity planning per instance instead of per fleet.

MCP compressed that whole journey into about twenty months. The protocol launched session-first, met real infrastructure (load balancers, serverless platforms, edge runtimes, autoscalers), and concluded what backends conclude: statelessness is not a purity ideal, it is what the world’s deployment infrastructure is optimized for. Microsoft says it plainly in the announcement: MCP over HTTP is a web workload.

Two-panel comparison: stateful MCP pins each client to one server instance through a session, so scaling needs sticky routing and session stores; stateless MCP lets any instance answer any request, so scaling is just adding replicas behind the load balancer.
The session is what welds a client to an instance. Remove it and the fleet becomes interchangeable, which is the whole trick of horizontal scale.

What running my own MCP servers taught me#

My two servers are deliberately small: one edits this site’s content through a guarded admin API, one publishes articles to LinkedIn with a ledger that guarantees at-most-once posting. They run over stdio, and stdio quietly hides the entire problem this spec revision solves. When the transport is a child process, the process is the session: state is free, lifetime is obvious, and nothing needs to scale. It is exactly the environment that teaches you habits that break the moment a server goes remote and shared.

Because remote is where the bill arrives. A shared MCP server behind HTTPS with real users is a backend, and every bit of implicit state it accumulated as a local process becomes infrastructure: something has to pin clients to instances or replicate the state, deploys have to drain sessions, and a crashed instance takes its conversations with it. The v2 design pushes that state out of the server. MRTR is the cleanest example: instead of the server holding a pending question for the client, the tool returns the question along with an opaque requestState blob, and the client re-issues the call with the answers and the blob. Serialize the state, hand it to the caller, let them bring it back. Pagination cursors do this. JWTs do this. It is the oldest trick in scalable systems, applied to agent interactions.

Sequence schematic of a Multi Round-Trip Request: the client calls a tool, the stateless server returns InputRequiredResult with an opaque requestState, the client collects the user input and re-issues the same call with inputResponses plus the echoed requestState, and the server completes without ever holding the conversation in memory.
MRTR in one picture: the conversation state rides with the client, so no server instance has to remember it.

When a session is still the right call#

Stateless by default is a default, not a commandment, and I would push back on anyone who reads it as one. Push-first systems are the honest exception: when the server genuinely initiates (live notifications, progress streams, subscription updates), the long-lived connection is the product, not an implementation detail. I spent years on that side of the fence with SignalR and raw WebSockets under backpressure, and nothing about this revision changes the calculus there. The SDK keeps stateful mode one flag away, and choosing it deliberately for a push workload is good engineering, not legacy thinking.

The part I am watching next is the one the team named as their next focus: end-to-end authentication and authorization. Stateless transports make horizontal scale easy and make auth the hard remaining problem, because the identity and permissions that a session used to carry now have to travel with every request too. That is also where my own servers’ guarded-admin-key approach does not extend beyond a single owner, so I have a personal stake in them getting it right.

What I would do with this release#

  • Take the upgrade early: v2 is backward compatible and your v1 code keeps running, so the cost of being current is unusually low.
  • Audit existing servers for implicit session state (caches keyed by session, pending elicitations, per-client flags) and decide for each: serialize it into the request flow, move it to external storage, or defend the session explicitly.
  • Build new tool servers stateless-first and let MRTR carry interactive flows; reach for the stateful flag only when the server genuinely initiates.
  • Use the Mcp-Method and Mcp-Name headers in your infrastructure: per-tool rate limits, routing, and observability without touching request bodies.
  • Watch the auth work that comes next; that is where remote MCP either becomes a normal part of a production stack or stays a demo.
Making state optional did not remove state from your system; it moved the burden of proof. That is what growing up looks like for a protocol, and for a backend.