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:
Jeff Handley 2026-07-14 15:53:58 -07:00 • committed by GitHub
commit 2ca96f548b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 2559 additions and 1895 deletions

View file

@ -69,6 +69,7 @@
<Project Path="src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj" /> <Project Path="src/ModelContextProtocol.AspNetCore/ModelContextProtocol.AspNetCore.csproj" />
<Project Path="src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj" /> <Project Path="src/ModelContextProtocol.Core/ModelContextProtocol.Core.csproj" />
<Project Path="src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.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" /> <Project Path="src/ModelContextProtocol/ModelContextProtocol.csproj" />
</Folder> </Folder>
<Folder Name="/tests/"> <Folder Name="/tests/">

View file

@ -590,7 +590,7 @@ In stateless mode, each HTTP request creates and disposes a short-lived `McpServ
## Tasks and session modes ## 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 ### Stateless mode
@ -600,9 +600,9 @@ In stateless mode, there is no `SessionId`, so the task store does not apply ses
### Stateful mode ### 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 ### Task cancellation vs request cancellation
@ -654,7 +654,7 @@ The `EventStreamStore` itself has TTL-based limits (default: 2-hour event expira
### With tasks (experimental) ### 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: This means:

View file

@ -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` 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 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` (e.g., <xref:ModelContextProtocol.Protocol.CallToolResult>). The client then polls `tasks/get`
until the task reaches a terminal state. 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) └──→ 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`. `working`, `input_required`, `completed`, `cancelled`, `failed`.
The discriminator field <xref:ModelContextProtocol.Protocol.Result.ResultType?displayProperty=nameWithType> 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. and `"complete"` for ordinary results.
### Server configuration ### Server configuration
#### Using the task store #### Using the task store
The easiest way to enable tasks is to set an <xref:ModelContextProtocol.Server.IMcpTaskStore> Tasks support lives in the `ModelContextProtocol.Extensions.Tasks` package. The easiest way to
on <xref:ModelContextProtocol.Server.McpServerOptions.TaskStore?displayProperty=nameWithType>. enable tasks is to call <xref:ModelContextProtocol.Extensions.Tasks.McpTasksBuilderExtensions.WithTasks*>
The SDK ships <xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> for development and tests: on the server builder, passing an <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore>.
The SDK ships <xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> for development and tests:
```csharp ```csharp
#pragma warning disable MCPEXP001 #pragma warning disable MCPEXP001
builder.Services.AddMcpServer(options => using ModelContextProtocol.Extensions.Tasks;
{
options.TaskStore = new InMemoryMcpTaskStore(); builder.Services.AddMcpServer()
}) .WithTools<MyTools>()
.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 - Wires the `tasks/get`, `tasks/update`, and `tasks/cancel` handlers from the store.
handlers in <xref:ModelContextProtocol.Server.McpServerOptions.Handlers> still take precedence
for any slot they fill.
- Advertises the `io.modelcontextprotocol/tasks` extension in - Advertises the `io.modelcontextprotocol/tasks` extension in
<xref:ModelContextProtocol.Protocol.ServerCapabilities.Extensions?displayProperty=nameWithType>. <xref:ModelContextProtocol.Protocol.ServerCapabilities.Extensions?displayProperty=nameWithType>.
- Wraps each `[McpServerTool]` invocation so that, when the client opts in to the extension, - 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. `tasks/cancel`, so cancellation propagates cooperatively.
For production scenarios that need durability, session isolation, multi-process routing, or 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). (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 #### Returning a task from a tool handler
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType> For full control without the store's auto-wrapping, set
returns <xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1>, so each invocation can choose <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>.
between an immediate result and a background task: 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 ```csharp
options.Handlers.CallToolWithTaskHandler = async (context, ct) => using ModelContextProtocol.Extensions.Tasks;
options.Handlers.CallToolWithAlternateHandler = async (context, ct) =>
{ {
if (ShouldRunInline(context.Params!)) if (ShouldRunInline(context.Params!))
{ {
@ -122,7 +102,7 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
} }
var taskId = await StartBackgroundWorkAsync(context.Params!, ct); var taskId = await StartBackgroundWorkAsync(context.Params!, ct);
return new CreateTaskResult var created = new CreateTaskResult
{ {
TaskId = taskId, TaskId = taskId,
Status = McpTaskStatus.Working, Status = McpTaskStatus.Working,
@ -130,22 +110,32 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
LastUpdatedAt = DateTimeOffset.UtcNow, LastUpdatedAt = DateTimeOffset.UtcNow,
PollIntervalMs = 1000, 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> > <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 > are mutually exclusive. Setting one while the other is already non-null throws
> `InvalidOperationException` at the property setter. > `InvalidOperationException` at the property setter.
#### Task scope for server-initiated requests #### Task scope for server-initiated requests
When you start background work from a custom <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType> 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.Server.McpServer.CreateMcpTaskScope*> (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 to route elicitation, sampling, and `roots/list` calls through the task store as input requests
instead of direct JSON-RPC messages: instead of direct JSON-RPC messages:
```csharp ```csharp
using ModelContextProtocol.Extensions.Tasks;
using (server.CreateMcpTaskScope(taskId, taskStore)) using (server.CreateMcpTaskScope(taskId, taskStore))
{ {
// ElicitAsync/SampleAsync/RequestRootsAsync calls in here are surfaced as // 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 `CreateMcpTaskScope` returns an `IDisposable` that restores the prior ambient context on
`Dispose`. The scope is established automatically for `[McpServerTool]` methods that run via `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 ### Client usage
#### Automatic polling #### 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: handles the full task lifecycle automatically:
- Injects the `io.modelcontextprotocol/tasks` extension capability into the request's `_meta`. - 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`. or throws <xref:ModelContextProtocol.McpException> on `Failed`/`Cancelled`.
```csharp ```csharp
var result = await client.CallToolAsync( using ModelContextProtocol.Extensions.Tasks;
var result = await client.CallToolWithPollingAsync(
new CallToolRequestParams { Name = "long-running-tool", Arguments = arguments }, new CallToolRequestParams { Name = "long-running-tool", Arguments = arguments },
cancellationToken); cancellationToken: cancellationToken);
``` ```
#### Manual control #### Manual control
Use <xref:ModelContextProtocol.Client.McpClient.CallToolRawAsync*> to receive the raw Use <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CallToolAsTaskAsync*> to receive the raw
<xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1> without auto-polling, then drive the <xref:ModelContextProtocol.Extensions.Tasks.ResultOrCreatedTask`1> without auto-polling, then drive the
lifecycle yourself using <xref:ModelContextProtocol.Client.McpClient.GetTaskAsync*>, lifecycle yourself using <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.GetTaskAsync*>,
<xref:ModelContextProtocol.Client.McpClient.UpdateTaskAsync*>, and <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.UpdateTaskAsync*>, and
<xref:ModelContextProtocol.Client.McpClient.CancelTaskAsync*>: <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CancelTaskAsync*>:
```csharp ```csharp
var raw = await client.CallToolRawAsync(requestParams, cancellationToken); using ModelContextProtocol.Extensions.Tasks;
var raw = await client.CallToolAsTaskAsync(requestParams, cancellationToken);
if (raw.IsTask) if (raw.IsTask)
{ {
var taskId = raw.TaskCreated!.TaskId; var taskId = raw.TaskCreated!.TaskId;
@ -206,17 +200,17 @@ if (raw.IsTask)
#### Stuck-task detector #### Stuck-task detector
`CallToolAsync` includes a safety net for misbehaving servers: if the task stays in `CallToolWithPollingAsync` includes a safety net for misbehaving servers: if the task stays in
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> across many consecutive polls <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 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 been resolved by the client and yet the server keeps returning `InputRequired`), the client
gives up, issues a best-effort `tasks/cancel`, and throws gives up, issues a best-effort `tasks/cancel`, and throws
<xref:ModelContextProtocol.McpException>. This guards against a server that never transitions <xref:ModelContextProtocol.McpException>. This guards against a server that never transitions
out of `InputRequired` and prevents an unbounded poll loop. out of `InputRequired` and prevents an unbounded poll loop.
The threshold defaults to `60` consecutive stuck polls and is configurable via The threshold defaults to `60` consecutive stuck polls and is configurable via the
<xref:ModelContextProtocol.Client.McpClientOptions.MaxConsecutiveStuckPolls>. The effective `maxConsecutiveStuckPolls` parameter on `CallToolWithPollingAsync`. The effective
wall-clock timeout is roughly `MaxConsecutiveStuckPolls * pollIntervalMs`, so tune the option 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 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 that are slow to surface follow-up input requests; setting it too high can mask misbehaving
servers. servers.
@ -224,11 +218,11 @@ servers.
### Input requests (multi-round-trip) ### Input requests (multi-round-trip)
When a task needs additional input from the client, the server transitions it to When a task needs additional input from the client, the server transitions it to
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> and returns the outstanding <xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.InputRequired> and returns the outstanding
requests in <xref:ModelContextProtocol.Protocol.InputRequiredTaskResult.InputRequests>. Each requests in <xref:ModelContextProtocol.Extensions.Tasks.InputRequiredTaskResult.InputRequests>. Each
entry is an arbitrary key paired with a `{ method, params }` envelope representing an entry is an arbitrary key paired with a `{ method, params }` envelope representing an
equivalent standalone server-to-client request. The client provides answers via 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: Supported input request methods:
@ -241,34 +235,34 @@ Per SEP-2663:
- Each input request key **must** be unique over the lifetime of the task. - 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 - 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 - Servers **should** ignore `inputResponses` entries whose key does not currently correspond to
an outstanding request, including responses for terminal-state tasks. 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 ### 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: requirements drawn from the SEP and the SDK contract:
1. **Thread safety** — every method may be called concurrently. 1. **Thread safety** — every method may be called concurrently.
2. **Idempotent terminal transitions** — 2. **Idempotent terminal transitions** —
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCompletedAsync*>, <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetCompletedAsync*>,
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetFailedAsync*>, and <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetFailedAsync*>, and
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCancelledAsync*> must be no-ops on a task <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. that is already in a terminal state so a late cancellation cannot overwrite a result.
3. **`InputResponseReceived` event** — after persisting an input response inside 3. **`InputResponseReceived` event** — after persisting an input response inside
<xref:ModelContextProtocol.Server.IMcpTaskStore.ResolveInputRequestsAsync*>, raise <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.ResolveInputRequestsAsync*>, raise
<xref:ModelContextProtocol.Server.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType> <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType>
for each resolved entry. This is the only mechanism that wakes a pending 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 `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 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 be propagated to the originating server (for example via Redis pub/sub, SignalR, or a custom
transport). transport).
4. **Strong-consistency on `CreateTaskAsync`** — 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 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 resolves immediately — even from a different process or node. Stores backed by
eventually-consistent storage must wait for the write to become visible (quorum eventually-consistent storage must wait for the write to become visible (quorum
acknowledgement, write-through, etc.) before returning. Required by SEP-2663 §306. acknowledgement, write-through, etc.) before returning. Required by SEP-2663 §306.
@ -310,12 +304,12 @@ public sealed class MyTaskStore : IMcpTaskStore
### Status semantics ### 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 underlying request produced its standard result, *including a
<xref:ModelContextProtocol.Protocol.CallToolResult> with `IsError = true`*. Per SEP-2663, <xref:ModelContextProtocol.Protocol.CallToolResult> with `IsError = true`*. Per SEP-2663,
tool-level error results are not promoted to `Failed`. 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 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 handler that the SDK converts to a JSON-RPC error. Use
<xref:ModelContextProtocol.Protocol.CallToolResult.IsError?displayProperty=nameWithType> for <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 #### 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 compare-and-swap updates for lock-free thread safety. `InputRequests` and `InputResponses` are
exposed as `ImmutableDictionary<,>` so observers cannot mutate internal state. 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 - **Server-push task status notifications (SEP-2575)**: not yet implemented. Clients rely on
polling exclusively. polling exclusively.
- **Lazy task creation**: when a tool runs through `TaskStore`, the store's - **Lazy task creation**: when a tool runs through the task store, the store's
<xref:ModelContextProtocol.Server.IMcpTaskStore.CreateTaskAsync*> is invoked eagerly before <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 the inner handler runs, so tools that complete inline still incur a store write. There is
currently no built-in deferral. currently no built-in deferral.
- **Mid-execution promotion to task**: an `[McpServerTool]` method cannot start executing - **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 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. if you need that pattern.
- **`roots/list` as an input request**: the server SDK routes `RequestRootsAsync` through the - **`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 task channel when called from inside a task scope, but the client SDK does not currently

View file

@ -1,18 +1,17 @@
// Demonstrates the MCP tasks extension (SEP-2663): // 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. // becomes a background task when the client opts in via the per-request _meta marker.
// - The client invokes the same tool two ways: // - The client invokes the tool and manually drives the lifecycle via GetTaskAsync.
// 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.
// //
// Both server and client are wired together in-process over an in-memory pipe so the sample // 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. // 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.Client;
using ModelContextProtocol.Extensions.Tasks;
using ModelContextProtocol.Protocol; using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server; using ModelContextProtocol.Server;
using System.ComponentModel; using System.ComponentModel;
@ -21,28 +20,25 @@ using System.Text.Json;
Pipe clientToServerPipe = new(), serverToClientPipe = new(); Pipe clientToServerPipe = new(), serverToClientPipe = new();
await using McpServer server = McpServer.Create( var store = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 };
new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()),
new McpServerOptions var services = new ServiceCollection();
{ services.AddMcpServer()
// Setting TaskStore is all that's needed for [McpServerTool]-attributed tools to be .WithTools([McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })])
// automatically wrapped as background tasks when the client opts in. .WithTasks(store);
TaskStore = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 }, services.AddSingleton<ITransport>(new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()));
ToolCollection = [McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })],
}); await using var serviceProvider = services.BuildServiceProvider();
var server = serviceProvider.GetRequiredService<McpServer>();
_ = server.RunAsync(); _ = server.RunAsync();
await using McpClient client = await McpClient.CreateAsync( 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) ==="); Console.WriteLine("=== CallToolAsTaskAsync (manual poll) ===");
var auto = await client.CallToolAsync( var raw = await client.CallToolAsTaskAsync(new CallToolRequestParams { Name = "run-report" });
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" });
if (!raw.IsTask) if (!raw.IsTask)
{ {
// Either the server doesn't advertise the tasks extension or it chose to run the call // Either the server doesn't advertise the tasks extension or it chose to run the call
@ -88,9 +84,6 @@ while (true)
continue; continue;
case InputRequiredTaskResult inputRequired: 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))"); Console.WriteLine($" poll {pollCount}: input requested ({inputRequired.InputRequests?.Count ?? 0} key(s))");
continue; continue;
} }

View file

@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" /> <ProjectReference Include="..\..\src\ModelContextProtocol\ModelContextProtocol.csproj" />
<ProjectReference Include="..\..\src\ModelContextProtocol.Extensions.Tasks\ModelContextProtocol.Extensions.Tasks.csproj" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -944,25 +944,17 @@ public abstract partial class McpClient : McpSession
McpJsonUtilities.JsonContext.Default.EmptyResult, McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask(); cancellationToken: cancellationToken).AsTask();
} }
/// <summary> /// <summary>
/// Invokes a tool on the server. /// Invokes a tool on the server.
/// </summary> /// </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="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="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> /// <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="ArgumentNullException"><paramref name="toolName"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</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( public ValueTask<CallToolResult> CallToolAsync(
string toolName, string toolName,
IReadOnlyDictionary<string, object?>? arguments = null, IReadOnlyDictionary<string, object?>? arguments = null,
@ -1033,221 +1025,19 @@ public abstract partial class McpClient : McpSession
/// <returns>The result of the request.</returns> /// <returns>The result of the request.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception> /// <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> /// <exception cref="McpException">The request failed or the server returned an error response.</exception>
/// <remarks> public ValueTask<CallToolResult> CallToolAsync(
/// 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(
CallToolRequestParams requestParams, CallToolRequestParams requestParams,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
Throw.IfNull(requestParams); Throw.IfNull(requestParams);
var augmented = await CallToolRawAsync(requestParams, cancellationToken).ConfigureAwait(false); return SendRequestAsync(
RequestMethods.ToolsCall,
if (!augmented.IsTask) requestParams,
{ McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
return augmented.Result!; McpJsonUtilities.JsonContext.Default.CallToolResult,
} cancellationToken: cancellationToken);
return await PollTaskToCompletionAsync(augmented.TaskCreated!, cancellationToken).ConfigureAwait(false);
} }
/// <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> /// <summary>
/// Sets the logging level for the server to control which log messages are sent to the client. /// Sets the logging level for the server to control which log messages are sent to the client.
/// </summary> /// </summary>
@ -1302,111 +1092,6 @@ public abstract partial class McpClient : McpSession
McpJsonUtilities.JsonContext.Default.EmptyResult, McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask(); 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( private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options) IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
{ {
@ -1425,43 +1110,4 @@ public abstract partial class McpClient : McpSession
return result; 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)"}'.");
}
}
} }

View file

@ -73,7 +73,7 @@ public abstract partial class McpClient : McpSession
public abstract Task<ClientCompletionDetails> Completion { get; } public abstract Task<ClientCompletionDetails> Completion { get; }
/// <summary> /// <summary>
/// Resolves input requests embedded in an <see cref="InputRequiredTaskResult"/> by dispatching /// Resolves input requests by dispatching
/// each request to the appropriate registered handler. /// each request to the appropriate registered handler.
/// </summary> /// </summary>
/// <param name="inputRequests"> /// <param name="inputRequests">
@ -82,16 +82,9 @@ public abstract partial class McpClient : McpSession
/// </param> /// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</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> /// <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); 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> /// <summary>
/// Inspects a received cacheable result (<c>tools/list</c>, <c>prompts/list</c>, <c>resources/list</c>, /// 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. /// <c>resources/templates/list</c>, or <c>resources/read</c>) so derived clients can emit diagnostics.

View file

@ -179,10 +179,9 @@ internal sealed partial class McpClientImpl : McpClient
public override Task<ClientCompletionDetails> Completion => _sessionHandler.CompletionTask; public override Task<ClientCompletionDetails> Completion => _sessionHandler.CompletionTask;
/// <inheritdoc/> /// <inheritdoc/>
private protected override int MaxConsecutiveStuckPolls => _options.MaxConsecutiveStuckPolls;
/// <inheritdoc/> /// <inheritdoc/>
private protected override async ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync( public override async ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
IDictionary<string, InputRequest> inputRequests, IDictionary<string, InputRequest> inputRequests,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {

View file

@ -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;
} }

View file

@ -57,6 +57,13 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -71,6 +78,20 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -113,6 +134,13 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target> <Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -211,6 +239,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -225,6 +260,20 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -267,6 +316,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target> <Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -365,6 +421,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -379,6 +442,20 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -421,6 +498,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target> <Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -519,6 +603,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -533,6 +624,20 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target> <Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -575,6 +680,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0001</DiagnosticId> <DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target> <Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -624,6 +736,27 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target> <Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -785,20 +918,6 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target> <Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -813,34 +932,6 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target> <Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -960,6 +1051,13 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target> <Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -995,6 +1093,27 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target> <Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1156,20 +1275,6 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target> <Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1184,34 +1289,6 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target> <Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -1331,6 +1408,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target> <Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -1366,6 +1450,27 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target> <Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1527,20 +1632,6 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target> <Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1555,34 +1646,6 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target> <Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -1702,6 +1765,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target> <Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -1737,6 +1807,27 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target> <Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1898,20 +1989,6 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target> <Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1926,34 +2003,6 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target> <Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -2073,6 +2122,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </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> <Suppression>
<DiagnosticId>CP0002</DiagnosticId> <DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target> <Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -2102,57 +2158,29 @@
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0011</DiagnosticId> <DiagnosticId>CP0005</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target> <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> <Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0011</DiagnosticId> <DiagnosticId>CP0005</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target> <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.Cancelled</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left> <Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0011</DiagnosticId> <DiagnosticId>CP0005</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target> <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.Cancelled</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left> <Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right> <Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression> </Suppression>
<Suppression> <Suppression>
<DiagnosticId>CP0011</DiagnosticId> <DiagnosticId>CP0005</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target> <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.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>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left> <Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right> <Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression> <IsBaselineSuppression>true</IsBaselineSuppression>

View file

@ -54,7 +54,13 @@ public static partial class McpJsonUtilities
return options; 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)); (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
internal static JsonElement DefaultMcpToolSchema { get; } = ParseJsonElement("""{"type":"object"}"""u8); internal static JsonElement DefaultMcpToolSchema { get; } = ParseJsonElement("""{"type":"object"}"""u8);
@ -120,12 +126,6 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))] [JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
[JsonSerializable(typeof(RootsListChangedNotificationParams))] [JsonSerializable(typeof(RootsListChangedNotificationParams))]
[JsonSerializable(typeof(ToolListChangedNotificationParams))] [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 // MCP Request Params / Results
[JsonSerializable(typeof(CallToolRequestParams))] [JsonSerializable(typeof(CallToolRequestParams))]
@ -174,19 +174,6 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(IDictionary<string, InputRequest>))] [JsonSerializable(typeof(IDictionary<string, InputRequest>))]
[JsonSerializable(typeof(IDictionary<string, InputResponse>))] [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 // MCP Content
[JsonSerializable(typeof(ContentBlock))] [JsonSerializable(typeof(ContentBlock))]
[JsonSerializable(typeof(TextContentBlock))] [JsonSerializable(typeof(TextContentBlock))]

View file

@ -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";
}

View file

@ -53,24 +53,4 @@ public static class MetaKeys
/// belonging to different subscriptions on a shared channel (especially STDIO). /// belonging to different subscriptions on a shared channel (especially STDIO).
/// </remarks> /// </remarks>
public const string SubscriptionId = "io.modelcontextprotocol/subscriptionId"; 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";
} }

View file

@ -144,16 +144,6 @@ public static class NotificationMethods
/// </remarks> /// </remarks>
public const string CancelledNotification = "notifications/cancelled"; 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> /// <summary>
/// The name of the notification sent first on a <see cref="RequestMethods.SubscriptionsListen"/> /// 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. /// response stream to indicate which notification types the server agreed to deliver.

View file

@ -8,8 +8,8 @@ namespace ModelContextProtocol.Protocol;
/// </summary> /// </summary>
public abstract class NotificationParams public abstract class NotificationParams
{ {
/// <summary>Prevent external derivations.</summary> /// <summary>Initializes the base notification parameter type.</summary>
private protected NotificationParams() protected NotificationParams()
{ {
} }

View file

@ -125,33 +125,6 @@ public static class RequestMethods
/// </remarks> /// </remarks>
public const string Initialize = "initialize"; 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> /// <summary>
/// The name of the request method sent from the client to discover the server's protocol versions, /// The name of the request method sent from the client to discover the server's protocol versions,
/// capabilities, and metadata. /// capabilities, and metadata.

View file

@ -11,8 +11,8 @@ namespace ModelContextProtocol.Protocol;
/// </remarks> /// </remarks>
public abstract class RequestParams public abstract class RequestParams
{ {
/// <summary>Prevent external derivations.</summary> /// <summary>Initializes the base request parameter type.</summary>
private protected RequestParams() protected RequestParams()
{ {
} }

View file

@ -8,8 +8,8 @@ namespace ModelContextProtocol.Protocol;
/// </summary> /// </summary>
public abstract class Result public abstract class Result
{ {
/// <summary>Prevent external derivations.</summary> /// <summary>Initializes the base result type.</summary>
private protected Result() protected Result()
{ {
} }
@ -27,11 +27,9 @@ public abstract class Result
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// When set to <c>"complete"</c>, the result is a normal completed response. /// When absent or 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 /// Other values discriminate alternate result subtypes so callers can choose the appropriate
/// that additional input is needed before the request can be completed. /// concrete payload to deserialize.
/// 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.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <value>Defaults to <see langword="null"/>, which is equivalent to <c>"complete"</c>.</value> /// <value>Defaults to <see langword="null"/>, which is equivalent to <c>"complete"</c>.</value>

View 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);
}

View file

@ -46,48 +46,35 @@ internal sealed class RequestHandlers : Dictionary<string, Func<JsonRpcRequest,
}; };
} }
#pragma warning disable MCPEXP002 // SetWithAlternate consumes the experimental ResultOrAlternate seam
/// <summary> /// <summary>
/// Registers a handler that may return either a standard result or a <see cref="CreateTaskResult"/> /// Registers a handler that may return either a standard result or an alternate <see cref="Result"/>
/// for task-augmented execution. /// subtype for scenarios like task-augmented execution.
/// </summary> /// </summary>
public void SetTaskAugmented<TParams, TResult>( public void SetWithAlternate<TParams, TResult>(
string method, string method,
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrCreatedTask<TResult>>> handler, Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrAlternate<TResult>>> handler,
JsonTypeInfo<TParams> requestTypeInfo, JsonTypeInfo<TParams> requestTypeInfo,
JsonTypeInfo<TResult> responseTypeInfo, JsonTypeInfo<TResult> responseTypeInfo)
JsonTypeInfo<CreateTaskResult> taskResultTypeInfo)
where TResult : Result where TResult : Result
{ {
Throw.IfNull(method); Throw.IfNull(method);
Throw.IfNull(handler); Throw.IfNull(handler);
Throw.IfNull(requestTypeInfo); Throw.IfNull(requestTypeInfo);
Throw.IfNull(responseTypeInfo); Throw.IfNull(responseTypeInfo);
Throw.IfNull(taskResultTypeInfo);
this[method] = async (request, cancellationToken) => this[method] = async (request, cancellationToken) =>
{ {
TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!; TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!;
var augmented = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false); 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 return JsonSerializer.SerializeToNode(augmented.Alternate!, augmented.AlternateTypeInfo!);
// 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.Result!, responseTypeInfo); 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