cd /news/developer-tools/mcp-c-sdk-hybrid-sessions-serve-old-… · home topics developer-tools article
[ARTICLE · art-103404] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

MCP C# SDK Hybrid Sessions: Serve Old and New Clients on One Endpoint

The MCP C# SDK 2.2.0 release introduces a hybrid session mode, `HttpServerSessionMode.StatefulForInitializeClients`, enabling a single ASP.NET Core endpoint to serve both legacy clients using the `2025-11-25` initialize handshake and modern clients on the `2026-07-28` stateless protocol. The mode decides per-request whether to create a session, preserving compatibility without forcing modern clients to downgrade. A sample using `Microsoft.AspNetCore.TestHost` demonstrates offline verification of the hybrid behavior.

read3 min views3 publishedAug 19, 2026

The MCP C# SDK hybrid sessions option solves an awkward upgrade boundary: some clients still use the 2025-11-25

initialize handshake and depend on sessions, while clients on 2026-07-28

expect every HTTP request to stand alone. I want both groups to reach one ASP.NET Core endpoint without making modern clients downgrade or stripping useful behavior from legacy clients.

The stable C# SDK 2.2.0 release added exactly that path with HttpServerSessionMode.StatefulForInitializeClients

. The release notes describe it as hybrid stateful/stateless serving, and the official session-mode guide spells out the per-request behavior.

The 2026-07-28

MCP revision removed the initialize handshake and Mcp-Session-Id

from its wire format. Client identity, capabilities, and protocol version travel with each request instead. The final specification announcement explains why the core moved toward request/response statelessness.

That creates a migration choice for an existing server.

With HttpServerSessionMode.Stateful

, initialize-era clients receive full sessions. A modern request is refused so a dual-path client can fall back to the older handshake. Compatibility is preserved, but the client does not use the new protocol natively.

With HttpServerSessionMode.Stateless

, every request is independent. That is the right default for servers that do not need session state, unsolicited notifications, resource subscriptions, or older server-to-client flows. It may be too abrupt when deployed clients still rely on those features.

Hybrid mode makes the decision from the incoming request instead of applying one choice to the endpoint.

The server configuration is deliberately small:

builder.Services
    .AddMcpServer()
    .WithHttpTransport(options =>
    {
        options.SessionMode =
            HttpServerSessionMode.StatefulForInitializeClients;
    })
    .WithTools<DemoTools>();

app.MapMcp("/mcp");

An initialize-era client sends an initialize

request with protocolVersion: "2025-11-25"

. The server returns Mcp-Session-Id

, and that client must send the value on its later requests.

A 2026-07-28

client sends server/discover

or another operation with its modern metadata. It does not receive a session ID:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "hybrid-probe",
        "version": "1.0.0"
      }
    }
  }
}

For Streamable HTTP, the request also carries MCP-Protocol-Version: 2026-07-28

and the routing header Mcp-Method: server/discover

. Tool calls add Mcp-Name

. Those headers do not create a session; they let the transport route and validate a self-describing request.

Configuration alone is easy to regress. I prefer a transport-level check that exercises the real SDK handler while staying offline.

The complete sample uses Microsoft.AspNetCore.TestHost

, so it opens no port and calls no model. Its verifier runs these checks against the same /mcp

route:

Mcp-Session-Id

.echo

tool call succeeds and remains stateless.DELETE

returns 405 Method Not Allowed

.DELETE

closes its session successfully.The assertion that matters is not just a 200

response. Each side must get the correct session semantics:

AssertNoSession(modernToolCall, "modern tool call");

string sessionId = GetRequiredSessionId(
    legacyInitialize,
    "legacy initialize");

Equal(
    sessionId,
    GetRequiredSessionId(legacyToolCall, "legacy tool call"));

Run the verifier with:

dotnet restore .\McpHybridSessions.csproj
dotnet build .\McpHybridSessions.csproj -c Release --no-restore
dotnet run --project .\McpHybridSessions.csproj -c Release --no-build

The merged implementation and validation record are also in the pull request.

Hybrid mode is a bridge, not a new universal default. If every supported client speaks 2026-07-28

and the server needs no session-only behavior, choose Stateless

. It is simpler to scale because requests can land on any instance without affinity.

The modern half of a hybrid endpoint is still stateless. It cannot receive unsolicited notifications or use resource subscriptions, and it does not gain per-client isolation. Use the newer multi-round-trip mechanism where it fits rather than assuming hybrid mode restores sessions for modern requests.

The legacy half still has the operational costs of sessions. Session memory lives on the server, restarts discard it, and multiple instances may need affinity or a deliberate migration design. Authentication and authorization are separate concerns; a session ID is not proof of identity.

I would keep this regression test until the last initialize-era client is retired, then change the server and test together to the explicit stateless mode.

Are you keeping a legacy session path during your 2026-07-28

migration, or can your server go fully stateless?

Happy coding!

── more in #developer-tools 4 stories · sorted by recency
── more on @mcp c# sdk 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/mcp-c-sdk-hybrid-ses…] indexed:0 read:3min 2026-08-19 ·