Extract Tasks into the ModelContextProtocol.Extensions.Tasks extension package (#1693)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
This commit is contained in:
parent
ffc557f920
commit
2ca96f548b
63 changed files with 2559 additions and 1895 deletions
|
|
@ -69,6 +69,7 @@
|
|||
<Project Path="src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj" />
|
||||
<Project Path="src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj" />
|
||||
<Project Path="src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.csproj" />
|
||||
<Project Path="src/ModelContextProtocol.Extensions.Tasks/ModelContextProtocol.Extensions.Tasks.csproj" />
|
||||
<Project Path="src/ModelContextProtocol/ModelContextProtocol.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
|
|
|
|||
|
|
@ -590,7 +590,7 @@ In stateless mode, each HTTP request creates and disposes a short-lived `McpServ
|
|||
|
||||
## Tasks and session modes
|
||||
|
||||
[Tasks](xref:tasks) enable a "call-now, fetch-later" pattern for long-running tool calls. Task support depends on having an <xref:ModelContextProtocol.Server.IMcpTaskStore> configured (`McpServerOptions.TaskStore`), and behavior differs between session modes.
|
||||
[Tasks](xref:tasks) enable a "call-now, fetch-later" pattern for long-running tool calls. Task support depends on having an <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> configured (enabled via `WithTasks`), and behavior differs between session modes.
|
||||
|
||||
### Stateless mode
|
||||
|
||||
|
|
@ -600,9 +600,9 @@ In stateless mode, there is no `SessionId`, so the task store does not apply ses
|
|||
|
||||
### Stateful mode
|
||||
|
||||
In stateful mode, the `IMcpTaskStore` receives the session's `SessionId` on every operation — `CreateTaskAsync`, `GetTaskAsync`, `ListTasksAsync`, `CancelTaskAsync`, etc. The built-in <xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> enforces session isolation: tasks created in one session cannot be accessed from another.
|
||||
In stateful mode, the `IMcpTaskStore` receives the session's `SessionId` on every operation: `CreateTaskAsync`, `GetTaskAsync`, `ListTasksAsync`, `CancelTaskAsync`, etc. The built-in <xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> enforces session isolation: tasks created in one session cannot be accessed from another.
|
||||
|
||||
Tasks can outlive individual HTTP requests because the tool executes in the background after returning the initial `CreateTaskResult`. Task cleanup is governed by the task's TTL (time-to-live), not by session termination. However, the `InMemoryMcpTaskStore` loses all tasks if the server process restarts. For durable tasks, implement a custom <xref:ModelContextProtocol.Server.IMcpTaskStore> backed by an external store. See [Implementing a custom task store](xref:tasks#implementing-a-custom-task-store) for guidance.
|
||||
Tasks can outlive individual HTTP requests because the tool executes in the background after returning the initial `CreateTaskResult`. Task cleanup is governed by the task's TTL (time-to-live), not by session termination. However, the `InMemoryMcpTaskStore` loses all tasks if the server process restarts. For durable tasks, implement a custom <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> backed by an external store. See [Implementing a custom task store](xref:tasks#implementing-a-custom-task-store) for guidance.
|
||||
|
||||
### Task cancellation vs request cancellation
|
||||
|
||||
|
|
@ -654,7 +654,7 @@ The `EventStreamStore` itself has TTL-based limits (default: 2-hour event expira
|
|||
|
||||
### With tasks (experimental)
|
||||
|
||||
[Tasks](xref:tasks) are an experimental feature that enables a "call-now, fetch-later" pattern for long-running tool calls. When a client sends a task-augmented `tools/call` request, the server creates a task record in the <xref:ModelContextProtocol.Server.IMcpTaskStore>, starts the tool handler as a fire-and-forget background task, and returns the task ID immediately — the POST response completes **before the handler starts its real work**.
|
||||
[Tasks](xref:tasks) are an experimental feature that enables a "call-now, fetch-later" pattern for long-running tool calls. When a client sends a task-augmented `tools/call` request, the server creates a task record in the <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore>, starts the tool handler as a fire-and-forget background task, and returns the task ID immediately, so the POST response completes **before the handler starts its real work**.
|
||||
|
||||
This means:
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ and the client polls for status, optionally exchanging additional input along th
|
|||
|
||||
A client opts into tasks on a per-request basis by including the `io.modelcontextprotocol/tasks`
|
||||
extension key in the request's `_meta`. When that opt-in is present, the server **may** respond
|
||||
with a <xref:ModelContextProtocol.Protocol.CreateTaskResult> instead of the standard result
|
||||
with a <xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult> instead of the standard result
|
||||
(e.g., <xref:ModelContextProtocol.Protocol.CallToolResult>). The client then polls `tasks/get`
|
||||
until the task reaches a terminal state.
|
||||
|
||||
|
|
@ -39,36 +39,35 @@ the extension opt-in. The SDK enforces this on the server side.
|
|||
└──→ Failed (terminal — JSON-RPC errors only)
|
||||
```
|
||||
|
||||
<xref:ModelContextProtocol.Protocol.McpTaskStatus> wire values are serialized in snake_case:
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus> wire values are serialized in snake_case:
|
||||
`working`, `input_required`, `completed`, `cancelled`, `failed`.
|
||||
|
||||
The discriminator field <xref:ModelContextProtocol.Protocol.Result.ResultType?displayProperty=nameWithType>
|
||||
on the response payload is `"task"` for <xref:ModelContextProtocol.Protocol.CreateTaskResult>
|
||||
on the response payload is `"task"` for <xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult>
|
||||
and `"complete"` for ordinary results.
|
||||
|
||||
### Server configuration
|
||||
|
||||
#### Using the task store
|
||||
|
||||
The easiest way to enable tasks is to set an <xref:ModelContextProtocol.Server.IMcpTaskStore>
|
||||
on <xref:ModelContextProtocol.Server.McpServerOptions.TaskStore?displayProperty=nameWithType>.
|
||||
The SDK ships <xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> for development and tests:
|
||||
Tasks support lives in the `ModelContextProtocol.Extensions.Tasks` package. The easiest way to
|
||||
enable tasks is to call <xref:ModelContextProtocol.Extensions.Tasks.McpTasksBuilderExtensions.WithTasks*>
|
||||
on the server builder, passing an <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore>.
|
||||
The SDK ships <xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> for development and tests:
|
||||
|
||||
```csharp
|
||||
#pragma warning disable MCPEXP001
|
||||
|
||||
builder.Services.AddMcpServer(options =>
|
||||
{
|
||||
options.TaskStore = new InMemoryMcpTaskStore();
|
||||
})
|
||||
.WithTools<MyTools>();
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
|
||||
builder.Services.AddMcpServer()
|
||||
.WithTools<MyTools>()
|
||||
.WithTasks(new InMemoryMcpTaskStore());
|
||||
```
|
||||
|
||||
When a `TaskStore` is configured the SDK automatically:
|
||||
When tasks are enabled with `WithTasks` the SDK automatically:
|
||||
|
||||
- Wires `tasks/get`, `tasks/update`, and `tasks/cancel` handlers from the store. Explicit
|
||||
handlers in <xref:ModelContextProtocol.Server.McpServerOptions.Handlers> still take precedence
|
||||
for any slot they fill.
|
||||
- Wires the `tasks/get`, `tasks/update`, and `tasks/cancel` handlers from the store.
|
||||
- Advertises the `io.modelcontextprotocol/tasks` extension in
|
||||
<xref:ModelContextProtocol.Protocol.ServerCapabilities.Extensions?displayProperty=nameWithType>.
|
||||
- Wraps each `[McpServerTool]` invocation so that, when the client opts in to the extension,
|
||||
|
|
@ -81,40 +80,21 @@ When a `TaskStore` is configured the SDK automatically:
|
|||
`tasks/cancel`, so cancellation propagates cooperatively.
|
||||
|
||||
For production scenarios that need durability, session isolation, multi-process routing, or
|
||||
TTL-based cleanup, implement <xref:ModelContextProtocol.Server.IMcpTaskStore> yourself
|
||||
TTL-based cleanup, implement <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> yourself
|
||||
(see [Implementing a custom task store](#implementing-a-custom-task-store) below).
|
||||
|
||||
#### Custom task handlers
|
||||
|
||||
For full control without a store, set the handlers directly. Each handler is an
|
||||
<xref:ModelContextProtocol.Server.McpRequestHandler`2> that receives an
|
||||
<xref:ModelContextProtocol.Server.RequestContext`1> with typed parameters:
|
||||
|
||||
```csharp
|
||||
options.Handlers.GetTaskHandler = (context, ct) =>
|
||||
{
|
||||
var taskId = context.Params!.TaskId;
|
||||
// … look up state and return one of the GetTaskResult subtypes.
|
||||
return new ValueTask<GetTaskResult>(new WorkingTaskResult { TaskId = taskId, /* … */ });
|
||||
};
|
||||
|
||||
options.Handlers.UpdateTaskHandler = (context, ct) => /* return ValueTask<UpdateTaskResult> */;
|
||||
options.Handlers.CancelTaskHandler = (context, ct) => /* return ValueTask<CancelTaskResult> */;
|
||||
```
|
||||
|
||||
> **Important**: configure all three lifecycle handlers (or use a `TaskStore`) before opting
|
||||
> into task responses. If a tool handler returns a `CreateTaskResult` but no `tasks/get`
|
||||
> handler is wired, the server throws `InvalidOperationException` at request time so misconfigured
|
||||
> deployments fail loudly instead of shipping unpollable tasks.
|
||||
|
||||
#### Returning a task from a tool handler
|
||||
|
||||
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
|
||||
returns <xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1>, so each invocation can choose
|
||||
between an immediate result and a background task:
|
||||
For full control without the store's auto-wrapping, set
|
||||
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>.
|
||||
It returns a <xref:ModelContextProtocol.Protocol.ResultOrAlternate`1>, so each invocation can choose
|
||||
between an immediate result and an alternate result such as a
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult>:
|
||||
|
||||
```csharp
|
||||
options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
|
||||
options.Handlers.CallToolWithAlternateHandler = async (context, ct) =>
|
||||
{
|
||||
if (ShouldRunInline(context.Params!))
|
||||
{
|
||||
|
|
@ -122,7 +102,7 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
|
|||
}
|
||||
|
||||
var taskId = await StartBackgroundWorkAsync(context.Params!, ct);
|
||||
return new CreateTaskResult
|
||||
var created = new CreateTaskResult
|
||||
{
|
||||
TaskId = taskId,
|
||||
Status = McpTaskStatus.Working,
|
||||
|
|
@ -130,22 +110,32 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
|
|||
LastUpdatedAt = DateTimeOffset.UtcNow,
|
||||
PollIntervalMs = 1000,
|
||||
};
|
||||
|
||||
return new ResultOrAlternate<CallToolResult>(created, McpTasksJsonContext.Default.CreateTaskResult);
|
||||
};
|
||||
```
|
||||
|
||||
> This low-level handler is mutually exclusive with `WithTasks`. When a store is configured, the
|
||||
> SDK does the wrapping for you and throws `InvalidOperationException` if the alternate handler also
|
||||
> returns an alternate. Use one mechanism or the other. When you return a task this way you are also
|
||||
> responsible for serving `tasks/get`, `tasks/update`, and `tasks/cancel`, which the store provides
|
||||
> automatically.
|
||||
|
||||
> <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolHandler?displayProperty=nameWithType>
|
||||
> and <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
|
||||
> and <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
|
||||
> 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.CallToolWithTaskHandler?displayProperty=nameWithType>
|
||||
(rather than the SDK's auto-wrapping), use <xref:ModelContextProtocol.Server.McpServer.CreateMcpTaskScope*>
|
||||
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
|
||||
|
|
@ -156,13 +146,13 @@ using (server.CreateMcpTaskScope(taskId, taskStore))
|
|||
|
||||
`CreateMcpTaskScope` returns an `IDisposable` that restores the prior ambient context on
|
||||
`Dispose`. The scope is established automatically for `[McpServerTool]` methods that run via
|
||||
`McpServerOptions.TaskStore`, so this API is only needed for custom handlers.
|
||||
`WithTasks`, so this API is only needed for custom handlers.
|
||||
|
||||
### Client usage
|
||||
|
||||
#### Automatic polling
|
||||
|
||||
<xref:ModelContextProtocol.Client.McpClient.CallToolAsync(ModelContextProtocol.Protocol.CallToolRequestParams,System.Threading.CancellationToken)>
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CallToolWithPollingAsync*>
|
||||
handles the full task lifecycle automatically:
|
||||
|
||||
- Injects the `io.modelcontextprotocol/tasks` extension capability into the request's `_meta`.
|
||||
|
|
@ -176,21 +166,25 @@ handles the full task lifecycle automatically:
|
|||
or throws <xref:ModelContextProtocol.McpException> on `Failed`/`Cancelled`.
|
||||
|
||||
```csharp
|
||||
var result = await client.CallToolAsync(
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
|
||||
var result = await client.CallToolWithPollingAsync(
|
||||
new CallToolRequestParams { Name = "long-running-tool", Arguments = arguments },
|
||||
cancellationToken);
|
||||
cancellationToken: cancellationToken);
|
||||
```
|
||||
|
||||
#### Manual control
|
||||
|
||||
Use <xref:ModelContextProtocol.Client.McpClient.CallToolRawAsync*> to receive the raw
|
||||
<xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1> without auto-polling, then drive the
|
||||
lifecycle yourself using <xref:ModelContextProtocol.Client.McpClient.GetTaskAsync*>,
|
||||
<xref:ModelContextProtocol.Client.McpClient.UpdateTaskAsync*>, and
|
||||
<xref:ModelContextProtocol.Client.McpClient.CancelTaskAsync*>:
|
||||
Use <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CallToolAsTaskAsync*> to receive the raw
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.ResultOrCreatedTask`1> without auto-polling, then drive the
|
||||
lifecycle yourself using <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.GetTaskAsync*>,
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.UpdateTaskAsync*>, and
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CancelTaskAsync*>:
|
||||
|
||||
```csharp
|
||||
var raw = await client.CallToolRawAsync(requestParams, cancellationToken);
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
|
||||
var raw = await client.CallToolAsTaskAsync(requestParams, cancellationToken);
|
||||
if (raw.IsTask)
|
||||
{
|
||||
var taskId = raw.TaskCreated!.TaskId;
|
||||
|
|
@ -206,17 +200,17 @@ if (raw.IsTask)
|
|||
|
||||
#### Stuck-task detector
|
||||
|
||||
`CallToolAsync` includes a safety net for misbehaving servers: if the task stays in
|
||||
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> across many consecutive polls
|
||||
`CallToolWithPollingAsync` includes a safety net for misbehaving servers: if the task stays in
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.InputRequired> across many consecutive polls
|
||||
without exposing any new input request keys (i.e. every previously requested input has already
|
||||
been resolved by the client and yet the server keeps returning `InputRequired`), the client
|
||||
gives up, issues a best-effort `tasks/cancel`, and throws
|
||||
<xref:ModelContextProtocol.McpException>. This guards against a server that never transitions
|
||||
out of `InputRequired` and prevents an unbounded poll loop.
|
||||
|
||||
The threshold defaults to `60` consecutive stuck polls and is configurable via
|
||||
<xref:ModelContextProtocol.Client.McpClientOptions.MaxConsecutiveStuckPolls>. The effective
|
||||
wall-clock timeout is roughly `MaxConsecutiveStuckPolls * pollIntervalMs`, so tune the option
|
||||
The threshold defaults to `60` consecutive stuck polls and is configurable via the
|
||||
`maxConsecutiveStuckPolls` parameter on `CallToolWithPollingAsync`. The effective
|
||||
wall-clock timeout is roughly `maxConsecutiveStuckPolls * pollIntervalMs`, so tune the value
|
||||
with the server-side poll cadence in mind. Setting it too low risks false positives for servers
|
||||
that are slow to surface follow-up input requests; setting it too high can mask misbehaving
|
||||
servers.
|
||||
|
|
@ -224,11 +218,11 @@ servers.
|
|||
### Input requests (multi-round-trip)
|
||||
|
||||
When a task needs additional input from the client, the server transitions it to
|
||||
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> and returns the outstanding
|
||||
requests in <xref:ModelContextProtocol.Protocol.InputRequiredTaskResult.InputRequests>. Each
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.InputRequired> and returns the outstanding
|
||||
requests in <xref:ModelContextProtocol.Extensions.Tasks.InputRequiredTaskResult.InputRequests>. Each
|
||||
entry is an arbitrary key paired with a `{ method, params }` envelope representing an
|
||||
equivalent standalone server-to-client request. The client provides answers via
|
||||
<xref:ModelContextProtocol.Client.McpClient.UpdateTaskAsync*>, keyed by the same identifiers.
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.UpdateTaskAsync*>, keyed by the same identifiers.
|
||||
|
||||
Supported input request methods:
|
||||
|
||||
|
|
@ -241,34 +235,34 @@ Per SEP-2663:
|
|||
|
||||
- Each input request key **must** be unique over the lifetime of the task.
|
||||
- Clients **should** deduplicate keys across polls so a request is only presented to the user
|
||||
or model once. `CallToolAsync` does this automatically.
|
||||
or model once. `CallToolWithPollingAsync` does this automatically.
|
||||
- Servers **should** ignore `inputResponses` entries whose key does not currently correspond to
|
||||
an outstanding request, including responses for terminal-state tasks.
|
||||
<xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> follows this rule.
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> follows this rule.
|
||||
|
||||
### Implementing a custom task store
|
||||
|
||||
Implement <xref:ModelContextProtocol.Server.IMcpTaskStore> for production scenarios. Key
|
||||
Implement <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> for production scenarios. Key
|
||||
requirements drawn from the SEP and the SDK contract:
|
||||
|
||||
1. **Thread safety** — every method may be called concurrently.
|
||||
2. **Idempotent terminal transitions** —
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCompletedAsync*>,
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetFailedAsync*>, and
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCancelledAsync*> must be no-ops on a task
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetCompletedAsync*>,
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetFailedAsync*>, and
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetCancelledAsync*> must be no-ops on a task
|
||||
that is already in a terminal state so a late cancellation cannot overwrite a result.
|
||||
3. **`InputResponseReceived` event** — after persisting an input response inside
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.ResolveInputRequestsAsync*>, raise
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType>
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.ResolveInputRequestsAsync*>, raise
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType>
|
||||
for each resolved entry. This is the only mechanism that wakes a pending
|
||||
`server.ElicitAsync`/`server.SampleAsync` call waiting inside a task scope. In distributed
|
||||
deployments where a different server instance receives the `tasks/update`, the event must
|
||||
be propagated to the originating server (for example via Redis pub/sub, SignalR, or a custom
|
||||
transport).
|
||||
4. **Strong-consistency on `CreateTaskAsync`** —
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.CreateTaskAsync*> must not return until the
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.CreateTaskAsync*> must not return until the
|
||||
task is durably persisted, so that a subsequent
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.GetTaskAsync*> with the returned task ID
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.GetTaskAsync*> with the returned task ID
|
||||
resolves immediately — even from a different process or node. Stores backed by
|
||||
eventually-consistent storage must wait for the write to become visible (quorum
|
||||
acknowledgement, write-through, etc.) before returning. Required by SEP-2663 §306.
|
||||
|
|
@ -310,12 +304,12 @@ public sealed class MyTaskStore : IMcpTaskStore
|
|||
|
||||
### Status semantics
|
||||
|
||||
<xref:ModelContextProtocol.Protocol.McpTaskStatus.Completed> is the terminal status whenever the
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.Completed> is the terminal status whenever the
|
||||
underlying request produced its standard result, *including a
|
||||
<xref:ModelContextProtocol.Protocol.CallToolResult> with `IsError = true`*. Per SEP-2663,
|
||||
tool-level error results are not promoted to `Failed`.
|
||||
|
||||
<xref:ModelContextProtocol.Protocol.McpTaskStatus.Failed> is reserved for JSON-RPC protocol-level
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.Failed> is reserved for JSON-RPC protocol-level
|
||||
errors during execution — for example, a malformed request, or an unhandled exception in a custom
|
||||
handler that the SDK converts to a JSON-RPC error. Use
|
||||
<xref:ModelContextProtocol.Protocol.CallToolResult.IsError?displayProperty=nameWithType> for
|
||||
|
|
@ -341,7 +335,7 @@ In the built-in SDK pipeline, when a task is wrapped by a configured `TaskStore`
|
|||
|
||||
#### Immutable store design
|
||||
|
||||
<xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> uses immutable record snapshots with
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> uses immutable record snapshots with
|
||||
compare-and-swap updates for lock-free thread safety. `InputRequests` and `InputResponses` are
|
||||
exposed as `ImmutableDictionary<,>` so observers cannot mutate internal state.
|
||||
|
||||
|
|
@ -357,13 +351,13 @@ responsible for handling — or rejecting — the input requests surfaced throug
|
|||
|
||||
- **Server-push task status notifications (SEP-2575)**: not yet implemented. Clients rely on
|
||||
polling exclusively.
|
||||
- **Lazy task creation**: when a tool runs through `TaskStore`, the store's
|
||||
<xref:ModelContextProtocol.Server.IMcpTaskStore.CreateTaskAsync*> is invoked eagerly before
|
||||
- **Lazy task creation**: when a tool runs through the task store, the store's
|
||||
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.CreateTaskAsync*> is invoked eagerly before
|
||||
the inner handler runs, so tools that complete inline still incur a store write. There is
|
||||
currently no built-in deferral.
|
||||
- **Mid-execution promotion to task**: an `[McpServerTool]` method cannot start executing
|
||||
synchronously and then transition its remaining work to a background task. Use a custom
|
||||
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
|
||||
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
|
||||
if you need that pattern.
|
||||
- **`roots/list` as an input request**: the server SDK routes `RequestRootsAsync` through the
|
||||
task channel when called from inside a task scope, but the client SDK does not currently
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
// Demonstrates the MCP tasks extension (SEP-2663):
|
||||
//
|
||||
// - A server is configured with InMemoryMcpTaskStore so that any [McpServerTool] invocation
|
||||
// - A server is configured with .WithTasks(store) so that any [McpServerTool] invocation
|
||||
// becomes a background task when the client opts in via the per-request _meta marker.
|
||||
// - The client invokes the same tool two ways:
|
||||
// 1. CallToolAsync — the SDK auto-polls until the task completes and returns the final
|
||||
// CallToolResult, just like a synchronous call.
|
||||
// 2. CallToolRawAsync — the caller drives the lifecycle manually (GetTaskAsync polls,
|
||||
// CancelTaskAsync, etc.). Use this when you need to surface progress to a UI or stream
|
||||
// status updates rather than block on a single await.
|
||||
// - The client invokes the tool and manually drives the lifecycle via GetTaskAsync.
|
||||
//
|
||||
// Both server and client are wired together in-process over an in-memory pipe so the sample
|
||||
// is self-contained — no separate server process or HTTP transport required.
|
||||
|
||||
#pragma warning disable MCPEXP001, MCPEXP002, MCPEXP004
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Extensions.Tasks;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
|
|
@ -21,28 +20,25 @@ using System.Text.Json;
|
|||
|
||||
Pipe clientToServerPipe = new(), serverToClientPipe = new();
|
||||
|
||||
await using McpServer server = McpServer.Create(
|
||||
new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()),
|
||||
new McpServerOptions
|
||||
{
|
||||
// Setting TaskStore is all that's needed for [McpServerTool]-attributed tools to be
|
||||
// automatically wrapped as background tasks when the client opts in.
|
||||
TaskStore = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 },
|
||||
ToolCollection = [McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })],
|
||||
});
|
||||
var store = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 };
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithTools([McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })])
|
||||
.WithTasks(store);
|
||||
services.AddSingleton<ITransport>(new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()));
|
||||
|
||||
await using var serviceProvider = services.BuildServiceProvider();
|
||||
var server = serviceProvider.GetRequiredService<McpServer>();
|
||||
_ = server.RunAsync();
|
||||
|
||||
await using McpClient client = await McpClient.CreateAsync(
|
||||
new StreamClientTransport(clientToServerPipe.Writer.AsStream(), serverToClientPipe.Reader.AsStream()));
|
||||
new StreamClientTransport(
|
||||
serverInput: clientToServerPipe.Writer.AsStream(),
|
||||
serverOutput: serverToClientPipe.Reader.AsStream()));
|
||||
|
||||
Console.WriteLine("=== CallToolAsync (auto-poll) ===");
|
||||
var auto = await client.CallToolAsync(
|
||||
new CallToolRequestParams { Name = "run-report" });
|
||||
Console.WriteLine($" result: {((TextContentBlock)auto.Content[0]).Text}");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("=== CallToolRawAsync (manual poll) ===");
|
||||
var raw = await client.CallToolRawAsync(new CallToolRequestParams { Name = "run-report" });
|
||||
Console.WriteLine("=== CallToolAsTaskAsync (manual poll) ===");
|
||||
var raw = await client.CallToolAsTaskAsync(new CallToolRequestParams { Name = "run-report" });
|
||||
if (!raw.IsTask)
|
||||
{
|
||||
// Either the server doesn't advertise the tasks extension or it chose to run the call
|
||||
|
|
@ -88,9 +84,6 @@ while (true)
|
|||
continue;
|
||||
|
||||
case InputRequiredTaskResult inputRequired:
|
||||
// The auto-poll path (CallToolAsync above) routes these through the registered
|
||||
// ElicitationHandler/SamplingHandler automatically. The manual path needs to call
|
||||
// UpdateTaskAsync with responses for each outstanding key.
|
||||
Console.WriteLine($" poll {pollCount}: input requested ({inputRequired.InputRequests?.Count ?? 0} key(s))");
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
|
||||
<ProjectReference Include="..\..\src\ModelContextProtocol.Extensions.Tasks\ModelContextProtocol.Extensions.Tasks.csproj" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -944,25 +944,17 @@ public abstract partial class McpClient : McpSession
|
|||
McpJsonUtilities.JsonContext.Default.EmptyResult,
|
||||
cancellationToken: cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a tool on the server.
|
||||
/// </summary>
|
||||
/// <param name="toolName">The name of the tool to call on the server.</param>
|
||||
/// <param name="toolName">The name of the tool to invoke.</param>
|
||||
/// <param name="arguments">An optional dictionary of arguments to pass to the tool.</param>
|
||||
/// <param name="progress">An optional progress reporter for server notifications.</param>
|
||||
/// <param name="progress">An optional progress handler for tracking operation progress.</param>
|
||||
/// <param name="options">Optional request options including metadata, serialization settings, and progress tracking.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The <see cref="CallToolResult"/> from the tool execution.</returns>
|
||||
/// <returns>The result of the tool invocation.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="toolName"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
/// <remarks>
|
||||
/// This overload supports the tasks extension transparently. If the server responds with a
|
||||
/// task handle rather than an immediate result, this method polls <c>tasks/get</c> until the
|
||||
/// task completes, dispatching any <see cref="McpTaskStatus.InputRequired"/> entries through
|
||||
/// the client's registered sampling and elicitation handlers along the way. Use
|
||||
/// <see cref="CallToolRawAsync"/> to disable automatic polling.
|
||||
/// </remarks>
|
||||
public ValueTask<CallToolResult> CallToolAsync(
|
||||
string toolName,
|
||||
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||
|
|
@ -1033,221 +1025,19 @@ public abstract partial class McpClient : McpSession
|
|||
/// <returns>The result of the request.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
/// <remarks>
|
||||
/// This method automatically includes the <c>io.modelcontextprotocol/tasks</c> extension capability
|
||||
/// in the request metadata. If the server returns a task handle instead of an immediate result,
|
||||
/// this method transparently polls <c>tasks/get</c> until the task completes, fails, or is cancelled.
|
||||
/// Use <see cref="CallToolRawAsync"/>
|
||||
/// to receive the raw <see cref="ResultOrCreatedTask{TResult}"/> without automatic polling.
|
||||
/// </remarks>
|
||||
public async ValueTask<CallToolResult> CallToolAsync(
|
||||
public ValueTask<CallToolResult> CallToolAsync(
|
||||
CallToolRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
|
||||
var augmented = await CallToolRawAsync(requestParams, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!augmented.IsTask)
|
||||
{
|
||||
return augmented.Result!;
|
||||
}
|
||||
|
||||
return await PollTaskToCompletionAsync(augmented.TaskCreated!, cancellationToken).ConfigureAwait(false);
|
||||
return SendRequestAsync(
|
||||
RequestMethods.ToolsCall,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResult,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Polls a task until it reaches a terminal state and returns the final <see cref="CallToolResult"/>.
|
||||
/// </summary>
|
||||
private async ValueTask<CallToolResult> PollTaskToCompletionAsync(
|
||||
CreateTaskResult taskCreated,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// If the server claims InputRequired but never publishes new input requests after we have
|
||||
// already responded to everything it asked for, treat that as a stuck task. The client
|
||||
// can still cancel earlier via cancellationToken; this guard prevents an unbounded poll
|
||||
// loop when the server is misbehaving. The threshold is configurable via
|
||||
// McpClientOptions.MaxConsecutiveStuckPolls.
|
||||
int maxConsecutiveStuckPolls = MaxConsecutiveStuckPolls;
|
||||
|
||||
string taskId = taskCreated.TaskId;
|
||||
long pollIntervalMs = taskCreated.PollIntervalMs ?? 1000;
|
||||
HashSet<string>? resolvedRequestKeys = null;
|
||||
bool isFirstPoll = true;
|
||||
int consecutiveStuckPolls = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Skip the delay before the first poll: many tasks complete almost immediately and we
|
||||
// don't want to pay the poll interval as gratuitous latency.
|
||||
if (!isFirstPoll)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(pollIntervalMs), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
isFirstPoll = false;
|
||||
|
||||
var taskResult = await GetTaskAsync(taskId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Update poll interval if the server changed it.
|
||||
if (taskResult.PollIntervalMs is { } newInterval)
|
||||
{
|
||||
pollIntervalMs = newInterval;
|
||||
}
|
||||
|
||||
switch (taskResult)
|
||||
{
|
||||
case CompletedTaskResult completed:
|
||||
return JsonSerializer.Deserialize(completed.Result, McpJsonUtilities.JsonContext.Default.CallToolResult)
|
||||
?? throw new JsonException("Failed to deserialize CallToolResult from completed task.");
|
||||
|
||||
case FailedTaskResult failed:
|
||||
throw new McpException($"Task '{taskId}' failed: {failed.Error}");
|
||||
|
||||
case CancelledTaskResult:
|
||||
throw new OperationCanceledException($"Task '{taskId}' was cancelled by the server.");
|
||||
|
||||
case InputRequiredTaskResult inputRequired:
|
||||
// Dedup: only resolve input requests we haven't already responded to.
|
||||
var newRequests = new Dictionary<string, InputRequest>();
|
||||
if (inputRequired.InputRequests is { } incomingRequests)
|
||||
{
|
||||
foreach (var kvp in incomingRequests)
|
||||
{
|
||||
if (resolvedRequestKeys is null || !resolvedRequestKeys.Contains(kvp.Key))
|
||||
{
|
||||
newRequests[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newRequests.Count > 0)
|
||||
{
|
||||
consecutiveStuckPolls = 0;
|
||||
|
||||
IDictionary<string, InputResponse> inputResponses;
|
||||
try
|
||||
{
|
||||
inputResponses = await ResolveInputRequestsAsync(newRequests, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The input handler failed (e.g., ElicitationHandler threw or no handler was registered).
|
||||
// Best-effort cancel of the server-side task so it doesn't stay stuck in InputRequired
|
||||
// until TTL expires.
|
||||
try
|
||||
{
|
||||
await CancelTaskAsync(taskId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow secondary failures; we're already propagating the original exception.
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
await UpdateTaskAsync(new UpdateTaskRequestParams
|
||||
{
|
||||
TaskId = taskId,
|
||||
InputResponses = inputResponses,
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
resolvedRequestKeys ??= new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var key in inputResponses.Keys)
|
||||
{
|
||||
resolvedRequestKeys.Add(key);
|
||||
}
|
||||
}
|
||||
else if (++consecutiveStuckPolls >= maxConsecutiveStuckPolls)
|
||||
{
|
||||
// Best-effort cancel of the server-side task so it doesn't leak until TTL expires.
|
||||
try
|
||||
{
|
||||
await CancelTaskAsync(taskId, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow secondary failures; we're already propagating an exception.
|
||||
}
|
||||
|
||||
throw new McpException(
|
||||
$"Task '{taskId}' has remained in '{McpTaskStatus.InputRequired}' for {maxConsecutiveStuckPolls} consecutive polls " +
|
||||
"without publishing new input requests after all previously requested inputs were resolved.");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WorkingTaskResult:
|
||||
// Continue polling.
|
||||
consecutiveStuckPolls = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new McpException(
|
||||
$"Unexpected task result type '{taskResult.GetType().Name}' for task '{taskId}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes a tool on the server with task extension support, returning the raw response
|
||||
/// without automatic polling. The caller is responsible for handling task lifecycle.
|
||||
/// </summary>
|
||||
/// <param name="requestParams">The request parameters to send. The tasks extension capability will be injected into the request metadata.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="ResultOrCreatedTask{TResult}"/> that is either an immediate result or a task handle.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Unlike <see cref="CallToolAsync(CallToolRequestParams, CancellationToken)"/>, this method does not
|
||||
/// automatically poll for task completion. If the server returns a <see cref="CreateTaskResult"/>,
|
||||
/// the caller must manage polling via <see cref="GetTaskAsync(string, CancellationToken)"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public async ValueTask<ResultOrCreatedTask<CallToolResult>> CallToolRawAsync(
|
||||
CallToolRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
|
||||
var paramsWithMeta = new CallToolRequestParams
|
||||
{
|
||||
Name = requestParams.Name,
|
||||
Arguments = requestParams.Arguments,
|
||||
// The SEP-2663 Tasks extension requires the 2026-07-28 or later protocol revision. On an older session, send a plain tools/call
|
||||
// (no task capability envelope) so the server returns a direct CallToolResult and never
|
||||
// creates a task.
|
||||
Meta = IsJuly2026OrLaterProtocol() ? GetMetaWithTaskCapability(requestParams.Meta) : requestParams.Meta,
|
||||
};
|
||||
|
||||
JsonRpcRequest jsonRpcRequest = new()
|
||||
{
|
||||
Method = RequestMethods.ToolsCall,
|
||||
Params = JsonSerializer.SerializeToNode(paramsWithMeta, McpJsonUtilities.JsonContext.Default.CallToolRequestParams),
|
||||
};
|
||||
|
||||
JsonRpcResponse response = await SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Discriminate based on resultType field.
|
||||
if (response.Result is JsonObject resultObj &&
|
||||
resultObj.TryGetPropertyValue("resultType", out var resultTypeNode) &&
|
||||
resultTypeNode?.GetValue<string>() == "task")
|
||||
{
|
||||
var taskCreated = resultObj.Deserialize(McpJsonUtilities.JsonContext.Default.CreateTaskResult)
|
||||
?? throw new JsonException("Failed to deserialize CreateTaskResult from response.");
|
||||
return new ResultOrCreatedTask<CallToolResult>(taskCreated);
|
||||
}
|
||||
|
||||
var callToolResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.CallToolResult)
|
||||
?? throw new JsonException("Failed to deserialize CallToolResult from response.");
|
||||
return new ResultOrCreatedTask<CallToolResult>(callToolResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the logging level for the server to control which log messages are sent to the client.
|
||||
/// </summary>
|
||||
|
|
@ -1302,111 +1092,6 @@ public abstract partial class McpClient : McpSession
|
|||
McpJsonUtilities.JsonContext.Default.EmptyResult,
|
||||
cancellationToken: cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the current state of a task from the server.
|
||||
/// </summary>
|
||||
/// <param name="taskId">The stable identifier of the task to retrieve.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="GetTaskResult"/> subtype representing the current task state.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="taskId"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
public ValueTask<GetTaskResult> GetTaskAsync(
|
||||
string taskId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(taskId);
|
||||
|
||||
return GetTaskAsync(new GetTaskRequestParams { TaskId = taskId }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the current state of a task from the server.
|
||||
/// </summary>
|
||||
/// <param name="requestParams">The request parameters to send in the request.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A <see cref="GetTaskResult"/> subtype representing the current task state.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
public ValueTask<GetTaskResult> GetTaskAsync(
|
||||
GetTaskRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(GetTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksGet,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.GetTaskRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.GetTaskResult,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides input responses to a task that is in the <see cref="McpTaskStatus.InputRequired"/> state.
|
||||
/// </summary>
|
||||
/// <param name="requestParams">The request parameters containing the task ID and input responses.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The result acknowledging the update.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
public ValueTask<UpdateTaskResult> UpdateTaskAsync(
|
||||
UpdateTaskRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(UpdateTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksUpdate,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.UpdateTaskRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.UpdateTaskResult,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests cancellation of an in-progress task on the server.
|
||||
/// </summary>
|
||||
/// <param name="taskId">The stable identifier of the task to cancel.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The result acknowledging the cancellation request.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="taskId"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
public ValueTask<CancelTaskResult> CancelTaskAsync(
|
||||
string taskId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(taskId);
|
||||
|
||||
return CancelTaskAsync(new CancelTaskRequestParams { TaskId = taskId }, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests cancellation of an in-progress task on the server.
|
||||
/// </summary>
|
||||
/// <param name="requestParams">The request parameters to send in the request.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The result acknowledging the cancellation request.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
|
||||
public ValueTask<CancelTaskResult> CancelTaskAsync(
|
||||
CancelTaskRequestParams requestParams,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(requestParams);
|
||||
ThrowIfTasksNotSupported(nameof(CancelTaskAsync));
|
||||
|
||||
return SendRequestAsync(
|
||||
RequestMethods.TasksCancel,
|
||||
requestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CancelTaskRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CancelTaskResult,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Converts a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
|
||||
private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
|
||||
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
|
||||
{
|
||||
|
|
@ -1425,43 +1110,4 @@ public abstract partial class McpClient : McpSession
|
|||
return result;
|
||||
}
|
||||
|
||||
// Per SEP-2663 §51, the per-request opt-in uses the SEP-2575 capabilities envelope:
|
||||
// _meta/io.modelcontextprotocol/clientCapabilities/extensions/io.modelcontextprotocol/tasks = {}
|
||||
private static JsonObject GetMetaWithTaskCapability(JsonObject? existingMeta)
|
||||
{
|
||||
JsonObject meta = existingMeta is not null
|
||||
? (JsonObject)existingMeta.DeepClone()
|
||||
: [];
|
||||
|
||||
if (meta[MetaKeys.ClientCapabilities] is not JsonObject capsRoot)
|
||||
{
|
||||
capsRoot = [];
|
||||
meta[MetaKeys.ClientCapabilities] = capsRoot;
|
||||
}
|
||||
|
||||
if (capsRoot["extensions"] is not JsonObject extensionsRoot)
|
||||
{
|
||||
extensionsRoot = [];
|
||||
capsRoot["extensions"] = extensionsRoot;
|
||||
}
|
||||
|
||||
extensionsRoot.TryAdd(McpExtensions.Tasks, new JsonObject());
|
||||
return meta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws when the negotiated protocol version does not support the SEP-2663 Tasks extension. Tasks
|
||||
/// require the 2026-07-28 or later protocol revision, and a task id only ever exists when the session
|
||||
/// negotiated such a revision, so invoking <c>tasks/get</c>, <c>tasks/update</c>, or <c>tasks/cancel</c>
|
||||
/// on an older session is a programming error rather than a recoverable protocol condition.
|
||||
/// </summary>
|
||||
private void ThrowIfTasksNotSupported(string operationName)
|
||||
{
|
||||
if (!IsJuly2026OrLaterProtocol())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"'{operationName}' requires a newer protocol revision that supports tasks. " +
|
||||
$"The negotiated protocol version is '{NegotiatedProtocolVersion ?? "(none)"}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public abstract partial class McpClient : McpSession
|
|||
public abstract Task<ClientCompletionDetails> Completion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resolves input requests embedded in an <see cref="InputRequiredTaskResult"/> by dispatching
|
||||
/// Resolves input requests by dispatching
|
||||
/// each request to the appropriate registered handler.
|
||||
/// </summary>
|
||||
/// <param name="inputRequests">
|
||||
|
|
@ -82,16 +82,9 @@ public abstract partial class McpClient : McpSession
|
|||
/// </param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>A dictionary of responses keyed by the same identifiers as the input requests.</returns>
|
||||
private protected abstract ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
|
||||
public abstract ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
|
||||
IDictionary<string, InputRequest> inputRequests, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum number of consecutive stuck-in-<see cref="McpTaskStatus.InputRequired"/> polls
|
||||
/// allowed by <see cref="PollTaskToCompletionAsync"/> before the client cancels and throws.
|
||||
/// Sourced from <see cref="McpClientOptions.MaxConsecutiveStuckPolls"/>.
|
||||
/// </summary>
|
||||
private protected abstract int MaxConsecutiveStuckPolls { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Inspects a received cacheable result (<c>tools/list</c>, <c>prompts/list</c>, <c>resources/list</c>,
|
||||
/// <c>resources/templates/list</c>, or <c>resources/read</c>) so derived clients can emit diagnostics.
|
||||
|
|
|
|||
|
|
@ -179,10 +179,9 @@ internal sealed partial class McpClientImpl : McpClient
|
|||
public override Task<ClientCompletionDetails> Completion => _sessionHandler.CompletionTask;
|
||||
|
||||
/// <inheritdoc/>
|
||||
private protected override int MaxConsecutiveStuckPolls => _options.MaxConsecutiveStuckPolls;
|
||||
|
||||
/// <inheritdoc/>
|
||||
private protected override async ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
|
||||
public override async ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
|
||||
IDictionary<string, InputRequest> inputRequests,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -151,41 +151,4 @@ public sealed class McpClientOptions
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of consecutive task polls during which a task may report
|
||||
/// <see cref="McpTaskStatus.InputRequired"/> without publishing any new input requests, before
|
||||
/// the client treats the task as stuck, issues a best-effort <c>tasks/cancel</c>, and throws
|
||||
/// an <see cref="McpException"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The maximum number of consecutive stuck polls allowed. The default value is <c>60</c>.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This guard prevents an unbounded poll loop when the server keeps a task in
|
||||
/// <see cref="McpTaskStatus.InputRequired"/> but never publishes new input requests after the
|
||||
/// client has already responded to every previously surfaced request. It only affects the
|
||||
/// long-poll path used by <see cref="McpClient.CallToolAsync(CallToolRequestParams, CancellationToken)"/>;
|
||||
/// it does not affect direct <see cref="McpClient.GetTaskAsync(string, CancellationToken)"/> calls.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Callers should size this value with the configured server-side poll interval in mind: the
|
||||
/// effective wall-clock timeout is roughly <c>MaxConsecutiveStuckPolls * pollIntervalMs</c>.
|
||||
/// Setting this to a very small value can cause false positives for servers that are slow to
|
||||
/// surface follow-up input requests; setting it too large can mask misbehaving servers.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentOutOfRangeException">The value is less than <c>1</c>.</exception>
|
||||
public int MaxConsecutiveStuckPolls
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, "must be greater than or equal to 1.");
|
||||
}
|
||||
field = value;
|
||||
}
|
||||
} = 60;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,13 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
|
||||
|
|
@ -71,6 +78,20 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
|
||||
|
|
@ -113,6 +134,13 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
|
||||
|
|
@ -211,6 +239,13 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
|
||||
|
|
@ -225,6 +260,20 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
|
||||
|
|
@ -267,6 +316,13 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
|
||||
|
|
@ -365,6 +421,13 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
|
||||
|
|
@ -379,6 +442,20 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
|
||||
|
|
@ -421,6 +498,13 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
|
||||
|
|
@ -519,6 +603,13 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
|
||||
|
|
@ -533,6 +624,20 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
|
||||
|
|
@ -575,6 +680,13 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0001</DiagnosticId>
|
||||
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
|
||||
|
|
@ -624,6 +736,27 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
|
||||
|
|
@ -785,20 +918,6 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
|
||||
|
|
@ -813,34 +932,6 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
|
||||
|
|
@ -960,6 +1051,13 @@
|
|||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
|
||||
|
|
@ -995,6 +1093,27 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
|
||||
|
|
@ -1156,20 +1275,6 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
|
||||
|
|
@ -1184,34 +1289,6 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
|
||||
|
|
@ -1331,6 +1408,13 @@
|
|||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
|
||||
|
|
@ -1366,6 +1450,27 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
|
||||
|
|
@ -1527,20 +1632,6 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
|
||||
|
|
@ -1555,34 +1646,6 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
|
||||
|
|
@ -1702,6 +1765,13 @@
|
|||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
|
||||
|
|
@ -1737,6 +1807,27 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
|
||||
|
|
@ -1898,20 +1989,6 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
|
||||
|
|
@ -1926,34 +2003,6 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
|
||||
|
|
@ -2073,6 +2122,13 @@
|
|||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0002</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
|
||||
|
|
@ -2102,57 +2158,29 @@
|
|||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
|
||||
<DiagnosticId>CP0005</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
|
||||
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
|
||||
<DiagnosticId>CP0005</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
|
||||
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
|
||||
<DiagnosticId>CP0005</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
|
||||
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
</Suppression>
|
||||
<Suppression>
|
||||
<DiagnosticId>CP0011</DiagnosticId>
|
||||
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
|
||||
<DiagnosticId>CP0005</DiagnosticId>
|
||||
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
|
||||
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
|
||||
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
|
||||
<IsBaselineSuppression>true</IsBaselineSuppression>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,13 @@ public static partial class McpJsonUtilities
|
|||
return options;
|
||||
}
|
||||
|
||||
internal static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
|
||||
/// <summary>
|
||||
/// Gets the resolved <see cref="JsonTypeInfo{T}"/> for <typeparamref name="T"/> from the specified options.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type whose serialization metadata should be resolved.</typeparam>
|
||||
/// <param name="options">The serializer options providing the type-info resolver chain.</param>
|
||||
/// <returns>The resolved <see cref="JsonTypeInfo{T}"/>.</returns>
|
||||
public static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
|
||||
(JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
|
||||
|
||||
internal static JsonElement DefaultMcpToolSchema { get; } = ParseJsonElement("""{"type":"object"}"""u8);
|
||||
|
|
@ -120,12 +126,6 @@ public static partial class McpJsonUtilities
|
|||
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
|
||||
[JsonSerializable(typeof(RootsListChangedNotificationParams))]
|
||||
[JsonSerializable(typeof(ToolListChangedNotificationParams))]
|
||||
[JsonSerializable(typeof(TaskStatusNotificationParams))]
|
||||
[JsonSerializable(typeof(WorkingTaskNotificationParams))]
|
||||
[JsonSerializable(typeof(CompletedTaskNotificationParams))]
|
||||
[JsonSerializable(typeof(FailedTaskNotificationParams))]
|
||||
[JsonSerializable(typeof(CancelledTaskNotificationParams))]
|
||||
[JsonSerializable(typeof(InputRequiredTaskNotificationParams))]
|
||||
|
||||
// MCP Request Params / Results
|
||||
[JsonSerializable(typeof(CallToolRequestParams))]
|
||||
|
|
@ -174,19 +174,6 @@ public static partial class McpJsonUtilities
|
|||
[JsonSerializable(typeof(IDictionary<string, InputRequest>))]
|
||||
[JsonSerializable(typeof(IDictionary<string, InputResponse>))]
|
||||
|
||||
[JsonSerializable(typeof(GetTaskRequestParams))]
|
||||
[JsonSerializable(typeof(GetTaskResult))]
|
||||
[JsonSerializable(typeof(WorkingTaskResult))]
|
||||
[JsonSerializable(typeof(CompletedTaskResult))]
|
||||
[JsonSerializable(typeof(FailedTaskResult))]
|
||||
[JsonSerializable(typeof(CancelledTaskResult))]
|
||||
[JsonSerializable(typeof(InputRequiredTaskResult))]
|
||||
[JsonSerializable(typeof(UpdateTaskRequestParams))]
|
||||
[JsonSerializable(typeof(UpdateTaskResult))]
|
||||
[JsonSerializable(typeof(CancelTaskRequestParams))]
|
||||
[JsonSerializable(typeof(CancelTaskResult))]
|
||||
[JsonSerializable(typeof(CreateTaskResult))]
|
||||
|
||||
// MCP Content
|
||||
[JsonSerializable(typeof(ContentBlock))]
|
||||
[JsonSerializable(typeof(TextContentBlock))]
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Provides constants for well-known MCP extension identifiers.
|
||||
/// </summary>
|
||||
public static class McpExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// The extension identifier for the MCP Tasks extension.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When included in client per-request capabilities, indicates the client can handle
|
||||
/// <see cref="CreateTaskResult"/> in lieu of a standard result.
|
||||
/// See the <see href="https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md">SEP-2663</see>
|
||||
/// specification for details.
|
||||
/// </remarks>
|
||||
public const string Tasks = "io.modelcontextprotocol/tasks";
|
||||
}
|
||||
|
|
@ -53,24 +53,4 @@ public static class MetaKeys
|
|||
/// belonging to different subscriptions on a shared channel (especially STDIO).
|
||||
/// </remarks>
|
||||
public const string SubscriptionId = "io.modelcontextprotocol/subscriptionId";
|
||||
|
||||
/// <summary>
|
||||
/// The metadata key used to associate requests, responses, and notifications with a task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This constant defines the key <c>"io.modelcontextprotocol/related-task"</c> used in the
|
||||
/// <c>_meta</c> field to associate messages with their originating task across the entire
|
||||
/// request lifecycle.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For example, an elicitation that a task-augmented tool call depends on must share the
|
||||
/// same related task ID with that tool call's task.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For <c>tasks/get</c>, <c>tasks/list</c>, and <c>tasks/cancel</c> operations, this
|
||||
/// metadata should not be included as the taskId is already present in the message structure.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public const string RelatedTask = "io.modelcontextprotocol/related-task";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,16 +144,6 @@ public static class NotificationMethods
|
|||
/// </remarks>
|
||||
public const string CancelledNotification = "notifications/cancelled";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the notification sent by the server to push task status updates to subscribed clients.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
|
||||
/// Each notification carries a complete task state for the current status, identical to what
|
||||
/// <c>tasks/get</c> would have returned at that moment.
|
||||
/// </remarks>
|
||||
public const string TaskStatusNotification = "notifications/tasks/status";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the notification sent first on a <see cref="RequestMethods.SubscriptionsListen"/>
|
||||
/// response stream to indicate which notification types the server agreed to deliver.
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ namespace ModelContextProtocol.Protocol;
|
|||
/// </summary>
|
||||
public abstract class NotificationParams
|
||||
{
|
||||
/// <summary>Prevent external derivations.</summary>
|
||||
private protected NotificationParams()
|
||||
/// <summary>Initializes the base notification parameter type.</summary>
|
||||
protected NotificationParams()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,33 +125,6 @@ public static class RequestMethods
|
|||
/// </remarks>
|
||||
public const string Initialize = "initialize";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to poll for task completion.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
|
||||
/// Clients poll for task status by sending this request with the task ID.
|
||||
/// </remarks>
|
||||
public const string TasksGet = "tasks/get";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to provide input responses to a task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
|
||||
/// Used when a task has <c>input_required</c> status and the client needs to fulfill outstanding requests.
|
||||
/// </remarks>
|
||||
public const string TasksUpdate = "tasks/update";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to signal intent to cancel a task.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
|
||||
/// Cancellation is cooperative — the server decides whether and when to honor it.
|
||||
/// </remarks>
|
||||
public const string TasksCancel = "tasks/cancel";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the request method sent from the client to discover the server's protocol versions,
|
||||
/// capabilities, and metadata.
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ namespace ModelContextProtocol.Protocol;
|
|||
/// </remarks>
|
||||
public abstract class RequestParams
|
||||
{
|
||||
/// <summary>Prevent external derivations.</summary>
|
||||
private protected RequestParams()
|
||||
/// <summary>Initializes the base request parameter type.</summary>
|
||||
protected RequestParams()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ namespace ModelContextProtocol.Protocol;
|
|||
/// </summary>
|
||||
public abstract class Result
|
||||
{
|
||||
/// <summary>Prevent external derivations.</summary>
|
||||
private protected Result()
|
||||
/// <summary>Initializes the base result type.</summary>
|
||||
protected Result()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -27,11 +27,9 @@ public abstract class Result
|
|||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When set to <c>"complete"</c>, the result is a normal completed response.
|
||||
/// When set to <c>"input_required"</c>, the result is an <see cref="InputRequiredResult"/> indicating
|
||||
/// that additional input is needed before the request can be completed.
|
||||
/// When set to <c>"task"</c>, the result is a <see cref="CreateTaskResult"/> indicating that the server
|
||||
/// has created a long-running task in lieu of returning the result directly.
|
||||
/// When absent or set to <c>"complete"</c>, the result is a normal completed response.
|
||||
/// Other values discriminate alternate result subtypes so callers can choose the appropriate
|
||||
/// concrete payload to deserialize.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <value>Defaults to <see langword="null"/>, which is equivalent to <c>"complete"</c>.</value>
|
||||
|
|
|
|||
95
src/ModelContextProtocol.Core/Protocol/ResultOrAlternate.cs
Normal file
95
src/ModelContextProtocol.Core/Protocol/ResultOrAlternate.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace ModelContextProtocol.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of a request that may return either the standard result or an alternate
|
||||
/// <see cref="Result"/> subtype for scenarios like asynchronous task execution.
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The standard result type for the request (e.g., <see cref="CallToolResult"/>).</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Extensions that augment request handling (such as the Tasks extension) use this type to indicate
|
||||
/// that the server returned an alternate result instead of the normal one. The alternate carries its
|
||||
/// own <see cref="JsonTypeInfo"/> so the transport layer can serialize it without compile-time knowledge
|
||||
/// of the concrete type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use <see cref="IsAlternate"/> to determine which variant was returned, then access either
|
||||
/// <see cref="Result"/> for the immediate result or <see cref="Alternate"/> for the alternate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Experimental(Experimentals.Subclassing_DiagnosticId, UrlFormat = Experimentals.Subclassing_Url)]
|
||||
public class ResultOrAlternate<TResult> where TResult : Result
|
||||
{
|
||||
private readonly TResult? _result;
|
||||
private readonly Result? _alternate;
|
||||
private readonly JsonTypeInfo? _alternateTypeInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ResultOrAlternate{TResult}"/> with an immediate result.
|
||||
/// </summary>
|
||||
/// <param name="result">The standard result returned by the server.</param>
|
||||
public ResultOrAlternate(TResult result)
|
||||
{
|
||||
Throw.IfNull(result);
|
||||
_result = result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ResultOrAlternate{TResult}"/> with an alternate result.
|
||||
/// </summary>
|
||||
/// <param name="alternate">The alternate result.</param>
|
||||
/// <param name="alternateTypeInfo">The <see cref="JsonTypeInfo"/> used to serialize the alternate result.</param>
|
||||
private ResultOrAlternate(Result alternate, JsonTypeInfo alternateTypeInfo)
|
||||
{
|
||||
Throw.IfNull(alternate);
|
||||
Throw.IfNull(alternateTypeInfo);
|
||||
_alternate = alternate;
|
||||
_alternateTypeInfo = alternateTypeInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ResultOrAlternate{TResult}"/> that carries an alternate <see cref="Result"/> subtype
|
||||
/// (for example an <c>InputRequiredResult</c> or a task-creation result) in place of the standard result.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAlternate">The concrete alternate result type.</typeparam>
|
||||
/// <param name="alternate">The alternate result to return instead of the standard result.</param>
|
||||
/// <param name="alternateTypeInfo">
|
||||
/// The <see cref="JsonTypeInfo{T}"/> used to serialize <paramref name="alternate"/>. Requiring the strongly-typed
|
||||
/// contract keeps the alternate value paired with matching serializer metadata rather than an unrelated type.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="ResultOrAlternate{TResult}"/> that wraps the alternate result.</returns>
|
||||
public static ResultOrAlternate<TResult> FromAlternate<TAlternate>(TAlternate alternate, JsonTypeInfo<TAlternate> alternateTypeInfo)
|
||||
where TAlternate : Result
|
||||
=> new(alternate, alternateTypeInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the server returned an alternate result instead of the standard result.
|
||||
/// </summary>
|
||||
public bool IsAlternate => _alternate is not null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the immediate result, or <see langword="null"/> if the server returned an alternate.
|
||||
/// </summary>
|
||||
public TResult? Result => _result;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alternate result, or <see langword="null"/> if the server returned the standard result.
|
||||
/// </summary>
|
||||
public Result? Alternate => _alternate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="JsonTypeInfo"/> for serializing the alternate result, or <see langword="null"/>
|
||||
/// if the server returned the standard result.
|
||||
/// </summary>
|
||||
public JsonTypeInfo? AlternateTypeInfo => _alternateTypeInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a <typeparamref name="TResult"/> to a <see cref="ResultOrAlternate{TResult}"/>
|
||||
/// wrapping the immediate result.
|
||||
/// </summary>
|
||||
/// <param name="result">The result to wrap.</param>
|
||||
public static implicit operator ResultOrAlternate<TResult>(TResult result) => new(result);
|
||||
}
|
||||
|
|
@ -46,48 +46,35 @@ internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest,
|
|||
};
|
||||
}
|
||||
|
||||
#pragma warning disable MCPEXP002 // SetWithAlternate consumes the experimental ResultOrAlternate seam
|
||||
/// <summary>
|
||||
/// Registers a handler that may return either a standard result or a <see cref="CreateTaskResult"/>
|
||||
/// for task-augmented execution.
|
||||
/// Registers a handler that may return either a standard result or an alternate <see cref="Result"/>
|
||||
/// subtype for scenarios like task-augmented execution.
|
||||
/// </summary>
|
||||
public void SetTaskAugmented<TParams, TResult>(
|
||||
public void SetWithAlternate<TParams, TResult>(
|
||||
string method,
|
||||
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrCreatedTask<TResult>>> handler,
|
||||
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrAlternate<TResult>>> handler,
|
||||
JsonTypeInfo<TParams> requestTypeInfo,
|
||||
JsonTypeInfo<TResult> responseTypeInfo,
|
||||
JsonTypeInfo<CreateTaskResult> taskResultTypeInfo)
|
||||
JsonTypeInfo<TResult> responseTypeInfo)
|
||||
where TResult : Result
|
||||
{
|
||||
Throw.IfNull(method);
|
||||
Throw.IfNull(handler);
|
||||
Throw.IfNull(requestTypeInfo);
|
||||
Throw.IfNull(responseTypeInfo);
|
||||
Throw.IfNull(taskResultTypeInfo);
|
||||
|
||||
this[method] = async (request, cancellationToken) =>
|
||||
{
|
||||
TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!;
|
||||
var augmented = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (augmented.IsTask)
|
||||
if (augmented.IsAlternate)
|
||||
{
|
||||
// Guard against a misconfiguration where a handler opts into task-augmented
|
||||
// execution but the server has no task lifecycle handlers wired up. Without
|
||||
// tasks/get, a client that received a CreateTaskResult would have no way to
|
||||
// poll the task to completion. Configure McpServerOptions.TaskStore or set
|
||||
// the task handlers explicitly via McpServerOptions.Handlers.
|
||||
if (!ContainsKey(RequestMethods.TasksGet))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Handler for '{method}' returned a {nameof(CreateTaskResult)}, but the server has no " +
|
||||
$"'{RequestMethods.TasksGet}' handler registered. Configure McpServerOptions.TaskStore " +
|
||||
"or set the task handlers explicitly in McpServerOptions.Handlers before starting the server.");
|
||||
}
|
||||
|
||||
return JsonSerializer.SerializeToNode(augmented.TaskCreated!, taskResultTypeInfo);
|
||||
return JsonSerializer.SerializeToNode(augmented.Alternate!, augmented.AlternateTypeInfo!);
|
||||
}
|
||||
|
||||
return JsonSerializer.SerializeToNode(augmented.Result!, responseTypeInfo);
|
||||
};
|
||||
}
|
||||
#pragma warning restore MCPEXP002
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue