benjamin-abt.com

The 2.0 release of the official Model Context Protocol (MCP) C# SDK is more than a version increment. It implements the 2026-07-28 MCP specification revision, which changes the default shape of MCP over HTTP. The protocol now favors independent, self-describing requests over a connection-scoped session, while still supporting interactive workflows that need more than one exchange.

That combination matters for .NET applications. MCP servers can now fit naturally into the operational model already used for ASP.NET Core APIs: load balancers distribute requests, middleware handles cross-cutting concerns, headers expose routing metadata, and any healthy instance can process a call. At the same time, a tool can request confirmation, sampling, or workspace roots without requiring a permanent connection to one server instance.

This article explains what changes in MCP C# SDK 2.0, why the new default is important, and how to migrate an existing .NET server without treating the upgrade as a rewrite.

What changed in the 2026-07-28 MCP revision

Earlier Streamable HTTP deployments commonly began with an initialize handshake. A server created an Mcp-Session-Id, and the client returned that identifier with later requests. This gave the transport continuity, but it also created a deployment constraint: subsequent requests had to return to the instance that owned the session, or the server had to coordinate session state across instances.

The current specification changes that default. For clients using the 2026-07-28 protocol version:

  • the initialize / initialized handshake is not part of the normal stateless path;
  • there is no Mcp-Session-Id header to retain between calls;
  • protocol version and capability information travel with the request; and
  • an MCP interaction is represented as self-contained HTTP traffic.

This does not mean that all application state disappears. It means that transport state is no longer the default place to hide it. A server that creates a report, browser context, shopping basket, or long-running operation should return an explicit identifier. A later tool call then receives that identifier as an ordinary argument. That design is easier to inspect, log, authorize, store, and compose across tools than an implicit server-side session.

The distinction is important. Stateless transport removes affinity from the MCP protocol layer. It does not eliminate the need for durable data, authorization, concurrency control, or idempotency in the business application.

A minimal stateless MCP server in ASP.NET Core

For an HTTP server, install the ASP.NET Core integration package:

1dotnet add package ModelContextProtocol.AspNetCore

The following server exposes a single tool over Streamable HTTP. In v2.0, .WithHttpTransport() is stateless by default.

 1using ModelContextProtocol.Server;
 2using System.ComponentModel;
 3
 4WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
 5
 6builder.Services
 7    .AddMcpServer()
 8    .WithHttpTransport()
 9    .WithToolsFromAssembly();
10
11WebApplication app = builder.Build();
12
13app.MapMcp();
14app.Run("http://localhost:3001");
15
16[McpServerToolType]
17public static class OrderTools
18{
19    [McpServerTool(Name = "get_order_status")]
20    [Description("Gets the current status of an order in a region.")]
21    public static string GetOrderStatus(
22        [McpHeader("Region")]
23        [Description("The region that owns the order.")]
24        string region,
25        [Description("The identifier of the order.")]
26        string orderId)
27    {
28        return $"Order {orderId} is being processed in {region}.";
29    }
30}

There is no server-side session setup in this example. A request can reach any instance that has the same application configuration and access to the same business dependencies. This makes ordinary horizontal scaling viable without sticky routing or a transport-session store.

Stateful mode remains available for cases that truly require it, such as server-to-client messages or session-scoped transport behavior. It is now an explicit choice rather than an invisible operational dependency. SDK analyzers identify stateful-only configuration and legacy SSE behavior with diagnostics such as MCP9004 and MCP9006, which is useful when reviewing an existing server.

MCP becomes visible to normal HTTP infrastructure

Stateless requests are useful on their own, but v2.0 also standardizes the HTTP information around a tool call. A call to tools/call can include headers such as Mcp-Method: tools/call and Mcp-Name: get_order_status. Tool parameters can also be promoted to Mcp-Param-* headers through the McpHeaderAttribute used in the example.

The region argument above is represented in the tool input schema as an HTTP header-capable field. A compatible client mirrors it as Mcp-Param-Region, allowing an intermediary to route the request to an appropriate regional backend without parsing the JSON-RPC body.

This is useful for more than global routing:

  • a gateway can apply a tool-specific rate limit using Mcp-Name;
  • a web application firewall can apply policy based on the MCP method and tool name;
  • a proxy can send calls to a region-local downstream service; and
  • observability infrastructure can classify traffic without custom JSON parsing.

The JSON-RPC body remains authoritative. When the body and promoted header disagree, the server rejects the request with a HeaderMismatch error rather than choosing one value. That rule is a small but important correctness property: routing metadata is available to intermediaries, but it cannot silently change the application input.

Headers also need the same caution as every other HTTP surface. Tool names and promoted parameters should be deliberately selected. A sensitive identifier, access token, personal datum, or high-cardinality value can be exposed to logs and tracing systems when moved into a header. In most cases, a coarse routing key such as a region, tenant shard, or service category is appropriate; an account number or raw user request is not.

Interactivity without a long-lived session

Stateless requests have an apparent limitation. Some tool calls cannot finish in a single response. A destructive action may require confirmation. A server may need the client to ask a language model to produce text. A filesystem-oriented tool may need the set of roots the client allows it to access.

Previous MCP versions handled these as server-initiated requests over a stateful session. That is incompatible with the goal that any subsequent request may arrive at any instance. MCP 2.0 addresses this with Multi Round-Trip Requests (MRTR).

Instead of contacting the client through a retained connection, a tool returns an InputRequiredResult. The result contains one or more input requests and an opaque requestState value. The client gathers the requested answers and invokes the same tool again with inputResponses and the returned requestState. The server continues the operation from that new request.

The flow is conceptually straightforward:

1client -> server: tools/call close_support_ticket(ticketId)
2server -> client: input_required(closeReason, requestState)
3client -> user or model: request confirmation and reason
4client -> server: tools/call close_support_ticket(ticketId, inputResponses, requestState)
5server -> client: completed tool result

The client-side McpClient can resolve this loop automatically when the appropriate handlers are registered. A client application supplies an ElicitationHandler, sampling handler, or roots handler according to the inputs it is willing to satisfy; CallToolAsync then returns the final result after the extra round trips.

On the server, a tool requests another turn by throwing InputRequiredException. The individual requests are created with InputRequest.ForElicitation(...), InputRequest.ForSampling(...), or InputRequest.ForRootsList(...). The return path is intentionally different from a normal exception: it describes incomplete work, not a failed operation.

Treat requestState as an untrusted continuation token

requestState makes MRTR stateless at the protocol layer, but it introduces a security boundary. The client receives the value and returns it later, so it must not be treated as trustworthy simply because it originated in a previous call.

For small, non-sensitive workflows, the value can contain an opaque identifier that references state in a shared store. The server validates that identifier, the authenticated principal, the requested action, expiration, and single-use rules before continuing. For self-contained state, the data should be integrity-protected and, where confidentiality matters, encrypted. A signed continuation token prevents undetected tampering; encryption also prevents clients from reading the payload. Both approaches still need an expiry and replay strategy.

The final operation must repeat authorization checks. Confirmation is evidence of user intent, not a substitute for permission validation. This is especially relevant when a tool performs changes to external systems, creates financial effects, or accesses resources that may change between the initial call and its continuation.

The new model replaces several server-initiated patterns

MRTR generalizes the common pattern of a server requiring additional client input. In a stateless server, it replaces the earlier server-initiated mechanisms for elicitation, sampling, and roots.

The legacy APIs are not removed, but their behavior reflects the transport. ElicitAsync, SampleAsync, and RequestRootsAsync remain usable on a stateful session. They throw in stateless mode because no connection exists for the server to initiate a request through. Sampling, roots, and logging APIs are also marked with MCP9005 deprecation guidance as the protocol moves toward the more general request-response model and standard .NET observability.

There is one specialized case. When consent must happen through a secure, out-of-band browser flow, such as a third-party OAuth approval, UrlElicitationRequiredException supports URL-mode elicitation. The server provides a hosted URL, the client presents it, and the client retries after the external consent flow completes. The sensitive interaction stays outside the tool-call payload rather than being simulated through ordinary text input.

Compatibility is deliberate, but Tasks need a migration plan

An SDK major version often suggests an all-at-once rollout. MCP C# SDK 2.0 deliberately avoids that for its stable v1 surface. Existing non-deprecated v1 code continues to compile and run. A v2 client falls back to the legacy handshake when it speaks to a previous-generation server, and a v2 server still accepts that handshake from an older client.

This makes a staged upgrade realistic:

  1. Upgrade an existing client or server to the v2 package.
  2. Resolve diagnostics for legacy SSE, stateful-only settings, or deprecated request patterns.
  3. Keep compatibility tests covering both old and 2026-07-28 peers during a transition period.
  4. Move HTTP deployments to the stateless default when application state has an explicit home.
  5. Adopt MRTR for interactive tool flows and retain a non-interactive argument path for older session-less clients.

The significant exception is the Tasks extension. The redesigned v2 Tasks model replaces the experimental Tasks extension from the 2025-11-25 specification and is not wire-compatible with that preview. Systems that adopted Tasks in MCP SDK 1.3.x or 1.4.x should treat it as a separate migration, with explicit interoperability and persistence tests.

The following packages reflect the SDK’s layered design:

1# Most server applications
2dotnet add package ModelContextProtocol
3
4# Streamable HTTP servers built with ASP.NET Core
5dotnet add package ModelContextProtocol.AspNetCore
6
7# Low-level APIs or client-only applications
8dotnet add package ModelContextProtocol.Core

The packages target net8.0, net9.0, net10.0, and netstandard2.0. The last target keeps the protocol library available to applications that must integrate with .NET Framework, while an ASP.NET Core HTTP host naturally belongs on a modern .NET runtime.

Extensions are opt-in by design

MCP 2.0 treats extensions as negotiated capabilities rather than inflating the base protocol package. The SDK follows that architecture with separate packages.

ModelContextProtocol.Extensions.Apps enables MCP Apps, which allow a tool to offer an interactive user interface in clients that support it. The extension is experimental, and its APIs require suppressing diagnostic MCPEXP003; that status should be treated as a signal to isolate the dependency and test upgrades carefully.

ModelContextProtocol.Extensions.Tasks supports long-running tool execution with client-side polling and a pluggable IMcpTaskStore. For development and tests, InMemoryMcpTaskStore is convenient. It is not suitable for a service that must survive a restart or distribute work across instances. A production task store needs durable, shared persistence and a clear ownership model for task execution, cancellation, retries, and MRTR continuations.

This packaging is operationally useful. A simple tool server does not take a dependency on UI or long-running task behavior merely because the protocol can support it. Each additional capability remains a conscious design and deployment decision.

Production guidance for a stateless MCP server

The v2 transport makes MCP easier to deploy, but a production server still needs the same discipline as any public HTTP API.

Authentication and authorization should be enforced before a tool executes, and authorization should be resource-aware rather than limited to a broad “may call MCP” decision. Tool descriptions should not be treated as an authorization boundary. An AI model may decide which tool to call; the server remains responsible for deciding whether the caller is allowed to perform the requested operation.

Input validation remains necessary even when an input schema exists. Validate ranges, formats, ownership, and cross-field invariants at the application boundary. A tool that receives an explicit basketId or browserId must verify access to that resource on every call. The move away from transport sessions makes this validation more visible, which is an advantage rather than a burden.

Observability should use the standard ASP.NET Core and OpenTelemetry pipeline. Record tool names, outcomes, latency, authentication context, and safely bounded routing fields. Do not emit prompts, secrets, access tokens, or full tool arguments by default. The standardized MCP headers make traffic easier to classify, but they do not remove the need for a considered telemetry schema.

Finally, test the deployment property that motivated the change. Integration tests should send related tool calls to different application instances against the same shared dependencies. A server that only works when requests remain on one pod is still stateful in practice, even if its transport configuration says otherwise.

Conclusion

MCP C# SDK 2.0 aligns MCP over HTTP with the way .NET services are normally built and operated. Streamable HTTP is stateless by default, tool calls expose useful routing metadata to standard HTTP infrastructure, and Multi Round-Trip Requests preserve interactive workflows without a retained transport session.

The practical migration is incremental. Upgrade the SDK, follow its diagnostics, make application state explicit, and design interactive tools around MRTR with an authenticated continuation model. The result is not simply an MCP server that scales more easily. It is a server whose state, routing, authorization, and operational behavior are clearer in the code and in the infrastructure around it.

For the full SDK announcement, see Announcing v2.0 of the official MCP C# SDK . The MCP C# SDK documentation and the 2026-07-28 MCP specification changelog provide the API-level and protocol-level detail for an implementation.


Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms
Modernization
Training & Consulting