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
#1693 moves Tasks out of Core. That split is useful only if extension packages can layer behavior onto the normal server pipeline without relying on private implementation details.
The current extension seams have three gaps:
CallToolWithAlternateFilters is mutually exclusive with normal CallToolFilters. A task wrapper can therefore displace filters used for validation, telemetry, and ASP.NET authorization.
Custom extension RPCs use raw JsonRpcRequest handlers. They do not get typed parameters/results or method-specific typed filters through the same pipeline as built-in methods.
subscriptions/listen grants are closed over the built-in tools, prompts, and resources fields. Extensions cannot implement their own subscription fields cleanly.
Subscription delivery has an additional transport constraint. The current Core fan-out model tracks active listeners on one long-lived McpServerImpl, which works for stdio and legacy stateful Streamable HTTP. It does not work for stateless Streamable HTTP, where each request has an independent server instance. Stateless delivery is tracked by #1662.
Proposed changes
Compose alternate tools/call behavior with the normal pipeline
Build the ordinary tools/call pipeline first, including primitive matching, CallToolFilters, request scoping, logging, and exception handling.
Adapt its normal result to ResultOrAlternate<CallToolResult>, then apply alternate-result filters around that adapter.
Keep an explicit CallToolWithAlternateHandler as the low-level full-replacement escape hatch. Reject combinations whose semantics cannot be preserved with an actionable configuration error.
Have the Tasks extension wrap the ordinary pipeline rather than replace it. Ordinary filters must run exactly once before a task-backed primitive executes.
Document the timing for task-backed calls, including whether an authorization denial is surfaced before or after the task record is created.
Add filterable typed custom RPCs
Keep raw custom handlers as an escape hatch and add a global raw custom-handler filter pipeline for coarse authorization, logging, and telemetry.
Add a typed custom-handler registration shape carrying the method name, JsonTypeInfo<TParams>, JsonTypeInfo<TResult>, a typed RequestContext<TParams> handler, and optional method-specific typed filters.
Preserve per-request DI scopes and source-generated serialization through the typed path.
Define and document filter ordering. Global raw filters should wrap typed filters, which should wrap the typed handler before result serialization.
Validate collisions with built-in, raw custom, and typed custom methods. Fail clearly when a configured typed filter uses parameter or result types that do not match its handler.
Move tasks/get, tasks/update, and tasks/cancel to the typed path, retaining protocol-version and Tasks capability checks.
Make subscriptions/listen extensible without coupling it to one delivery model
Allow extension-owned fields in SubscriptionsListenNotifications.
Add subscription grant processors that can inspect a request, validate capabilities or authorization, and populate the granted acknowledgement.
Use the grant hook for Tasks taskIds. Grant only recognized task IDs and require the Tasks capability.
Keep Core responsible for subscription ID metadata and routing, but do not make the current per-McpServerImpl_activeSubscriptions registry part of the public contract.
The filtering and typed-handler portions should not be blocked on #1662. The DTO and grant-processing seams can also land independently. Only the final public delivery contract needs coordination.
Tests and documentation
Add coverage for:
Normal and alternate tool filter composition and ordering.
Task-backed tools running ordinary CallToolFilters exactly once with the matched primitive available.
An ASP.NET integration using AddAuthorizationFilters() plus WithTasks(). An unauthorized caller must not execute the tool, and an authorized task-backed call must complete.
Global raw and method-specific typed filters around custom extension methods.
All three task lifecycle methods participating in typed filters.
Handler collisions, mismatched typed filters, request scopes, and serialization behavior.
Task subscription grants, capability failures, subscription metadata, and suppression of notifications without a matching grant on long-lived transports.
Mark the new Core extensibility APIs experimental and document their ordering, replacement semantics, and transport limitations.
Why
This preserves existing authorization and policy behavior while allowing Tasks and future protocol extensions to remain separate packages. It also gives extensions a reusable, typed composition model instead of requiring each package to duplicate Core's private dispatch, filtering, serialization, and subscription machinery.
Problem
#1693 moves Tasks out of Core. That split is useful only if extension packages can layer behavior onto the normal server pipeline without relying on private implementation details.
The current extension seams have three gaps:
CallToolWithAlternateFiltersis mutually exclusive with normalCallToolFilters. A task wrapper can therefore displace filters used for validation, telemetry, and ASP.NET authorization.JsonRpcRequesthandlers. They do not get typed parameters/results or method-specific typed filters through the same pipeline as built-in methods.subscriptions/listengrants are closed over the built-in tools, prompts, and resources fields. Extensions cannot implement their own subscription fields cleanly.Subscription delivery has an additional transport constraint. The current Core fan-out model tracks active listeners on one long-lived
McpServerImpl, which works for stdio and legacy stateful Streamable HTTP. It does not work for stateless Streamable HTTP, where each request has an independent server instance. Stateless delivery is tracked by #1662.Proposed changes
Compose alternate
tools/callbehavior with the normal pipelinetools/callpipeline first, including primitive matching,CallToolFilters, request scoping, logging, and exception handling.ResultOrAlternate<CallToolResult>, then apply alternate-result filters around that adapter.CallToolWithAlternateHandleras the low-level full-replacement escape hatch. Reject combinations whose semantics cannot be preserved with an actionable configuration error.Add filterable typed custom RPCs
JsonTypeInfo<TParams>,JsonTypeInfo<TResult>, a typedRequestContext<TParams>handler, and optional method-specific typed filters.tasks/get,tasks/update, andtasks/cancelto the typed path, retaining protocol-version and Tasks capability checks.Make
subscriptions/listenextensible without coupling it to one delivery modelSubscriptionsListenNotifications.taskIds. Grant only recognized task IDs and require the Tasks capability.McpServerImpl_activeSubscriptionsregistry part of the public contract.SendTaskStatusNotificationAsynccan remain the stable Tasks-facing API and delegate to the resulting Core delivery abstraction.The filtering and typed-handler portions should not be blocked on #1662. The DTO and grant-processing seams can also land independently. Only the final public delivery contract needs coordination.
Tests and documentation
Add coverage for:
CallToolFiltersexactly once with the matched primitive available.AddAuthorizationFilters()plusWithTasks(). An unauthorized caller must not execute the tool, and an authorized task-backed call must complete.Mark the new Core extensibility APIs experimental and document their ordering, replacement semantics, and transport limitations.
Why
This preserves existing authorization and policy behavior while allowing Tasks and future protocol extensions to remain separate packages. It also gives extensions a reusable, typed composition model instead of requiring each package to duplicate Core's private dispatch, filtering, serialization, and subscription machinery.
Related work
subscriptions/listendelivery over stateless Streamable HTTP. This issue must not introduce a competing shared-session or cross-request registry design.[McpServerTool]composability with MRTR input requests + tasks #1635 tracks task-body input request composition with MRTR.Out of scope
ResultOrAlternaterepresentation.