2026-10-26 11:35 UTC
DANGMUAAI & Developer Tools, Decoded
BackDev Tools

MCP C# SDK 2.0 Is Stable: 10 Breaking Changes to Triage

Microsoft's stable MCP C# SDK 2.0 makes HTTP stateless by default and ships ten numbered breaking changes. Which are build warnings, which reach production.

DangMua EditorialOct 26, 20264 min read
MCP C# SDK 2.0 Is Stable: 10 Breaking Changes to Triage

Microsoft has announced the stable 2.0 line of the official MCP C# SDK, and the upgrade carries ten numbered breaking changes.

The SDK now implements the 2026-07-28 spec revision, and HTTP transport is stateless by default. InfoQ covered the announcement on October 26.

The version history matters more than the announcement

If you tested a preview over the summer and shelved it, the code has moved three times since. The v2.0.0 tag landed on GitHub on July 28, the same day the spec revision was cut, followed by 2.1 on August 5, 2.2 on August 13 and 2.3 on October 20. What is new this week is Microsoft's announcement of the stable line, not the code itself.

The write-up calls the revision the largest protocol change since MCP launched: the initialize handshake and the Mcp-Session-Id header are gone, protocol version and capabilities travel with every request, and cross-call state is now the server author's problem rather than the protocol's.

Sessions are opt-in now

HttpServerTransportOptions.Stateless defaults to true in 2.0. A stateless server creates no transport sessions, does not expose the standalone SSE GET and DELETE endpoints, and does not support unsolicited server-to-client requests. The payoff is operational: any instance can serve any request, so round-robin load balancers and horizontal scaling work without a shared session store.

You can opt back in with Stateless = false, but stateful-only options now raise MCP9006 and apply only to down-level initialize-handshake connections. Before flipping that flag, check what the session was actually carrying — in most servers the author saw, it held elicitation state and nothing else.

Discovery, headers and the elicitation replacement

Clients probe server/discover first and fall back to the legacy handshake against older servers. A tools/call request now carries Mcp-Method and Mcp-Name headers, and the [McpHeader] attribute promotes a tool parameter to an Mcp-Param-* header — SEP-2243 standardization, so gateways can route on headers without parsing the body. The body stays authoritative; a mismatched header returns a HeaderMismatch error so a misconfigured proxy fails loud. Legacy SSE endpoints raise MCP9004 on the way out.

Statelessness also removed the channel that servers used to ask a client for input mid-call. The replacement is Multi Round-Trip Requests: a tool throws InputRequiredException carrying input requests built with InputRequest.ForElicitation, ForSampling or ForRootsList plus an opaque requestState, and the client re-issues the call with inputResponses.

The quiet breaks that reach production

Three changes land in tests before they land in release notes. A tool with UseStructuredContent = true and a non-object return type now emits the raw value — structuredContent: 72 instead of { "result": 72 } — so clients reading a result property break. Deserializing a Tool payload without inputSchema throws JsonException rather than defaulting the schema; an empty {} is enough. And explicit SSE connections now surface the underlying HttpRequestException or TimeoutException instead of wrapping everything in IOException.

Authorization moved in one direction: reject rather than tolerate. Issuer validation follows RFC 9207 and RFC 8414, S256 must appear in code_challenge_methods_supported so PKCE is a hard requirement, and AuthorizationRedirectDelegate raises MCP9007 in favour of ClientOAuthOptions.AuthorizationCallbackHandler.

Is it worth upgrading now?

For most servers, yes — the warnings do the triage for you. The author's suggested first move is a build and a grep:

dotnet build 2>&1 | grep -E 'MCP90(04|05|06|07)'

Then grep for RequestMethods.Tasks, AuthorizationRedirectDelegate and any handler matching on the old IOException wrapper. One item is not a recompile: the v1.4.x Tasks implementation moved into ModelContextProtocol.Extensions.Tasks with no API or wire compatibility with the experimental 1.3 and 1.4 versions. If you exposed long-running work through that API, budget real migration time.

One more reason not to stall: the author reports probing the client side and finding that Claude Code 2.1.292 asks MCP servers for 2026-07-28. Two upgrade paths, same revision — worth closing before a client and a server disagree about whether a session exists.

More from DangMua