AI Native  

MCP HTTP Scalability with the New C# SDK 2.0

Model Context Protocol (MCP) applications are increasingly being deployed as HTTP services rather than local development tools.

That creates a familiar set of backend engineering questions:

  • Can the MCP server scale horizontally?

  • Does every request need to reach the same server instance?

  • How should load balancers route MCP traffic?

  • What happens when a tool needs multiple interactions with the client?

  • Should MCP servers maintain sessions?

  • How should state be handled when requests can reach different instances?

The MCP C# SDK 2.0 addresses several of these concerns by aligning with the MCP specification revision dated July 28, 2026.

The most important architectural change is that MCP over HTTP is now stateless by default for the new protocol revision. The SDK builds on ASP.NET Core, allowing MCP traffic to use conventional HTTP infrastructure such as load balancers, reverse proxies, gateways, and container orchestration platforms.

This changes how developers should think about scaling MCP servers.

Instead of designing around a persistent MCP session, applications can increasingly treat MCP requests like ordinary HTTP workloads.

What Changed in MCP C# SDK 2.0?

The MCP C# SDK 2.0 implements the July 28, 2026 protocol revision.

Several changes are particularly important for HTTP scalability:

AreaEarlier ModelMCP C# SDK 2.0
HTTP stateSession-orientedStateless by default
Session IDMcp-Session-IdRemoved for the new protocol revision
Initializationinitialize handshakeserver/discover for the new revision
Load balancingSticky routing may be requiredAny instance can process requests
Interactive requestsStateful server-to-client flowMulti Round-Trip Requests
HTTP infrastructureMCP-specific considerationsStandard HTTP infrastructure

The SDK also remains backward compatible with older protocol versions, allowing clients and servers to fall back to the previous initialization model when necessary.

Why Stateless MCP Matters for Scaling

Consider a traditional stateful MCP deployment:

                    Load Balancer
                         |
             +-----------+-----------+
             |                       |
             v                       v
        MCP Server A            MCP Server B
             |                       |
        Session Store          Session Store

If a client establishes a session with Server A, subsequent requests may need to return to Server A.

That creates operational complexity.

You may need:

  • Sticky sessions

  • Shared session storage

  • Session replication

  • More complicated failover

  • Additional memory per server instance

The stateless model changes the architecture:

                    Load Balancer
                         |
          +--------------+--------------+
          |              |              |
          v              v              v
      MCP Server A   MCP Server B   MCP Server C
          |              |              |
          +--------------+--------------+
                         |
                   Shared Services

Each request contains the information required for processing.

A load balancer can therefore distribute requests across available instances without maintaining MCP-specific session affinity.

Microsoft's SDK documentation explicitly describes stateless mode as the recommended model for HTTP-based MCP servers because it avoids session complexity, memory overhead, and deployment constraints.

Building a Stateless MCP Server

The MCP C# SDK integrates with ASP.NET Core.

A minimal HTTP server can be configured like this:

using ModelContextProtocol.Server;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = true;
    })
    .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp();

app.Run();

In SDK 2.0, stateless HTTP transport is already the default, but explicitly setting it can make the deployment intention clear.

The important configuration is:

options.Stateless = true;

With stateless mode enabled, the server does not maintain MCP session state between HTTP requests. Each request is handled independently.

Adding an MCP Tool

A simple tool can be defined using the SDK attributes:

using System.ComponentModel;
using ModelContextProtocol.Server;

[McpServerToolType]
public static class OrderTools
{
    [McpServerTool]
    [Description("Gets the status of an order.")]
    public static string GetOrderStatus(
        string orderId)
    {
        return $"Order {orderId} is processing.";
    }
}

The tool itself should not assume that the next request will arrive at the same server instance.

That is an important design constraint.

Avoid code such as:

private static Dictionary<string, object> Sessions = new();

when that dictionary represents business state that needs to survive across requests.

With multiple server instances, each instance would have its own copy.

Where Should Application State Go?

Stateless MCP does not mean that the entire application must be stateless.

It means that MCP transport state does not have to be maintained between requests.

Suppose a tool starts a shopping workflow.

Instead of storing the current basket inside an MCP session, return an explicit identifier:

basketId = "bsk_12345"

The client can then send the identifier to the next tool:

create_basket()
        |
        v
basketId
        |
        v
add_item(basketId, productId)
        |
        v
checkout(basketId)

The state can live in an external data store:

MCP Server
    |
    +---- Redis
    |
    +---- SQL Server
    |
    +---- Cosmos DB
    |
    +---- External API

This is a familiar distributed-system pattern.

The important difference is that the MCP server does not need to know which server instance handled the previous request.

Stateless Does Not Mean No State

This distinction is important.

A stateless MCP service can still use:

  • SQL Server

  • Redis

  • Azure Cosmos DB

  • Blob storage

  • External APIs

  • Message queues

The service simply avoids storing request continuity in process-local MCP session state.

For example:

public async Task<Order> GetOrderAsync(
    string orderId,
    CancellationToken cancellationToken)
{
    return await orderRepository.GetAsync(
        orderId,
        cancellationToken);
}

The repository may access a database, but the MCP server itself does not need to remember which instance previously processed the client request.

Multi Round-Trip Requests

One challenge with stateless communication is interactive tools.

Suppose an MCP tool needs additional information from the client.

Previously, server-initiated interactions such as elicitation depended on session-oriented communication.

The July 28 protocol revision introduces Multi Round-Trip Requests (MRTR).

Instead of maintaining a persistent connection state, the server can return an input_required result containing the information required to continue. The client supplies the requested input and sends another request containing the relevant state.

Conceptually:

Client
  |
  | tools/call
  v
Server
  |
  | input_required
  v
Client
  |
  | inputResponses + requestState
  v
Server
  |
  | final result
  v
Client

The continuity is carried through the request payload instead of requiring a server-side transport session.

This is one of the changes that makes interactive workflows compatible with stateless HTTP.

Scaling Behind a Load Balancer

A major benefit of stateless MCP is simpler horizontal scaling.

Consider three containers:

                   Reverse Proxy
                        |
          +-------------+-------------+
          |             |             |
          v             v             v
      Container 1   Container 2   Container 3
          |             |             |
          +-------------+-------------+
                        |
                 Shared Database

A request sequence could look like:

Request 1 -> Container 1
Request 2 -> Container 3
Request 3 -> Container 2
Request 4 -> Container 1

The application should continue working because no MCP session is tied to a specific container.

This makes common deployment strategies easier to use:

  • Kubernetes

  • Azure Container Apps

  • Container Apps environments

  • VM scale sets

  • Containerized ASP.NET Core hosting

  • Cloud load balancers

The important requirement is that application state required across requests must live in an appropriate shared system.

Routing MCP Requests Through Standard HTTP Infrastructure

The new MCP HTTP model also introduces standardized headers that make MCP traffic easier for HTTP infrastructure to inspect.

The protocol can expose information such as:

Mcp-Method
Mcp-Name
Mcp-Param-*

For example:

Mcp-Method: tools/call
Mcp-Name: get_order_status
Mcp-Param-Region: eastus2

This allows infrastructure such as gateways and load balancers to make routing decisions without parsing the JSON-RPC request body.

Microsoft's SDK documentation describes this as a way to let ordinary HTTP infrastructure route MCP requests based on standardized headers while keeping the JSON-RPC body authoritative.

Region-Aware MCP Routing

This becomes especially useful for geographically distributed applications.

Imagine an MCP tool accepts:

region
orderId

A client might send:

region = eastus2
orderId = ORD-10042

The HTTP infrastructure can use the promoted region parameter to route the request toward the appropriate deployment.

Conceptually:

                    Global Gateway
                          |
             +------------+------------+
             |                         |
             v                         v
        East Region               West Region
             |                         |
        MCP Cluster               MCP Cluster
             |                         |
        Orders API               Orders API

The MCP server does not need to implement its own global routing mechanism.

However, routing should not rely solely on a header supplied by an untrusted client. Authorization and server-side validation still need to determine whether the requested region and resource are valid.

Handling Long-Running Operations

Stateless HTTP also works well with asynchronous task patterns.

Instead of holding an HTTP request open for a long operation:

Client
  |
  | Start operation
  v
Server
  |
  | Task ID
  v
Client
  |
  | Poll
  v
Server
  |
  | Status
  v
Client

The SDK provides a Tasks extension for long-running operations.

A task store can hold the operation state so that subsequent polling requests can reach any suitable server instance.

For example:

builder.Services
    .AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = true;
    })
    .WithTasks(new InMemoryMcpTaskStore());

The in-memory implementation is appropriate for development and testing, but a multi-instance production deployment generally needs durable shared storage when tasks must survive process restarts or be accessible consistently across instances.

Stateful vs Stateless MCP

Stateless mode is not the correct choice for every workload.

RequirementStatelessStateful
Horizontal scalingExcellent fitMore complex
Sticky sessionsNot required for new stateless protocolMay be required
Per-session server stateNoYes
Unsolicited notificationsNot available through legacy session mechanismSupported
Legacy server-to-client requestsLimitedSupported
MRTRSupportedSupported
Deployment complexityLowerHigher
Session memoryNone at MCP transport layerRequired

The SDK documentation recommends stateless mode when the server does not need session-specific behavior. Stateful mode remains useful when applications require session-scoped state or legacy server-to-client interaction patterns.

Common Scalability Mistakes

Keeping Business State in Static Variables

This works with one server instance but fails when requests move between instances.

Use a shared state store instead.

Assuming Stateless Means No Database

Stateless services can use databases normally.

The goal is to remove unnecessary transport-level affinity, not eliminate persistence.

Using Sticky Sessions by Default

Sticky routing can hide architectural problems.

If the application does not actually need session affinity, allowing requests to reach any healthy instance generally produces a simpler distributed architecture.

Using In-Memory Task Storage Across Multiple Instances

An in-memory task store belongs to one process.

If another instance receives the polling request, it cannot necessarily retrieve the task.

Use durable shared storage when tasks must survive restarts or move across instances.

Trusting Routing Headers

Headers that influence routing should not automatically become authorization decisions.

Validate the underlying resource and enforce authorization independently.

Production Deployment Checklist

Before deploying a stateless MCP service, verify the following:

  1. No required business state exists only in process memory.

  2. All required cross-request state uses shared persistence.

  3. Health checks are configured for the HTTP service.

  4. Request timeouts are appropriate for tool execution.

  5. Rate limiting protects expensive tools.

  6. Authentication and authorization are enforced independently of routing.

  7. Task storage is shared when multiple instances need access.

  8. Logging includes tool name and correlation information.

  9. Load testing includes concurrent MCP tool requests.

  10. Failure testing verifies that requests can move between instances.

How to Test Horizontal Scalability

A useful scalability test should not simply send requests to one local process.

Run at least two server instances:

MCP Server A
MCP Server B

Put a load balancer in front of them.

Then execute a sequence such as:

1. Create workflow
2. Store workflow identifier
3. Execute operation
4. Poll status
5. Continue workflow
6. Complete operation

Verify that the requests can be distributed across both instances without losing state.

For example:

Create -> A
Update -> B
Poll   -> A
Finish -> B

If the workflow fails in this scenario, some state is probably still incorrectly tied to an individual server process.

Observability for MCP HTTP Workloads

Standard ASP.NET Core observability practices become particularly valuable when MCP servers scale horizontally.

Capture information such as:

  • Request duration

  • HTTP status code

  • MCP method

  • Tool name

  • Instance identifier

  • Correlation ID

  • Downstream dependency latency

  • Error category

  • Authentication result

A useful log entry might conceptually look like:

Tool: get_order_status
Instance: mcp-server-03
Duration: 82ms
Status: Success
Order: ORD-10042

Avoid logging sensitive tool arguments or credentials simply because they are available to the server.

Observability should provide enough information to diagnose distributed execution without creating a new data-exposure problem.

Backward Compatibility

Moving to SDK 2.0 does not require every client and server to upgrade simultaneously.

Microsoft states that the SDK maintains backward compatibility with older protocol revisions. A v2 client can fall back to the older initialization flow when communicating with a server that does not support the new revision.

This allows teams to migrate incrementally.

However, applications using experimental Tasks from earlier SDK versions need additional migration planning because the redesigned Tasks implementation is not wire-compatible with the earlier experimental version.

When Should You Still Use Stateful MCP?

Stateful mode remains useful when the application genuinely needs session-oriented behavior.

Examples include:

  • Session-specific server state

  • Legacy server-to-client request patterns

  • Unsolicited notifications

  • Resource subscriptions

  • Scenarios where older clients require stateful interaction

The mistake is not using stateful mode.

The mistake is using stateful mode simply because it was the historical default when the application does not actually need it.

Frequently Asked Questions

Is MCP C# SDK 2.0 completely stateless?

No.

The SDK supports both stateless and stateful operation. Stateless HTTP is the default for the new protocol revision and is the recommended choice for most HTTP-based servers. Stateful mode remains available for scenarios that require it.

Does stateless MCP eliminate the need for a database?

No.

A stateless server can still use databases, caches, queues, and external services. It simply avoids relying on server-local MCP session state to maintain workflow continuity.

Can I put a stateless MCP server behind a load balancer?

Yes. This is one of the main architectural benefits of the new model. Requests do not need MCP-specific session affinity in stateless mode.

Do all MCP clients support the new protocol revision?

Not necessarily.

The SDK supports compatibility and fallback for older protocol versions, so migration does not require every component to change simultaneously.

Is stateless always faster?

Not necessarily.

Stateless architecture can reduce session-management overhead and simplify scaling, but overall performance depends on application state, network latency, database access, tool execution time, serialization, concurrency, and infrastructure configuration.

Conclusion

MCP C# SDK 2.0 changes the HTTP deployment model in an important way.

The July 28, 2026 MCP protocol revision removes the need for protocol-level sessions in the new stateless HTTP flow and introduces Multi Round-Trip Requests for interactions that previously depended on persistent server-to-client communication.

For .NET developers, the result is a much more familiar architecture:

                    Load Balancer
                         |
        +----------------+----------------+
        |                |                |
        v                v                v
     MCP #1           MCP #2           MCP #3
        |                |                |
        +----------------+----------------+
                         |
                 Shared Application State

The key architectural principle is simple:

Keep MCP transport stateless, and make application state explicit and shareable.

That approach makes horizontal scaling easier, reduces dependence on sticky sessions, fits naturally with ASP.NET Core infrastructure, and gives teams a clearer path toward containerized and distributed MCP deployments.

For developers adopting MCP on .NET, the important shift is therefore not simply upgrading a NuGet package. It is rethinking the server as a scalable HTTP workload where request routing, persistence, authorization, observability, and failure recovery follow established distributed-system patterns.