Background
SEP-2575 adds subscriptions/listen as a long-lived request on which a server acknowledges the requested subscriptions and then streams matching server-to-client notifications.
The SDK has built-in handling for standard */list_changed events, but it does not currently expose a public typed handler for subscriptions/listen. That leaves server authors without a supported way to implement custom subscription kinds, app-driven resources/updated delivery, or subscriptions backed by their own event source.
This is especially important for stateless Streamable HTTP. Notifications sent through the parent stateless server transport are intentionally dropped because there is no session-wide server-to-client channel. A subscriptions/listen request is different: its held-open POST response is the solicited stream, and the request's RelatedTransport can carry notifications for that subscription.
Required public API
Add a first-class handler slot consistent with the other typed server handlers:
McpServerHandlers.SubscriptionsListenHandler
IMcpServerBuilder.WithSubscriptionsListenHandler(...)
- Handler type:
McpRequestHandler<SubscriptionsListenRequestParams, EmptyResult>
Intended use
The handler owns the subscription stream. An implementation should be able to follow this pattern (helper names below are illustrative):
builder.WithSubscriptionsListenHandler(async (request, cancellationToken) =>
{
RequestId subscriptionId = request.JsonRpcRequest.Id;
// The acknowledgement must be the first message on the stream.
await request.Server.SendMessageAsync(
new JsonRpcNotification
{
Method = NotificationMethods.SubscriptionsAcknowledgedNotification,
Params = CreateAcknowledgementParams(request.Params, subscriptionId),
},
cancellationToken);
await foreach (JsonRpcNotification notification in
eventSource.ListenAsync(request.Params, cancellationToken))
{
AddSubscriptionId(notification, subscriptionId);
await request.Server.SendMessageAsync(notification, cancellationToken);
}
return new EmptyResult();
});
The handler is responsible for:
- Sending
notifications/subscriptions/acknowledged before any subscription events.
- Reporting only the filters it actually honors.
- Adding the listen request id to every streamed notification under
_meta[MetaKeys.SubscriptionId].
- Remaining active for the subscription lifetime and cleaning up when the supplied cancellation token is cancelled.
- Returning
EmptyResult when it deliberately completes the stream.
For extension filters not represented by SubscriptionsListenRequestParams, the handler should still be able to inspect request.JsonRpcRequest.Params. Application services or event buses can be resolved from request.Services or captured by the handler.
Open design questions
These choices are intentionally not acceptance criteria for this issue:
- Replacement or additive handler: The custom handler may fully replace the built-in implementation, or it may augment it. Either is acceptable if the behavior is clear. An additive design must still produce exactly one acknowledgement and avoid duplicate delivery.
- Automatic list-change delivery in stateless mode: The SDK may or may not automatically observe tool, prompt, and resource collection changes for stateless listeners. This is useful but orthogonal to exposing the handler and may not justify additional complexity. Whichever behavior is selected, advertised list-change capabilities must match what the server will actually deliver.
- Fan-out storage and buffering: A channel, subscription dictionary, event bus, or other internal mechanism is an implementation detail. This issue does not require reworking the existing built-in fan-out solely to support the public handler API.
Out of scope
- A durable cross-request or cross-process pub/sub system.
- A global unsolicited notification channel for stateless servers.
- Requiring automatic SDK-generated
resources/updated events; those remain application-driven and can be implemented by the custom handler.
Background
SEP-2575 adds
subscriptions/listenas a long-lived request on which a server acknowledges the requested subscriptions and then streams matching server-to-client notifications.The SDK has built-in handling for standard
*/list_changedevents, but it does not currently expose a public typed handler forsubscriptions/listen. That leaves server authors without a supported way to implement custom subscription kinds, app-drivenresources/updateddelivery, or subscriptions backed by their own event source.This is especially important for stateless Streamable HTTP. Notifications sent through the parent stateless server transport are intentionally dropped because there is no session-wide server-to-client channel. A
subscriptions/listenrequest is different: its held-open POST response is the solicited stream, and the request'sRelatedTransportcan carry notifications for that subscription.Required public API
Add a first-class handler slot consistent with the other typed server handlers:
McpServerHandlers.SubscriptionsListenHandlerIMcpServerBuilder.WithSubscriptionsListenHandler(...)McpRequestHandler<SubscriptionsListenRequestParams, EmptyResult>Intended use
The handler owns the subscription stream. An implementation should be able to follow this pattern (helper names below are illustrative):
The handler is responsible for:
notifications/subscriptions/acknowledgedbefore any subscription events._meta[MetaKeys.SubscriptionId].EmptyResultwhen it deliberately completes the stream.For extension filters not represented by
SubscriptionsListenRequestParams, the handler should still be able to inspectrequest.JsonRpcRequest.Params. Application services or event buses can be resolved fromrequest.Servicesor captured by the handler.Open design questions
These choices are intentionally not acceptance criteria for this issue:
Out of scope
resources/updatedevents; those remain application-driven and can be implemented by the custom handler.