Make server extension handlers composable with filters and subscriptions
メンテナーはふだん 2 日以内に返信
@onatozmenn がすでに取り組んでいます。
2026年7月25日 から。
評価
この issue はまだ評価されていません。
説明
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.- Custom extension RPCs use raw
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/call behavior with the normal pipeline
- Build the ordinary
tools/callpipeline 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
CallToolWithAlternateHandleras 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 typedRequestContext<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, andtasks/cancelto 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.
- Do not invoke extension grant processors when the transport cannot deliver any granted subscriptions. Until #1662 lands, stateless HTTP should acknowledge no grants without task-store lookups, capability errors, or other extension side effects.
- 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_activeSubscriptionsregistry part of the public contract. - Coordinate the public notification delivery abstraction with #1662. It must be able to support a held-open stateless listen request or another transport-neutral backend rather than assuming the publisher owns every active listener.
SendTaskStatusNotificationAsynccan remain the stable Tasks-facing API and delegate to the resulting Core delivery abstraction.- If this work lands before #1662, scope notification fan-out explicitly to listeners owned by the current long-lived server instance, keep the Core seam experimental, and document that stateless Streamable HTTP delivery is not yet supported.
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
CallToolFiltersexactly once with the matched primitive available. - An ASP.NET integration using
AddAuthorizationFilters()plusWithTasks(). 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.
- Stateless HTTP acknowledging no extension grants and not invoking extension grant processors until #1662 provides delivery.
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
- #1662 owns server-to-client
subscriptions/listendelivery over stateless Streamable HTTP. This issue must not introduce a competing shared-session or cross-request registry design. - #1635 tracks task-body input request composition with MRTR.
Out of scope
- Implementing stateless Streamable HTTP subscription delivery, tracked by #1662.
- Ambient task context or a public task ID/status helper for tool implementations.
- Broader changes to the
ResultOrAlternaterepresentation.
- 主要言語
- C#
- スター
- 4.5k
- フォーク
- 814
- 平均マージ
- 9日 19時間
- マージ済み PR(30日)
- 4
環境構築
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
modelcontextprotocol/csharp-sdk のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
modelcontextprotocol/csharp-sdk#1867 ·
メンテナーはふだん 2 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
modelcontextprotocol/csharp-sdk#1840 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
modelcontextprotocol/csharp-sdk#1836 ·
メンテナーはふだん 2 日以内に返信
-
enhancement needs confirmation
難易度 2/5 1〜3時間 初心者へのやさしさ 64/100
modelcontextprotocol/csharp-sdk#678 · コメント 1 件 ·
メンテナーはふだん 2 日以内に返信
-
enhancement needs confirmation P3 ready for work
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
modelcontextprotocol/csharp-sdk#515 · コメント 6 件 · リアクション 3 件 ·
メンテナーはふだん 2 日以内に返信
modelcontextprotocol/csharp-sdk の issue をすべて見る
似ている issue
-
.NET bug
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
RayWangQvQ/BiliBiliToolPro#1144 ·
メンテナーはふだん 1 日以内に返信
-
area-System.Numerics.Tensors untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/runtime#134691 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 86/100
microsoft/DataFactory.MCP#121 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
unoplatform/uno.templates#2277 ·
メンテナーはふだん 5 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
microsoft/fluentui-blazor#5344 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信