You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This release improves stateless HTTP transport defaults and documentation with a breaking behavioral change that we are considering as a server reliability fix and therefore not bumping the major version with this release. Legacy SSE endpoints are now disabled by default with a new HttpServerTransportOptions.EnableLegacySse property available to opt back into responding to the SSE endpoints; the property is marked as an [Obsolete] warning as we expect to remove this property in a future major version.
A warning-level [Obsolete] attribute is also applied to the RequestContext<TParams>(McpServer, JsonRpcRequest) constructor, and the RequestContext<TParams>(McpServer, JsonRpcRequest, TParams) overload should be used instead. This change contributes to fixes including DI scope lifetime in task-augmented tools, meta/progress combination failures, and outgoing message filter routing. We plan to remove the obsolete overload in a future major version.
Breaking Changes
Refer to the C# SDK Versioning documentation for details on versioning and breaking change policies.
MapMcp() no longer maps /sse and /message endpoints by default. Servers whose clients connect via SSE will find those endpoints removed.
Migrating from legacy SSE
If your clients connect to a /sse endpoint (e.g., https://my-server.example.com/sse), they were using the legacy SSE transport--if not running in Stateless mode. The /sse and /message endpoints are now disabled by default (xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse is false and marked [Obsolete] with diagnostic MCP9003). Upgrading the server SDK without updating clients will break SSE connections.
Client-side migration. Change the client Endpoint from the /sse path to the root MCP endpoint — the same URL your server passes to MapMcp(). For example:
// Before (legacy SSE):Endpoint=newUri("https://my-server.example.com/sse")// After (Streamable HTTP):Endpoint=newUri("https://my-server.example.com/")
With the default HttpTransportMode.AutoDetect transport mode, the client automatically tries Streamable HTTP first. You can also set TransportMode = HttpTransportMode.StreamableHttp explicitly if you know the server supports it.
Server-side migration. If you previously relied on /sse being mapped automatically, you now need EnableLegacySse = true (suppressing the MCP9003 warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove EnableLegacySse.
Transition period. If some clients still need SSE while others have already migrated to Streamable HTTP, set EnableLegacySse = true with Stateless = false. Both transports are served simultaneously by MapMcp() — Streamable HTTP on the root endpoint and SSE on /sse and /message. Once all clients have migrated, remove EnableLegacySse and optionally switch to Stateless = true.
SSE (legacy — opt-in only)
Legacy SSE endpoints are now disabled by default and must be explicitly enabled via HttpServerTransportOptions.EnableLegacySse. This is the primary reason they are disabled — the SSE transport has no built-in HTTP-level backpressure.
The legacy SSE transport separates the request and response channels: clients POST JSON-RPC messages to /message and receive responses through a long-lived GET SSE stream on /sse. The POST endpoint returns 202 Accepted immediately after queuing the message — it does not wait for the handler to complete. This means there is no HTTP-level backpressure on handler concurrency, because each POST frees its connection immediately regardless of how long the handler runs.
Internally, handlers are dispatched with a fire-and-forget pattern. A client can send unlimited POST requests to /message while keeping the GET stream open, and each one spawns a concurrent handler with no built-in limit.
The GET stream does provide session lifetime bounds: handler cancellation tokens are linked to the GET request's HttpContext.RequestAborted, so when the client disconnects the SSE stream, all in-flight handlers are cancelled. This is similar to SignalR's connection-bound lifetime model — but unlike SignalR, there is no per-client concurrency limit like MaximumParallelInvocationsPerClient. The GET stream provides cleanup on disconnect, not rate-limiting during the connection.
The RequestContext<TParams>(McpServer, JsonRpcRequest) constructor is now [Obsolete] with diagnostic MCP9003, producing build warnings. The Params property is also changed from TParams? to TParams.
Migration: Use the new 3-arg constructor: new RequestContext<TParams>(server, request, parameters).
If your clients connect to a /sse endpoint (e.g., https://my-server.example.com/sse), they were using the legacy SSE transport — if not running in Stateless mode. The /sse and /message endpoints are now disabled by default (xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse is false and marked [Obsolete] with diagnostic MCP9003). Upgrading the server SDK without updating clients will break SSE connections.
Client-side migration. Change the client Endpoint from the /sse path to the root MCP endpoint — the same URL your server passes to MapMcp(). For example:
// Before (legacy SSE):Endpoint=newUri("https://my-server.example.com/sse")// After (Streamable HTTP):Endpoint=newUri("https://my-server.example.com/")
With the default xref:ModelContextProtocol.Client.HttpTransportMode.AutoDetect transport mode, the client automatically tries Streamable HTTP first. You can also set TransportMode = HttpTransportMode.StreamableHttp explicitly if you know the server supports it.
Server-side migration. If you previously relied on /sse being mapped automatically, you now need EnableLegacySse = true (suppressing the MCP9003 warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove EnableLegacySse.
Transition period. If some clients still need SSE while others have already migrated to Streamable HTTP, set EnableLegacySse = true with Stateless = false. Both transports are served simultaneously by MapMcp() — Streamable HTTP on the root endpoint and SSE on /sse and /message. Once all clients have migrated, remove EnableLegacySse and optionally switch to Stateless = true.
Then if that fails for whatever reason, then there's the obsolete option/appcontext switch
Legacy SSE endpoints are disabled by default and must be explicitly enabled via xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse. This is the primary reason they are disabled — the SSE transport has no built-in HTTP-level backpressure.
The legacy SSE transport separates the request and response channels: clients POST JSON-RPC messages to /message and receive responses through a long-lived GET SSE stream on /sse. The POST endpoint returns 202 Accepted immediately after queuing the message — it does not wait for the handler to complete. This means there is no HTTP-level backpressure on handler concurrency, because each POST frees its connection immediately regardless of how long the handler runs.
Internally, handlers are dispatched with a fire-and-forget pattern. A client can send unlimited POST requests to /message while keeping the GET stream open, and each one spawns a concurrent handler with no built-in limit.
The GET stream does provide session lifetime bounds: handler cancellation tokens are linked to the GET request's HttpContext.RequestAborted, so when the client disconnects the SSE stream, all in-flight handlers are cancelled. This is similar to SignalR's connection-bound lifetime model — but unlike SignalR, there is no per-client concurrency limit like MaximumParallelInvocationsPerClient. The GET stream provides cleanup on disconnect, not rate-limiting during the connection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v1.2.0
This release improves stateless HTTP transport defaults and documentation with a breaking behavioral change that we are considering as a server reliability fix and therefore not bumping the major version with this release. Legacy SSE endpoints are now disabled by default with a new
HttpServerTransportOptions.EnableLegacySseproperty available to opt back into responding to the SSE endpoints; the property is marked as an[Obsolete]warning as we expect to remove this property in a future major version.A warning-level
[Obsolete]attribute is also applied to theRequestContext<TParams>(McpServer, JsonRpcRequest)constructor, and theRequestContext<TParams>(McpServer, JsonRpcRequest, TParams)overload should be used instead. This change contributes to fixes including DI scope lifetime in task-augmented tools, meta/progress combination failures, and outgoing message filter routing. We plan to remove the obsolete overload in a future major version.Breaking Changes
Refer to the C# SDK Versioning documentation for details on versioning and breaking change policies.
1. Disable legacy SSE by default #1468
MapMcp()no longer maps/sseand/messageendpoints by default. Servers whose clients connect via SSE will find those endpoints removed.Migrating from legacy SSE
If your clients connect to a
/sseendpoint (e.g.,https://my-server.example.com/sse), they were using the legacy SSE transport--if not running inStatelessmode. The/sseand/messageendpoints are now disabled by default (xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse isfalseand marked[Obsolete]with diagnosticMCP9003). Upgrading the server SDK without updating clients will break SSE connections.Client-side migration. Change the client
Endpointfrom the/ssepath to the root MCP endpoint — the same URL your server passes toMapMcp(). For example:With the default
HttpTransportMode.AutoDetecttransport mode, the client automatically tries Streamable HTTP first. You can also setTransportMode = HttpTransportMode.StreamableHttpexplicitly if you know the server supports it.Server-side migration. If you previously relied on
/ssebeing mapped automatically, you now needEnableLegacySse = true(suppressing theMCP9003warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then removeEnableLegacySse.Transition period. If some clients still need SSE while others have already migrated to Streamable HTTP, set
EnableLegacySse = truewithStateless = false. Both transports are served simultaneously byMapMcp()— Streamable HTTP on the root endpoint and SSE on/sseand/message. Once all clients have migrated, removeEnableLegacySseand optionally switch toStateless = true.SSE (legacy — opt-in only)
Legacy SSE endpoints are now disabled by default and must be explicitly enabled via
HttpServerTransportOptions.EnableLegacySse. This is the primary reason they are disabled — the SSE transport has no built-in HTTP-level backpressure.The legacy SSE transport separates the request and response channels: clients POST JSON-RPC messages to
/messageand receive responses through a long-lived GET SSE stream on/sse. The POST endpoint returns 202 Accepted immediately after queuing the message — it does not wait for the handler to complete. This means there is no HTTP-level backpressure on handler concurrency, because each POST frees its connection immediately regardless of how long the handler runs.Internally, handlers are dispatched with a fire-and-forget pattern. A client can send unlimited POST requests to
/messagewhile keeping the GET stream open, and each one spawns a concurrent handler with no built-in limit.The GET stream does provide session lifetime bounds: handler cancellation tokens are linked to the GET request's
HttpContext.RequestAborted, so when the client disconnects the SSE stream, all in-flight handlers are cancelled. This is similar to SignalR's connection-bound lifetime model — but unlike SignalR, there is no per-client concurrency limit likeMaximumParallelInvocationsPerClient. The GET stream provides cleanup on disconnect, not rate-limiting during the connection.2. Obsolete 2-arg RequestContext constructor #1462
The
RequestContext<TParams>(McpServer, JsonRpcRequest)constructor is now[Obsolete]with diagnosticMCP9003, producing build warnings. TheParamsproperty is also changed fromTParams?toTParams.Migration: Use the new 3-arg constructor:
new RequestContext<TParams>(server, request, parameters).What's Changed
Documentation Updates
Repository Infrastructure Updates
Acknowledgements
Full Changelog: v1.1.0...release-1.2.0
API Compatibility Report
✅ All packages pass API compatibility validation against v1.0.0 baseline.
Existing suppressions (from v1.0.0 → v1.1.0):
ModelContextProtocol.Core: 8 CP0005 suppressions forMcpClient.Completion(abstract member addition gated by[Experimental])No new suppressions required for v1.2.0.
API Diff Report
ModelContextProtocol.Core
namespace ModelContextProtocol.Server { public sealed class McpServerToolAttribute { + public System.Type? OutputSchemaType { get; set; } } public sealed class McpServerToolCreateOptions { + public System.Text.Json.JsonElement? OutputSchema { get; set; } } public sealed class RequestContext<TParams> : ModelContextProtocol.Server.MessageContext { - public TParams? Params { get; set; } + public TParams Params { get; set; } + public RequestContext(McpServer server, JsonRpcRequest jsonRpcRequest, TParams parameters); } }ModelContextProtocol
No API changes.
ModelContextProtocol.AspNetCore
namespace ModelContextProtocol.AspNetCore { public class HttpServerTransportOptions { + public bool? EnableLegacySse { get; set; } } }