Fix HTTP task filter composition (#1722)
This commit is contained in:
parent
ef37c2156e
commit
a5179df39b
2 changed files with 1164 additions and 286 deletions
|
|
@ -32,6 +32,22 @@ The following request filter methods are available on `IMcpRequestFilterBuilder`
|
|||
| `AddUnsubscribeFromResourcesFilter` | Resource unsubscription handlers |
|
||||
| `AddSetLoggingLevelFilter` | Logging level handlers |
|
||||
|
||||
The Tasks extension and ASP.NET Core tool authorization use alternate-result `tools/call` filters.
|
||||
Alternate-result filters run in registration order outside the ordinary `AddCallToolFilter`
|
||||
pipeline. The Tasks filter creates a task at its position in that order and runs its remaining
|
||||
pipeline in the background. Consequently, alternate-result filters registered before Tasks run
|
||||
before task creation, while those registered after Tasks run in the background before the
|
||||
ordinary filters and tool. ASP.NET Core authorization is registered before Tasks, so an
|
||||
unauthorized call is rejected without creating a task.
|
||||
|
||||
Ordinary call-tool filters run exactly once after all alternate-result filters and before the tool.
|
||||
For task-backed calls, ordinary filters execute in the background after task creation. An explicit
|
||||
`CallToolWithAlternateHandler` remains a full replacement and cannot be combined with ordinary
|
||||
call-tool filters.
|
||||
|
||||
Configure `WithTasks` before adding ordinary call-tool filters. Tasks validates this ordering when
|
||||
its filter is installed because it must execute outside the ordinary call-tool pipeline.
|
||||
|
||||
## Message Filters
|
||||
|
||||
In addition to the request-specific filters above, there are low-level message filters that intercept all JSON-RPC messages before they are routed to specific handlers.
|
||||
|
|
|
|||
|
|
@ -79,6 +79,17 @@ When tasks are enabled with `WithTasks` the SDK automatically:
|
|||
- Plumbs a `CancellationToken` through to the tool that fires when the client invokes
|
||||
`tasks/cancel`, so cancellation propagates cooperatively.
|
||||
|
||||
Alternate-result `tools/call` filters run in registration order, with the Tasks filter creating a
|
||||
task at its position in that order. Filters before Tasks run before task creation. Filters after
|
||||
Tasks run in the background before the ordinary filter pipeline. ASP.NET Core tool authorization
|
||||
uses an alternate-result filter registered before Tasks, so an unauthorized call does not create
|
||||
a task.
|
||||
|
||||
Ordinary `tools/call` filters still run exactly once for task-backed calls. They execute in the
|
||||
background after the task record is created and before the tool body, so validation and telemetry
|
||||
continue to apply. Each background invocation gets an independent DI scope that remains alive
|
||||
until the tool pipeline completes.
|
||||
|
||||
For production scenarios that need durability, session isolation, multi-process routing, or
|
||||
TTL-based cleanup, implement <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> yourself
|
||||
(see the [Implementing a custom task store](#implementing-a-custom-task-store) section).
|
||||
|
|
@ -126,28 +137,6 @@ options.Handlers.CallToolWithAlternateHandler = async (context, ct) =>
|
|||
> are mutually exclusive. Setting one while the other is already non-null throws
|
||||
> `InvalidOperationException` at the property setter.
|
||||
|
||||
#### Task scope for server-initiated requests
|
||||
|
||||
When you start background work from a custom <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
|
||||
(rather than the SDK's auto-wrapping), use <xref:ModelContextProtocol.Extensions.Tasks.McpTasksServerExtensions.CreateMcpTaskScope*>
|
||||
to route elicitation, sampling, and `roots/list` calls through the task store as input requests
|
||||
instead of direct JSON-RPC messages:
|
||||
|
||||
```csharp
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
|
||||
using (server.CreateMcpTaskScope(taskId, taskStore))
|
||||
{
|
||||
// ElicitAsync/SampleAsync/RequestRootsAsync calls in here are surfaced as
|
||||
// entries in the task's inputRequests, then await client responses via tasks/update.
|
||||
var elicit = await server.ElicitAsync(elicitParams, ct);
|
||||
}
|
||||
```
|
||||
|
||||
`CreateMcpTaskScope` returns an `IDisposable` that restores the prior ambient context on
|
||||
`Dispose`. The scope is established automatically for `[McpServerTool]` methods that run via
|
||||
`WithTasks`, so this API is only needed for custom handlers.
|
||||
|
||||
### Client usage
|
||||
|
||||
#### Automatic polling
|
||||
|
|
|
|||
Loading…
Reference in a new issue