AI Native  

Migrating MCP C# Servers from SDK 1.x to 2.0

The Model Context Protocol (MCP) C# SDK 2.0 is a significant architectural upgrade rather than a routine package-version change. The release aligns the SDK with the MCP 2026-07-28 specification revision, introducing stateless HTTP by default, discovery-first negotiation, Multi Round-Trip Requests, standardized HTTP headers, and a new extension model.

The important part for existing .NET applications is that the migration does not require a flag-day rewrite. Stable, non-deprecated APIs from the 1.x line remain compatible, while several older capabilities and experimental APIs require deliberate migration.

This article walks through a practical migration strategy for an MCP C# server, focusing on the changes most likely to affect an existing application.

What Changed in MCP C# SDK 2.0?

The most important changes can be summarized as follows:

AreaSDK 1.xSDK 2.0
HTTP behaviorStateful by defaultStateless by default
Protocol negotiationinitialize handshakeDiscovery-first negotiation
Session IDCommon for HTTP sessionsNot required in stateless mode
Horizontal scalingOften required session affinityStateless requests simplify scaling
Legacy SSEAvailableOpt-in/obsolete
Roots, Sampling, LoggingSupported APIsDeprecated
TasksExperimental Core implementationSeparate Tasks extension
Interactive requestsSession-orientedMulti Round-Trip Requests
HTTP infrastructureSession-awareDesigned for ordinary HTTP routing

The 2.0 release was published alongside the 2026-07-28 MCP specification revision. Microsoft describes this as the largest MCP protocol revision since its launch.

Step 1: Upgrade the NuGet Packages

Start by identifying which MCP packages your application currently references.

For a typical HTTP-based MCP server, the important packages are:

dotnet add package ModelContextProtocol
dotnet add package ModelContextProtocol.AspNetCore

The SDK also provides ModelContextProtocol.Core for lower-level client/server scenarios. Tasks are now distributed separately through:

dotnet add package ModelContextProtocol.Extensions.Tasks

The 2.0 packages target net8.0, net9.0, and net10.0, with netstandard2.0 support for scenarios involving .NET Framework.

After upgrading, build the application before making behavioral changes:

dotnet restore
dotnet build

This is important because the compiler and MCP analyzers can immediately identify deprecated APIs and migration points.

Do not suppress all warnings at this stage. Treat the warnings as your migration checklist.

Step 2: Understand the New Stateless HTTP Default

This is probably the most important behavioral change for an existing HTTP MCP server.

In 1.x, HTTP transport commonly used stateful sessions. A client received an Mcp-Session-Id and subsequent requests were associated with that session.

In SDK 2.0, HTTP transport is stateless by default:

builder.Services.AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.Stateless = true;
    })
    .WithTools<MyTools>();

var app = builder.Build();

app.MapMcp();

app.Run();

With stateless transport, the server does not maintain transport session state in memory. This makes ordinary load balancing and horizontal scaling significantly simpler because requests do not have to remain attached to a particular server instance. (GitHub)

For example, with multiple application instances:

             Load Balancer
                  |
        +---------+---------+
        |         |         |
     Server A  Server B  Server C
        |         |         |
        +---------+---------+
                  |
             Backend APIs

A request can be handled by any instance without relying on an MCP transport session.

When Should You Keep Stateful Mode?

Stateless should not be enabled blindly.

If your application depends on session-specific transport state, unsolicited server-to-client communication, resource subscriptions, or other stateful behavior, explicitly configure:

.WithHttpTransport(options =>
{
    options.Stateless = false;
})

The SDK continues to support stateful operation, but it is no longer the default architecture.

The production decision should therefore be based on application behavior rather than simply following the new default.

Step 3: Replace Legacy SSE Dependencies

One common migration problem is an application that still connects clients through /sse.

SDK 2.0 favors Streamable HTTP. Legacy SSE endpoints are no longer enabled by default.

A client that previously used:

Endpoint = new Uri("https://example.com/sse")

should migrate toward the MCP endpoint itself:

Endpoint = new Uri("https://example.com/mcp")

The server can expose the endpoint through:

app.MapMcp();

If legacy clients still require SSE during a transition period, the SDK provides an explicit compatibility option. However, this requires stateful mode and the legacy API is marked obsolete.

A better migration sequence is:

  1. Identify clients using /sse.

  2. Upgrade those clients to Streamable HTTP.

  3. Validate the new endpoint.

  4. Remove legacy SSE configuration.

  5. Move the server to stateless mode if its workload permits it.

This avoids carrying obsolete transport infrastructure indefinitely.

Step 4: Review Protocol Negotiation

The protocol connection lifecycle has changed significantly.

Older MCP clients and servers use the initialize/initialized handshake.

SDK 2.0 prefers discovery-first negotiation for the new protocol revision. When communicating with an older peer, the SDK can fall back to the legacy protocol flow.

This is one reason a staged migration is possible.

Conceptually:

SDK 2.0 Client
      |
      v
server/discover
      |
      +---- New server ----> Modern protocol
      |
      +---- Old server ----> Legacy initialize flow

Therefore, upgrading your server does not automatically mean every client must be upgraded on the same day.

This compatibility should still be tested explicitly in CI, particularly if your organization operates multiple independently deployed MCP clients.

Step 5: Review Deprecated APIs

SDK 2.0 marks several previously supported capability APIs as deprecated because of changes in the MCP specification.

The important diagnostic is MCP9005.

In particular, review usage of:

  • Roots

  • Sampling

  • Logging

These APIs can continue to work for down-level connections, but new implementations should not build additional dependencies around deprecated behavior.

Instead of globally suppressing the warning:

#pragma warning disable MCP9005

use the warning to locate the actual dependency and determine whether the functionality should be replaced, isolated, or temporarily retained for backward compatibility.

Step 6: Migrate Tasks Separately

Tasks deserve special attention because they are the major compatibility exception in the otherwise backward-compatible 2.0 migration.

The previous experimental Tasks implementation from the 1.x line has been replaced by the ModelContextProtocol.Extensions.Tasks package. It is not API- or wire-compatible with the earlier implementation.

Add the new package:

dotnet add package ModelContextProtocol.Extensions.Tasks

Then migrate the server-side registration to the new extension model.

Conceptually, the architecture changes from Tasks being part of the core SDK to Tasks being an opt-in extension:

MCP Core
   |
   +-- Tools
   +-- Resources
   +-- Prompts
   |
   +-- Tasks Extension

This is consistent with the broader 2.0 architecture, where optional capabilities are separated from the protocol core.

If your 1.x application never used experimental Tasks, this migration step can simply be skipped.

Step 7: Understand Multi Round-Trip Requests

Another important change is Multi Round-Trip Requests (MRTR).

Previously, interactive server-to-client behavior could depend on a session. The new protocol allows a tool to return an input requirement and an opaque requestState. The client provides the requested information and sends the tool call again.

The simplified flow is:

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

This allows interaction to continue without requiring server-side transport session state.

For example, a tool might require a confirmation reason before completing an operation. In the new SDK, a tool can use an input-required flow rather than depending on a persistent HTTP session.

This is particularly useful when designing MCP servers that need to work reliably behind load balancers.

A Practical Migration Checklist

Before deploying the upgraded server, verify the following:

CheckRequired Action
NuGet packagesUpgrade MCP packages
Build warningsReview all MCP diagnostics
HTTP transportDecide whether stateless mode is appropriate
/sse clientsMigrate to Streamable HTTP
Session stateIdentify code depending on Mcp-Session-Id
RootsReview deprecated usage
SamplingReview deprecated usage
LoggingReview deprecated usage
TasksMigrate if the application used experimental Tasks
Load balancingTest requests across multiple instances
AuthenticationRetest authentication and authorization
Integration testsTest both modern and down-level clients
ObservabilityVerify request correlation without session IDs

Common Migration Mistakes

Assuming a Major Version Means Everything Is Breaking

It does not.

Stable, non-deprecated 1.x APIs remain compatible in 2.0. The major changes are concentrated around protocol behavior, deprecated capabilities, and experimental APIs.

Enabling Stateful Mode Just to Avoid Migration Work

This can be useful temporarily, but it should be an intentional compatibility decision.

If your server does not require transport sessions, stateless operation generally provides a cleaner deployment model.

Ignoring Legacy SSE Clients

A server upgrade can expose a compatibility problem if existing clients still depend on /sse.

Inventory clients before deploying the new server.

Suppressing MCP Warnings Globally

Warnings such as MCP9005 and MCP9006 contain useful migration information. Suppressing them across the entire project can hide genuine compatibility problems.

Testing Only on a Single Server

Stateless architecture changes the assumptions around deployment. Test with multiple instances behind the same load balancer.

A useful integration test should verify that sequential requests from the same logical operation can reach different server instances without relying on transport affinity.

Troubleshooting

The Existing Client Cannot Connect

First determine whether the client is using legacy SSE, the older initialization flow, or Streamable HTTP.

Check the endpoint and transport mode before changing server-side logic.

The Application Suddenly Requires Session State

Review the actual feature requiring it.

If the dependency is unsolicited server-to-client communication or another stateful feature, explicitly use:

options.Stateless = false;

If the dependency is only an assumption inherited from the old architecture, remove it rather than preserving unnecessary session state.

Build Produces MCP9005 Warnings

Locate the deprecated Roots, Sampling, or Logging API usage and decide whether it is still required for down-level compatibility.

Do not treat the warning as a compiler problem to hide.

Tasks No Longer Compile

Check whether the application used the experimental 1.x Tasks APIs.

If it did, add:

dotnet add package ModelContextProtocol.Extensions.Tasks

and migrate to the new Tasks extension APIs.

Best Practices for Production Migration

A reliable migration should be incremental.

  1. Upgrade dependencies first.

  2. Build and capture all MCP warnings.

  3. Separate compile-time migration from transport migration.

  4. Inventory existing MCP clients.

  5. Test Streamable HTTP before removing legacy SSE.

  6. Validate stateless behavior behind a load balancer.

  7. Migrate experimental Tasks independently.

  8. Test authentication and authorization again.

  9. Test both modern and older MCP clients.

  10. Remove compatibility code only after dependent clients have migrated.

This approach minimizes the risk of combining several architectural changes into one deployment.

Frequently Asked Questions

Is MCP C# SDK 2.0 backward compatible with 1.x?

Yes, stable non-deprecated 1.x APIs continue to work. The SDK also supports interoperability with down-level MCP peers through protocol negotiation. Experimental Tasks are the major exception because the 2.0 Tasks design is not compatible with the earlier implementation.

Do I have to use stateless HTTP?

No. Stateless HTTP is the default in 2.0, but stateful mode remains available when the application genuinely requires session behavior. (GitHub)

Should I migrate from SSE?

For new deployments, Streamable HTTP is the recommended direction. Legacy SSE can be retained temporarily for compatibility, but it is an explicit legacy configuration in the newer SDK.

Will I need to update every MCP client immediately?

Not necessarily. The SDK is designed to negotiate with down-level peers, allowing clients and servers to be upgraded independently in many deployments.

Is every 1.x API available unchanged?

No. Deprecated capabilities and experimental APIs require review. In particular, Roots, Sampling, Logging, and the previous experimental Tasks implementation need attention.

Conclusion

Migrating an MCP C# server from SDK 1.x to 2.0 is best understood as a protocol and deployment architecture upgrade, not simply a NuGet package update.

The most important migration decisions are whether your server can operate statelessly, whether any clients still depend on legacy SSE, whether your code uses deprecated capabilities, and whether you adopted the experimental Tasks APIs.

The good news is that the migration can be staged. Stable 1.x APIs remain compatible, and SDK 2.0 provides down-level protocol interoperability. That allows teams to upgrade infrastructure, clients, and application behavior progressively rather than performing a single disruptive migration.