{"slug": "mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint", "title": "MCP C# SDK Hybrid Sessions: Serve Old and New Clients on One Endpoint", "summary": "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.", "body_md": "The MCP C# SDK hybrid sessions option solves an awkward upgrade boundary: some clients still use the `2025-11-25`\n\ninitialize handshake and depend on sessions, while clients on `2026-07-28`\n\nexpect 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.\n\nThe stable C# SDK 2.2.0 release added exactly that path with `HttpServerSessionMode.StatefulForInitializeClients`\n\n. The [release notes](https://github.com/modelcontextprotocol/csharp-sdk/releases/tag/v2.2.0) describe it as hybrid stateful/stateless serving, and the [official session-mode guide](https://csharp.sdk.modelcontextprotocol.io/v2/concepts/stateless/stateless.html#hybrid-mode-sessions-for-initialize-clients-only) spells out the per-request behavior.\n\nThe `2026-07-28`\n\nMCP revision removed the initialize handshake and `Mcp-Session-Id`\n\nfrom its wire format. Client identity, capabilities, and protocol version travel with each request instead. The [final specification announcement](https://blog.modelcontextprotocol.io/posts/2026-07-28/) explains why the core moved toward request/response statelessness.\n\nThat creates a migration choice for an existing server.\n\nWith `HttpServerSessionMode.Stateful`\n\n, 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.\n\nWith `HttpServerSessionMode.Stateless`\n\n, 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.\n\nHybrid mode makes the decision from the incoming request instead of applying one choice to the endpoint.\n\nThe server configuration is deliberately small:\n\n``` js\nbuilder.Services\n    .AddMcpServer()\n    .WithHttpTransport(options =>\n    {\n        options.SessionMode =\n            HttpServerSessionMode.StatefulForInitializeClients;\n    })\n    .WithTools<DemoTools>();\n\napp.MapMcp(\"/mcp\");\n```\n\nAn initialize-era client sends an `initialize`\n\nrequest with `protocolVersion: \"2025-11-25\"`\n\n. The server returns `Mcp-Session-Id`\n\n, and that client must send the value on its later requests.\n\nA `2026-07-28`\n\nclient sends `server/discover`\n\nor another operation with its modern metadata. It does not receive a session ID:\n\n```\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"server/discover\",\n  \"params\": {\n    \"_meta\": {\n      \"io.modelcontextprotocol/protocolVersion\": \"2026-07-28\",\n      \"io.modelcontextprotocol/clientCapabilities\": {},\n      \"io.modelcontextprotocol/clientInfo\": {\n        \"name\": \"hybrid-probe\",\n        \"version\": \"1.0.0\"\n      }\n    }\n  }\n}\n```\n\nFor Streamable HTTP, the request also carries `MCP-Protocol-Version: 2026-07-28`\n\nand the routing header `Mcp-Method: server/discover`\n\n. Tool calls add `Mcp-Name`\n\n. Those headers do not create a session; they let the transport route and validate a self-describing request.\n\nConfiguration alone is easy to regress. I prefer a transport-level check that exercises the real SDK handler while staying offline.\n\nThe [complete sample](https://github.com/ssukhpinder/dev-to-code-samples/tree/main/036-mcp-hybrid-sessions) uses `Microsoft.AspNetCore.TestHost`\n\n, so it opens no port and calls no model. Its verifier runs these checks against the same `/mcp`\n\nroute:\n\n`Mcp-Session-Id`\n\n.`echo`\n\ntool call succeeds and remains stateless.`DELETE`\n\nreturns `405 Method Not Allowed`\n\n.`DELETE`\n\ncloses its session successfully.The assertion that matters is not just a `200`\n\nresponse. Each side must get the correct session semantics:\n\n```\nAssertNoSession(modernToolCall, \"modern tool call\");\n\nstring sessionId = GetRequiredSessionId(\n    legacyInitialize,\n    \"legacy initialize\");\n\nEqual(\n    sessionId,\n    GetRequiredSessionId(legacyToolCall, \"legacy tool call\"));\n```\n\nRun the verifier with:\n\n```\ndotnet restore .\\McpHybridSessions.csproj\ndotnet build .\\McpHybridSessions.csproj -c Release --no-restore\ndotnet run --project .\\McpHybridSessions.csproj -c Release --no-build\n```\n\nThe merged implementation and validation record are also in [the pull request](https://github.com/ssukhpinder/dev-to-code-samples/pull/24).\n\nHybrid mode is a bridge, not a new universal default. If every supported client speaks `2026-07-28`\n\nand the server needs no session-only behavior, choose `Stateless`\n\n. It is simpler to scale because requests can land on any instance without affinity.\n\nThe 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.\n\nThe 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.\n\nI 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.\n\nAre you keeping a legacy session path during your `2026-07-28`\n\nmigration, or can your server go fully stateless?\n\nHappy coding!", "url": "https://wpnews.pro/news/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint", "canonical_source": "https://dev.to/ssukhpinder/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint-47hp", "published_at": "2026-08-19 18:47:30+00:00", "updated_at": "2026-08-19 19:46:12.768290+00:00", "lang": "en", "topics": ["developer-tools", "mlops"], "entities": ["MCP C# SDK", "ASP.NET Core", "Model Context Protocol", "Microsoft.AspNetCore.TestHost"], "alternates": {"html": "https://wpnews.pro/news/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint", "markdown": "https://wpnews.pro/news/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint.md", "text": "https://wpnews.pro/news/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint.txt", "jsonld": "https://wpnews.pro/news/mcp-c-sdk-hybrid-sessions-serve-old-and-new-clients-on-one-endpoint.jsonld"}}