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.Core/ModelContextProtocol.Core.csproj" />
<Project Path="src/ModelContextProtocol.Extensions.Apps/ModelContextProtocol.Extensions.Apps.csproj" />
<Project Path="src/ModelContextProtocol.Extensions.Tasks/ModelContextProtocol.Extensions.Tasks.csproj" />
<Project Path="src/ModelContextProtocol/ModelContextProtocol.csproj" />
</Folder>
<Folder Name="/tests/">

View file

@ -590,7 +590,7 @@ In stateless mode, each HTTP request creates and disposes a short-lived `McpServ
## Tasks and session modes
[Tasks](xref:tasks) enable a "call-now, fetch-later" pattern for long-running tool calls. Task support depends on having an <xref:ModelContextProtocol.Server.IMcpTaskStore> configured (`McpServerOptions.TaskStore`), and behavior differs between session modes.
[Tasks](xref:tasks) enable a "call-now, fetch-later" pattern for long-running tool calls. Task support depends on having an <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> configured (enabled via `WithTasks`), and behavior differs between session modes.
### Stateless mode
@ -600,9 +600,9 @@ In stateless mode, there is no `SessionId`, so the task store does not apply ses
### Stateful mode
In stateful mode, the `IMcpTaskStore` receives the session's `SessionId` on every operation — `CreateTaskAsync`, `GetTaskAsync`, `ListTasksAsync`, `CancelTaskAsync`, etc. The built-in <xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> enforces session isolation: tasks created in one session cannot be accessed from another.
In stateful mode, the `IMcpTaskStore` receives the session's `SessionId` on every operation: `CreateTaskAsync`, `GetTaskAsync`, `ListTasksAsync`, `CancelTaskAsync`, etc. The built-in <xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> enforces session isolation: tasks created in one session cannot be accessed from another.
Tasks can outlive individual HTTP requests because the tool executes in the background after returning the initial `CreateTaskResult`. Task cleanup is governed by the task's TTL (time-to-live), not by session termination. However, the `InMemoryMcpTaskStore` loses all tasks if the server process restarts. For durable tasks, implement a custom <xref:ModelContextProtocol.Server.IMcpTaskStore> backed by an external store. See [Implementing a custom task store](xref:tasks#implementing-a-custom-task-store) for guidance.
Tasks can outlive individual HTTP requests because the tool executes in the background after returning the initial `CreateTaskResult`. Task cleanup is governed by the task's TTL (time-to-live), not by session termination. However, the `InMemoryMcpTaskStore` loses all tasks if the server process restarts. For durable tasks, implement a custom <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> backed by an external store. See [Implementing a custom task store](xref:tasks#implementing-a-custom-task-store) for guidance.
### Task cancellation vs request cancellation
@ -654,7 +654,7 @@ The `EventStreamStore` itself has TTL-based limits (default: 2-hour event expira
### With tasks (experimental)
[Tasks](xref:tasks) are an experimental feature that enables a "call-now, fetch-later" pattern for long-running tool calls. When a client sends a task-augmented `tools/call` request, the server creates a task record in the <xref:ModelContextProtocol.Server.IMcpTaskStore>, starts the tool handler as a fire-and-forget background task, and returns the task ID immediately — the POST response completes **before the handler starts its real work**.
[Tasks](xref:tasks) are an experimental feature that enables a "call-now, fetch-later" pattern for long-running tool calls. When a client sends a task-augmented `tools/call` request, the server creates a task record in the <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore>, starts the tool handler as a fire-and-forget background task, and returns the task ID immediately, so the POST response completes **before the handler starts its real work**.
This means:

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`
extension key in the request's `_meta`. When that opt-in is present, the server **may** respond
with a <xref:ModelContextProtocol.Protocol.CreateTaskResult> instead of the standard result
with a <xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult> instead of the standard result
(e.g., <xref:ModelContextProtocol.Protocol.CallToolResult>). The client then polls `tasks/get`
until the task reaches a terminal state.
@ -39,36 +39,35 @@ the extension opt-in. The SDK enforces this on the server side.
└──→ Failed (terminal — JSON-RPC errors only)
```
<xref:ModelContextProtocol.Protocol.McpTaskStatus> wire values are serialized in snake_case:
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus> wire values are serialized in snake_case:
`working`, `input_required`, `completed`, `cancelled`, `failed`.
The discriminator field <xref:ModelContextProtocol.Protocol.Result.ResultType?displayProperty=nameWithType>
on the response payload is `"task"` for <xref:ModelContextProtocol.Protocol.CreateTaskResult>
on the response payload is `"task"` for <xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult>
and `"complete"` for ordinary results.
### Server configuration
#### Using the task store
The easiest way to enable tasks is to set an <xref:ModelContextProtocol.Server.IMcpTaskStore>
on <xref:ModelContextProtocol.Server.McpServerOptions.TaskStore?displayProperty=nameWithType>.
The SDK ships <xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> for development and tests:
Tasks support lives in the `ModelContextProtocol.Extensions.Tasks` package. The easiest way to
enable tasks is to call <xref:ModelContextProtocol.Extensions.Tasks.McpTasksBuilderExtensions.WithTasks*>
on the server builder, passing an <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore>.
The SDK ships <xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> for development and tests:
```csharp
#pragma warning disable MCPEXP001
builder.Services.AddMcpServer(options =>
{
options.TaskStore = new InMemoryMcpTaskStore();
})
.WithTools<MyTools>();
using ModelContextProtocol.Extensions.Tasks;
builder.Services.AddMcpServer()
.WithTools<MyTools>()
.WithTasks(new InMemoryMcpTaskStore());
```
When a `TaskStore` is configured the SDK automatically:
When tasks are enabled with `WithTasks` the SDK automatically:
- Wires `tasks/get`, `tasks/update`, and `tasks/cancel` handlers from the store. Explicit
handlers in <xref:ModelContextProtocol.Server.McpServerOptions.Handlers> still take precedence
for any slot they fill.
- Wires the `tasks/get`, `tasks/update`, and `tasks/cancel` handlers from the store.
- Advertises the `io.modelcontextprotocol/tasks` extension in
<xref:ModelContextProtocol.Protocol.ServerCapabilities.Extensions?displayProperty=nameWithType>.
- Wraps each `[McpServerTool]` invocation so that, when the client opts in to the extension,
@ -81,40 +80,21 @@ When a `TaskStore` is configured the SDK automatically:
`tasks/cancel`, so cancellation propagates cooperatively.
For production scenarios that need durability, session isolation, multi-process routing, or
TTL-based cleanup, implement <xref:ModelContextProtocol.Server.IMcpTaskStore> yourself
TTL-based cleanup, implement <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> yourself
(see [Implementing a custom task store](#implementing-a-custom-task-store) below).
#### Custom task handlers
For full control without a store, set the handlers directly. Each handler is an
<xref:ModelContextProtocol.Server.McpRequestHandler`2> that receives an
<xref:ModelContextProtocol.Server.RequestContext`1> with typed parameters:
```csharp
options.Handlers.GetTaskHandler = (context, ct) =>
{
var taskId = context.Params!.TaskId;
// … look up state and return one of the GetTaskResult subtypes.
return new ValueTask<GetTaskResult>(new WorkingTaskResult { TaskId = taskId, /* … */ });
};
options.Handlers.UpdateTaskHandler = (context, ct) => /* return ValueTask<UpdateTaskResult> */;
options.Handlers.CancelTaskHandler = (context, ct) => /* return ValueTask<CancelTaskResult> */;
```
> **Important**: configure all three lifecycle handlers (or use a `TaskStore`) before opting
> into task responses. If a tool handler returns a `CreateTaskResult` but no `tasks/get`
> handler is wired, the server throws `InvalidOperationException` at request time so misconfigured
> deployments fail loudly instead of shipping unpollable tasks.
#### Returning a task from a tool handler
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
returns <xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1>, so each invocation can choose
between an immediate result and a background task:
For full control without the store's auto-wrapping, set
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>.
It returns a <xref:ModelContextProtocol.Protocol.ResultOrAlternate`1>, so each invocation can choose
between an immediate result and an alternate result such as a
<xref:ModelContextProtocol.Extensions.Tasks.CreateTaskResult>:
```csharp
options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
using ModelContextProtocol.Extensions.Tasks;
options.Handlers.CallToolWithAlternateHandler = async (context, ct) =>
{
if (ShouldRunInline(context.Params!))
{
@ -122,7 +102,7 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
}
var taskId = await StartBackgroundWorkAsync(context.Params!, ct);
return new CreateTaskResult
var created = new CreateTaskResult
{
TaskId = taskId,
Status = McpTaskStatus.Working,
@ -130,22 +110,32 @@ options.Handlers.CallToolWithTaskHandler = async (context, ct) =>
LastUpdatedAt = DateTimeOffset.UtcNow,
PollIntervalMs = 1000,
};
return new ResultOrAlternate<CallToolResult>(created, McpTasksJsonContext.Default.CreateTaskResult);
};
```
> This low-level handler is mutually exclusive with `WithTasks`. When a store is configured, the
> SDK does the wrapping for you and throws `InvalidOperationException` if the alternate handler also
> returns an alternate. Use one mechanism or the other. When you return a task this way you are also
> responsible for serving `tasks/get`, `tasks/update`, and `tasks/cancel`, which the store provides
> automatically.
> <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolHandler?displayProperty=nameWithType>
> and <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
> and <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
> are mutually exclusive. Setting one while the other is already non-null throws
> `InvalidOperationException` at the property setter.
#### Task scope for server-initiated requests
When you start background work from a custom <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
(rather than the SDK's auto-wrapping), use <xref:ModelContextProtocol.Server.McpServer.CreateMcpTaskScope*>
When you start background work from a custom <xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
(rather than the SDK's auto-wrapping), use <xref:ModelContextProtocol.Extensions.Tasks.McpTasksServerExtensions.CreateMcpTaskScope*>
to route elicitation, sampling, and `roots/list` calls through the task store as input requests
instead of direct JSON-RPC messages:
```csharp
using ModelContextProtocol.Extensions.Tasks;
using (server.CreateMcpTaskScope(taskId, taskStore))
{
// ElicitAsync/SampleAsync/RequestRootsAsync calls in here are surfaced as
@ -156,13 +146,13 @@ using (server.CreateMcpTaskScope(taskId, taskStore))
`CreateMcpTaskScope` returns an `IDisposable` that restores the prior ambient context on
`Dispose`. The scope is established automatically for `[McpServerTool]` methods that run via
`McpServerOptions.TaskStore`, so this API is only needed for custom handlers.
`WithTasks`, so this API is only needed for custom handlers.
### Client usage
#### Automatic polling
<xref:ModelContextProtocol.Client.McpClient.CallToolAsync(ModelContextProtocol.Protocol.CallToolRequestParams,System.Threading.CancellationToken)>
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CallToolWithPollingAsync*>
handles the full task lifecycle automatically:
- Injects the `io.modelcontextprotocol/tasks` extension capability into the request's `_meta`.
@ -176,21 +166,25 @@ handles the full task lifecycle automatically:
or throws <xref:ModelContextProtocol.McpException> on `Failed`/`Cancelled`.
```csharp
var result = await client.CallToolAsync(
using ModelContextProtocol.Extensions.Tasks;
var result = await client.CallToolWithPollingAsync(
new CallToolRequestParams { Name = "long-running-tool", Arguments = arguments },
cancellationToken);
cancellationToken: cancellationToken);
```
#### Manual control
Use <xref:ModelContextProtocol.Client.McpClient.CallToolRawAsync*> to receive the raw
<xref:ModelContextProtocol.Protocol.ResultOrCreatedTask`1> without auto-polling, then drive the
lifecycle yourself using <xref:ModelContextProtocol.Client.McpClient.GetTaskAsync*>,
<xref:ModelContextProtocol.Client.McpClient.UpdateTaskAsync*>, and
<xref:ModelContextProtocol.Client.McpClient.CancelTaskAsync*>:
Use <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CallToolAsTaskAsync*> to receive the raw
<xref:ModelContextProtocol.Extensions.Tasks.ResultOrCreatedTask`1> without auto-polling, then drive the
lifecycle yourself using <xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.GetTaskAsync*>,
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.UpdateTaskAsync*>, and
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.CancelTaskAsync*>:
```csharp
var raw = await client.CallToolRawAsync(requestParams, cancellationToken);
using ModelContextProtocol.Extensions.Tasks;
var raw = await client.CallToolAsTaskAsync(requestParams, cancellationToken);
if (raw.IsTask)
{
var taskId = raw.TaskCreated!.TaskId;
@ -206,17 +200,17 @@ if (raw.IsTask)
#### Stuck-task detector
`CallToolAsync` includes a safety net for misbehaving servers: if the task stays in
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> across many consecutive polls
`CallToolWithPollingAsync` includes a safety net for misbehaving servers: if the task stays in
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.InputRequired> across many consecutive polls
without exposing any new input request keys (i.e. every previously requested input has already
been resolved by the client and yet the server keeps returning `InputRequired`), the client
gives up, issues a best-effort `tasks/cancel`, and throws
<xref:ModelContextProtocol.McpException>. This guards against a server that never transitions
out of `InputRequired` and prevents an unbounded poll loop.
The threshold defaults to `60` consecutive stuck polls and is configurable via
<xref:ModelContextProtocol.Client.McpClientOptions.MaxConsecutiveStuckPolls>. The effective
wall-clock timeout is roughly `MaxConsecutiveStuckPolls * pollIntervalMs`, so tune the option
The threshold defaults to `60` consecutive stuck polls and is configurable via the
`maxConsecutiveStuckPolls` parameter on `CallToolWithPollingAsync`. The effective
wall-clock timeout is roughly `maxConsecutiveStuckPolls * pollIntervalMs`, so tune the value
with the server-side poll cadence in mind. Setting it too low risks false positives for servers
that are slow to surface follow-up input requests; setting it too high can mask misbehaving
servers.
@ -224,11 +218,11 @@ servers.
### Input requests (multi-round-trip)
When a task needs additional input from the client, the server transitions it to
<xref:ModelContextProtocol.Protocol.McpTaskStatus.InputRequired> and returns the outstanding
requests in <xref:ModelContextProtocol.Protocol.InputRequiredTaskResult.InputRequests>. Each
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.InputRequired> and returns the outstanding
requests in <xref:ModelContextProtocol.Extensions.Tasks.InputRequiredTaskResult.InputRequests>. Each
entry is an arbitrary key paired with a `{ method, params }` envelope representing an
equivalent standalone server-to-client request. The client provides answers via
<xref:ModelContextProtocol.Client.McpClient.UpdateTaskAsync*>, keyed by the same identifiers.
<xref:ModelContextProtocol.Extensions.Tasks.McpTasksClientExtensions.UpdateTaskAsync*>, keyed by the same identifiers.
Supported input request methods:
@ -241,34 +235,34 @@ Per SEP-2663:
- Each input request key **must** be unique over the lifetime of the task.
- Clients **should** deduplicate keys across polls so a request is only presented to the user
or model once. `CallToolAsync` does this automatically.
or model once. `CallToolWithPollingAsync` does this automatically.
- Servers **should** ignore `inputResponses` entries whose key does not currently correspond to
an outstanding request, including responses for terminal-state tasks.
<xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> follows this rule.
<xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> follows this rule.
### Implementing a custom task store
Implement <xref:ModelContextProtocol.Server.IMcpTaskStore> for production scenarios. Key
Implement <xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore> for production scenarios. Key
requirements drawn from the SEP and the SDK contract:
1. **Thread safety** — every method may be called concurrently.
2. **Idempotent terminal transitions** —
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCompletedAsync*>,
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetFailedAsync*>, and
<xref:ModelContextProtocol.Server.IMcpTaskStore.SetCancelledAsync*> must be no-ops on a task
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetCompletedAsync*>,
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetFailedAsync*>, and
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.SetCancelledAsync*> must be no-ops on a task
that is already in a terminal state so a late cancellation cannot overwrite a result.
3. **`InputResponseReceived` event** — after persisting an input response inside
<xref:ModelContextProtocol.Server.IMcpTaskStore.ResolveInputRequestsAsync*>, raise
<xref:ModelContextProtocol.Server.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType>
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.ResolveInputRequestsAsync*>, raise
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.InputResponseReceived?displayProperty=nameWithType>
for each resolved entry. This is the only mechanism that wakes a pending
`server.ElicitAsync`/`server.SampleAsync` call waiting inside a task scope. In distributed
deployments where a different server instance receives the `tasks/update`, the event must
be propagated to the originating server (for example via Redis pub/sub, SignalR, or a custom
transport).
4. **Strong-consistency on `CreateTaskAsync`** —
<xref:ModelContextProtocol.Server.IMcpTaskStore.CreateTaskAsync*> must not return until the
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.CreateTaskAsync*> must not return until the
task is durably persisted, so that a subsequent
<xref:ModelContextProtocol.Server.IMcpTaskStore.GetTaskAsync*> with the returned task ID
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.GetTaskAsync*> with the returned task ID
resolves immediately — even from a different process or node. Stores backed by
eventually-consistent storage must wait for the write to become visible (quorum
acknowledgement, write-through, etc.) before returning. Required by SEP-2663 §306.
@ -310,12 +304,12 @@ public sealed class MyTaskStore : IMcpTaskStore
### Status semantics
<xref:ModelContextProtocol.Protocol.McpTaskStatus.Completed> is the terminal status whenever the
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.Completed> is the terminal status whenever the
underlying request produced its standard result, *including a
<xref:ModelContextProtocol.Protocol.CallToolResult> with `IsError = true`*. Per SEP-2663,
tool-level error results are not promoted to `Failed`.
<xref:ModelContextProtocol.Protocol.McpTaskStatus.Failed> is reserved for JSON-RPC protocol-level
<xref:ModelContextProtocol.Extensions.Tasks.McpTaskStatus.Failed> is reserved for JSON-RPC protocol-level
errors during execution — for example, a malformed request, or an unhandled exception in a custom
handler that the SDK converts to a JSON-RPC error. Use
<xref:ModelContextProtocol.Protocol.CallToolResult.IsError?displayProperty=nameWithType> for
@ -341,7 +335,7 @@ In the built-in SDK pipeline, when a task is wrapped by a configured `TaskStore`
#### Immutable store design
<xref:ModelContextProtocol.Server.InMemoryMcpTaskStore> uses immutable record snapshots with
<xref:ModelContextProtocol.Extensions.Tasks.InMemoryMcpTaskStore> uses immutable record snapshots with
compare-and-swap updates for lock-free thread safety. `InputRequests` and `InputResponses` are
exposed as `ImmutableDictionary<,>` so observers cannot mutate internal state.
@ -357,13 +351,13 @@ responsible for handling — or rejecting — the input requests surfaced throug
- **Server-push task status notifications (SEP-2575)**: not yet implemented. Clients rely on
polling exclusively.
- **Lazy task creation**: when a tool runs through `TaskStore`, the store's
<xref:ModelContextProtocol.Server.IMcpTaskStore.CreateTaskAsync*> is invoked eagerly before
- **Lazy task creation**: when a tool runs through the task store, the store's
<xref:ModelContextProtocol.Extensions.Tasks.IMcpTaskStore.CreateTaskAsync*> is invoked eagerly before
the inner handler runs, so tools that complete inline still incur a store write. There is
currently no built-in deferral.
- **Mid-execution promotion to task**: an `[McpServerTool]` method cannot start executing
synchronously and then transition its remaining work to a background task. Use a custom
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithTaskHandler?displayProperty=nameWithType>
<xref:ModelContextProtocol.Server.McpServerHandlers.CallToolWithAlternateHandler?displayProperty=nameWithType>
if you need that pattern.
- **`roots/list` as an input request**: the server SDK routes `RequestRootsAsync` through the
task channel when called from inside a task scope, but the client SDK does not currently

View file

@ -1,18 +1,17 @@
// Demonstrates the MCP tasks extension (SEP-2663):
//
// - A server is configured with InMemoryMcpTaskStore so that any [McpServerTool] invocation
// - A server is configured with .WithTasks(store) so that any [McpServerTool] invocation
// becomes a background task when the client opts in via the per-request _meta marker.
// - The client invokes the same tool two ways:
// 1. CallToolAsync — the SDK auto-polls until the task completes and returns the final
// CallToolResult, just like a synchronous call.
// 2. CallToolRawAsync — the caller drives the lifecycle manually (GetTaskAsync polls,
// CancelTaskAsync, etc.). Use this when you need to surface progress to a UI or stream
// status updates rather than block on a single await.
// - The client invokes the tool and manually drives the lifecycle via GetTaskAsync.
//
// Both server and client are wired together in-process over an in-memory pipe so the sample
// is self-contained — no separate server process or HTTP transport required.
#pragma warning disable MCPEXP001, MCPEXP002, MCPEXP004
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Extensions.Tasks;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
@ -21,28 +20,25 @@ using System.Text.Json;
Pipe clientToServerPipe = new(), serverToClientPipe = new();
await using McpServer server = McpServer.Create(
new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()),
new McpServerOptions
{
// Setting TaskStore is all that's needed for [McpServerTool]-attributed tools to be
// automatically wrapped as background tasks when the client opts in.
TaskStore = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 },
ToolCollection = [McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })],
});
var store = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 };
var services = new ServiceCollection();
services.AddMcpServer()
.WithTools([McpServerTool.Create(SlowTools.RunReport, new() { Name = "run-report" })])
.WithTasks(store);
services.AddSingleton<ITransport>(new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()));
await using var serviceProvider = services.BuildServiceProvider();
var server = serviceProvider.GetRequiredService<McpServer>();
_ = server.RunAsync();
await using McpClient client = await McpClient.CreateAsync(
new StreamClientTransport(clientToServerPipe.Writer.AsStream(), serverToClientPipe.Reader.AsStream()));
new StreamClientTransport(
serverInput: clientToServerPipe.Writer.AsStream(),
serverOutput: serverToClientPipe.Reader.AsStream()));
Console.WriteLine("=== CallToolAsync (auto-poll) ===");
var auto = await client.CallToolAsync(
new CallToolRequestParams { Name = "run-report" });
Console.WriteLine($" result: {((TextContentBlock)auto.Content[0]).Text}");
Console.WriteLine();
Console.WriteLine("=== CallToolRawAsync (manual poll) ===");
var raw = await client.CallToolRawAsync(new CallToolRequestParams { Name = "run-report" });
Console.WriteLine("=== CallToolAsTaskAsync (manual poll) ===");
var raw = await client.CallToolAsTaskAsync(new CallToolRequestParams { Name = "run-report" });
if (!raw.IsTask)
{
// Either the server doesn't advertise the tasks extension or it chose to run the call
@ -88,9 +84,6 @@ while (true)
continue;
case InputRequiredTaskResult inputRequired:
// The auto-poll path (CallToolAsync above) routes these through the registered
// ElicitationHandler/SamplingHandler automatically. The manual path needs to call
// UpdateTaskAsync with responses for each outstanding key.
Console.WriteLine($" poll {pollCount}: input requested ({inputRequired.InputRequests?.Count ?? 0} key(s))");
continue;
}

View file

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

View file

@ -944,25 +944,17 @@ public abstract partial class McpClient : McpSession
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
/// <summary>
/// Invokes a tool on the server.
/// </summary>
/// <param name="toolName">The name of the tool to call on the server.</param>
/// <param name="toolName">The name of the tool to invoke.</param>
/// <param name="arguments">An optional dictionary of arguments to pass to the tool.</param>
/// <param name="progress">An optional progress reporter for server notifications.</param>
/// <param name="progress">An optional progress handler for tracking operation progress.</param>
/// <param name="options">Optional request options including metadata, serialization settings, and progress tracking.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The <see cref="CallToolResult"/> from the tool execution.</returns>
/// <returns>The result of the tool invocation.</returns>
/// <exception cref="ArgumentNullException"><paramref name="toolName"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
/// <remarks>
/// This overload supports the tasks extension transparently. If the server responds with a
/// task handle rather than an immediate result, this method polls <c>tasks/get</c> until the
/// task completes, dispatching any <see cref="McpTaskStatus.InputRequired"/> entries through
/// the client's registered sampling and elicitation handlers along the way. Use
/// <see cref="CallToolRawAsync"/> to disable automatic polling.
/// </remarks>
public ValueTask<CallToolResult> CallToolAsync(
string toolName,
IReadOnlyDictionary<string, object?>? arguments = null,
@ -1033,221 +1025,19 @@ public abstract partial class McpClient : McpSession
/// <returns>The result of the request.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
/// <remarks>
/// This method automatically includes the <c>io.modelcontextprotocol/tasks</c> extension capability
/// in the request metadata. If the server returns a task handle instead of an immediate result,
/// this method transparently polls <c>tasks/get</c> until the task completes, fails, or is cancelled.
/// Use <see cref="CallToolRawAsync"/>
/// to receive the raw <see cref="ResultOrCreatedTask{TResult}"/> without automatic polling.
/// </remarks>
public async ValueTask<CallToolResult> CallToolAsync(
public ValueTask<CallToolResult> CallToolAsync(
CallToolRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
var augmented = await CallToolRawAsync(requestParams, cancellationToken).ConfigureAwait(false);
if (!augmented.IsTask)
{
return augmented.Result!;
}
return await PollTaskToCompletionAsync(augmented.TaskCreated!, cancellationToken).ConfigureAwait(false);
return SendRequestAsync(
RequestMethods.ToolsCall,
requestParams,
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResult,
cancellationToken: cancellationToken);
}
/// <summary>
/// Polls a task until it reaches a terminal state and returns the final <see cref="CallToolResult"/>.
/// </summary>
private async ValueTask<CallToolResult> PollTaskToCompletionAsync(
CreateTaskResult taskCreated,
CancellationToken cancellationToken)
{
// If the server claims InputRequired but never publishes new input requests after we have
// already responded to everything it asked for, treat that as a stuck task. The client
// can still cancel earlier via cancellationToken; this guard prevents an unbounded poll
// loop when the server is misbehaving. The threshold is configurable via
// McpClientOptions.MaxConsecutiveStuckPolls.
int maxConsecutiveStuckPolls = MaxConsecutiveStuckPolls;
string taskId = taskCreated.TaskId;
long pollIntervalMs = taskCreated.PollIntervalMs ?? 1000;
HashSet<string>? resolvedRequestKeys = null;
bool isFirstPoll = true;
int consecutiveStuckPolls = 0;
while (true)
{
// Skip the delay before the first poll: many tasks complete almost immediately and we
// don't want to pay the poll interval as gratuitous latency.
if (!isFirstPoll)
{
await Task.Delay(TimeSpan.FromMilliseconds(pollIntervalMs), cancellationToken).ConfigureAwait(false);
}
isFirstPoll = false;
var taskResult = await GetTaskAsync(taskId, cancellationToken).ConfigureAwait(false);
// Update poll interval if the server changed it.
if (taskResult.PollIntervalMs is { } newInterval)
{
pollIntervalMs = newInterval;
}
switch (taskResult)
{
case CompletedTaskResult completed:
return JsonSerializer.Deserialize(completed.Result, McpJsonUtilities.JsonContext.Default.CallToolResult)
?? throw new JsonException("Failed to deserialize CallToolResult from completed task.");
case FailedTaskResult failed:
throw new McpException($"Task '{taskId}' failed: {failed.Error}");
case CancelledTaskResult:
throw new OperationCanceledException($"Task '{taskId}' was cancelled by the server.");
case InputRequiredTaskResult inputRequired:
// Dedup: only resolve input requests we haven't already responded to.
var newRequests = new Dictionary<string, InputRequest>();
if (inputRequired.InputRequests is { } incomingRequests)
{
foreach (var kvp in incomingRequests)
{
if (resolvedRequestKeys is null || !resolvedRequestKeys.Contains(kvp.Key))
{
newRequests[kvp.Key] = kvp.Value;
}
}
}
if (newRequests.Count > 0)
{
consecutiveStuckPolls = 0;
IDictionary<string, InputResponse> inputResponses;
try
{
inputResponses = await ResolveInputRequestsAsync(newRequests, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// The input handler failed (e.g., ElicitationHandler threw or no handler was registered).
// Best-effort cancel of the server-side task so it doesn't stay stuck in InputRequired
// until TTL expires.
try
{
await CancelTaskAsync(taskId, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// Swallow secondary failures; we're already propagating the original exception.
}
throw;
}
await UpdateTaskAsync(new UpdateTaskRequestParams
{
TaskId = taskId,
InputResponses = inputResponses,
}, cancellationToken).ConfigureAwait(false);
resolvedRequestKeys ??= new HashSet<string>(StringComparer.Ordinal);
foreach (var key in inputResponses.Keys)
{
resolvedRequestKeys.Add(key);
}
}
else if (++consecutiveStuckPolls >= maxConsecutiveStuckPolls)
{
// Best-effort cancel of the server-side task so it doesn't leak until TTL expires.
try
{
await CancelTaskAsync(taskId, CancellationToken.None).ConfigureAwait(false);
}
catch
{
// Swallow secondary failures; we're already propagating an exception.
}
throw new McpException(
$"Task '{taskId}' has remained in '{McpTaskStatus.InputRequired}' for {maxConsecutiveStuckPolls} consecutive polls " +
"without publishing new input requests after all previously requested inputs were resolved.");
}
break;
case WorkingTaskResult:
// Continue polling.
consecutiveStuckPolls = 0;
break;
default:
throw new McpException(
$"Unexpected task result type '{taskResult.GetType().Name}' for task '{taskId}'.");
}
}
}
/// <summary>
/// Invokes a tool on the server with task extension support, returning the raw response
/// without automatic polling. The caller is responsible for handling task lifecycle.
/// </summary>
/// <param name="requestParams">The request parameters to send. The tasks extension capability will be injected into the request metadata.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ResultOrCreatedTask{TResult}"/> that is either an immediate result or a task handle.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
/// <remarks>
/// <para>
/// Unlike <see cref="CallToolAsync(CallToolRequestParams, CancellationToken)"/>, this method does not
/// automatically poll for task completion. If the server returns a <see cref="CreateTaskResult"/>,
/// the caller must manage polling via <see cref="GetTaskAsync(string, CancellationToken)"/>.
/// </para>
/// </remarks>
public async ValueTask<ResultOrCreatedTask<CallToolResult>> CallToolRawAsync(
CallToolRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
var paramsWithMeta = new CallToolRequestParams
{
Name = requestParams.Name,
Arguments = requestParams.Arguments,
// The SEP-2663 Tasks extension requires the 2026-07-28 or later protocol revision. On an older session, send a plain tools/call
// (no task capability envelope) so the server returns a direct CallToolResult and never
// creates a task.
Meta = IsJuly2026OrLaterProtocol() ? GetMetaWithTaskCapability(requestParams.Meta) : requestParams.Meta,
};
JsonRpcRequest jsonRpcRequest = new()
{
Method = RequestMethods.ToolsCall,
Params = JsonSerializer.SerializeToNode(paramsWithMeta, McpJsonUtilities.JsonContext.Default.CallToolRequestParams),
};
JsonRpcResponse response = await SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
// Discriminate based on resultType field.
if (response.Result is JsonObject resultObj &&
resultObj.TryGetPropertyValue("resultType", out var resultTypeNode) &&
resultTypeNode?.GetValue<string>() == "task")
{
var taskCreated = resultObj.Deserialize(McpJsonUtilities.JsonContext.Default.CreateTaskResult)
?? throw new JsonException("Failed to deserialize CreateTaskResult from response.");
return new ResultOrCreatedTask<CallToolResult>(taskCreated);
}
var callToolResult = JsonSerializer.Deserialize(response.Result, McpJsonUtilities.JsonContext.Default.CallToolResult)
?? throw new JsonException("Failed to deserialize CallToolResult from response.");
return new ResultOrCreatedTask<CallToolResult>(callToolResult);
}
/// <summary>
/// Sets the logging level for the server to control which log messages are sent to the client.
/// </summary>
@ -1302,111 +1092,6 @@ public abstract partial class McpClient : McpSession
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
/// <summary>
/// Retrieves the current state of a task from the server.
/// </summary>
/// <param name="taskId">The stable identifier of the task to retrieve.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="GetTaskResult"/> subtype representing the current task state.</returns>
/// <exception cref="ArgumentNullException"><paramref name="taskId"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
public ValueTask<GetTaskResult> GetTaskAsync(
string taskId,
CancellationToken cancellationToken = default)
{
Throw.IfNull(taskId);
return GetTaskAsync(new GetTaskRequestParams { TaskId = taskId }, cancellationToken);
}
/// <summary>
/// Retrieves the current state of a task from the server.
/// </summary>
/// <param name="requestParams">The request parameters to send in the request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="GetTaskResult"/> subtype representing the current task state.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
public ValueTask<GetTaskResult> GetTaskAsync(
GetTaskRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
ThrowIfTasksNotSupported(nameof(GetTaskAsync));
return SendRequestAsync(
RequestMethods.TasksGet,
requestParams,
McpJsonUtilities.JsonContext.Default.GetTaskRequestParams,
McpJsonUtilities.JsonContext.Default.GetTaskResult,
cancellationToken: cancellationToken);
}
/// <summary>
/// Provides input responses to a task that is in the <see cref="McpTaskStatus.InputRequired"/> state.
/// </summary>
/// <param name="requestParams">The request parameters containing the task ID and input responses.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The result acknowledging the update.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
public ValueTask<UpdateTaskResult> UpdateTaskAsync(
UpdateTaskRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
ThrowIfTasksNotSupported(nameof(UpdateTaskAsync));
return SendRequestAsync(
RequestMethods.TasksUpdate,
requestParams,
McpJsonUtilities.JsonContext.Default.UpdateTaskRequestParams,
McpJsonUtilities.JsonContext.Default.UpdateTaskResult,
cancellationToken: cancellationToken);
}
/// <summary>
/// Requests cancellation of an in-progress task on the server.
/// </summary>
/// <param name="taskId">The stable identifier of the task to cancel.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The result acknowledging the cancellation request.</returns>
/// <exception cref="ArgumentNullException"><paramref name="taskId"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
public ValueTask<CancelTaskResult> CancelTaskAsync(
string taskId,
CancellationToken cancellationToken = default)
{
Throw.IfNull(taskId);
return CancelTaskAsync(new CancelTaskRequestParams { TaskId = taskId }, cancellationToken);
}
/// <summary>
/// Requests cancellation of an in-progress task on the server.
/// </summary>
/// <param name="requestParams">The request parameters to send in the request.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The result acknowledging the cancellation request.</returns>
/// <exception cref="ArgumentNullException"><paramref name="requestParams"/> is <see langword="null"/>.</exception>
/// <exception cref="McpException">The request failed or the server returned an error response.</exception>
public ValueTask<CancelTaskResult> CancelTaskAsync(
CancelTaskRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
ThrowIfTasksNotSupported(nameof(CancelTaskAsync));
return SendRequestAsync(
RequestMethods.TasksCancel,
requestParams,
McpJsonUtilities.JsonContext.Default.CancelTaskRequestParams,
McpJsonUtilities.JsonContext.Default.CancelTaskResult,
cancellationToken: cancellationToken);
}
/// <summary>Converts a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
{
@ -1425,43 +1110,4 @@ public abstract partial class McpClient : McpSession
return result;
}
// Per SEP-2663 §51, the per-request opt-in uses the SEP-2575 capabilities envelope:
// _meta/io.modelcontextprotocol/clientCapabilities/extensions/io.modelcontextprotocol/tasks = {}
private static JsonObject GetMetaWithTaskCapability(JsonObject? existingMeta)
{
JsonObject meta = existingMeta is not null
? (JsonObject)existingMeta.DeepClone()
: [];
if (meta[MetaKeys.ClientCapabilities] is not JsonObject capsRoot)
{
capsRoot = [];
meta[MetaKeys.ClientCapabilities] = capsRoot;
}
if (capsRoot["extensions"] is not JsonObject extensionsRoot)
{
extensionsRoot = [];
capsRoot["extensions"] = extensionsRoot;
}
extensionsRoot.TryAdd(McpExtensions.Tasks, new JsonObject());
return meta;
}
/// <summary>
/// Throws when the negotiated protocol version does not support the SEP-2663 Tasks extension. Tasks
/// require the 2026-07-28 or later protocol revision, and a task id only ever exists when the session
/// negotiated such a revision, so invoking <c>tasks/get</c>, <c>tasks/update</c>, or <c>tasks/cancel</c>
/// on an older session is a programming error rather than a recoverable protocol condition.
/// </summary>
private void ThrowIfTasksNotSupported(string operationName)
{
if (!IsJuly2026OrLaterProtocol())
{
throw new InvalidOperationException(
$"'{operationName}' requires a newer protocol revision that supports tasks. " +
$"The negotiated protocol version is '{NegotiatedProtocolVersion ?? "(none)"}'.");
}
}
}

View file

@ -73,7 +73,7 @@ public abstract partial class McpClient : McpSession
public abstract Task<ClientCompletionDetails> Completion { get; }
/// <summary>
/// Resolves input requests embedded in an <see cref="InputRequiredTaskResult"/> by dispatching
/// Resolves input requests by dispatching
/// each request to the appropriate registered handler.
/// </summary>
/// <param name="inputRequests">
@ -82,16 +82,9 @@ public abstract partial class McpClient : McpSession
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
/// <returns>A dictionary of responses keyed by the same identifiers as the input requests.</returns>
private protected abstract ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
public abstract ValueTask<IDictionary<string, InputResponse>> ResolveInputRequestsAsync(
IDictionary<string, InputRequest> inputRequests, CancellationToken cancellationToken);
/// <summary>
/// Gets the maximum number of consecutive stuck-in-<see cref="McpTaskStatus.InputRequired"/> polls
/// allowed by <see cref="PollTaskToCompletionAsync"/> before the client cancels and throws.
/// Sourced from <see cref="McpClientOptions.MaxConsecutiveStuckPolls"/>.
/// </summary>
private protected abstract int MaxConsecutiveStuckPolls { get; }
/// <summary>
/// Inspects a received cacheable result (<c>tools/list</c>, <c>prompts/list</c>, <c>resources/list</c>,
/// <c>resources/templates/list</c>, or <c>resources/read</c>) so derived clients can emit diagnostics.

View file

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

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>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -71,6 +78,20 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -113,6 +134,13 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -211,6 +239,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -225,6 +260,20 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -267,6 +316,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -365,6 +421,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -379,6 +442,20 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -421,6 +498,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -519,6 +603,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.CreateTaskResult</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ElicitationMcpTasksCapability</Target>
@ -533,6 +624,20 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskRequestParams</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.GetTaskResult</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.ListMcpTasksCapability</Target>
@ -575,6 +680,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatus</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:ModelContextProtocol.Protocol.McpTaskStatusNotificationParams</Target>
@ -624,6 +736,27 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -785,20 +918,6 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -813,34 +932,6 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -960,6 +1051,13 @@
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -995,6 +1093,27 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1156,20 +1275,6 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1184,34 +1289,6 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -1331,6 +1408,13 @@
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -1366,6 +1450,27 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1527,20 +1632,6 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1555,34 +1646,6 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -1702,6 +1765,13 @@
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -1737,6 +1807,27 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.NotificationMethods.TaskStatusNotification</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksCancel</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksGet</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.RequestMethods.TasksList</Target>
@ -1898,20 +1989,6 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.get_Task</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.CreateTaskResult.set_Task(ModelContextProtocol.Protocol.McpTask)</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ElicitRequestParams.get_Task</Target>
@ -1926,34 +2003,6 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.#ctor</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.get_PollInterval</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_PollInterval(System.Nullable{System.TimeSpan})</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.GetTaskResult.set_Status(ModelContextProtocol.Protocol.McpTaskStatus)</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Protocol.ServerCapabilities.get_Tasks</Target>
@ -2073,6 +2122,13 @@
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerOptions.set_TaskStore(ModelContextProtocol.IMcpTaskStore)</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:ModelContextProtocol.Server.McpServerToolAttribute.get_TaskSupport</Target>
@ -2102,57 +2158,29 @@
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
<DiagnosticId>CP0005</DiagnosticId>
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
<Left>lib/net10.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net10.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
<DiagnosticId>CP0005</DiagnosticId>
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
<Left>lib/net8.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net8.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
<DiagnosticId>CP0005</DiagnosticId>
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
<Left>lib/net9.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/net9.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Cancelled</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0011</DiagnosticId>
<Target>F:ModelContextProtocol.Protocol.McpTaskStatus.Failed</Target>
<DiagnosticId>CP0005</DiagnosticId>
<Target>M:ModelContextProtocol.Client.McpClient.ResolveInputRequestsAsync(System.Collections.Generic.IDictionary{System.String,ModelContextProtocol.Protocol.InputRequest},System.Threading.CancellationToken)</Target>
<Left>lib/netstandard2.0/ModelContextProtocol.Core.dll</Left>
<Right>lib/netstandard2.0/ModelContextProtocol.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>

View file

@ -54,7 +54,13 @@ public static partial class McpJsonUtilities
return options;
}
internal static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
/// <summary>
/// Gets the resolved <see cref="JsonTypeInfo{T}"/> for <typeparamref name="T"/> from the specified options.
/// </summary>
/// <typeparam name="T">The type whose serialization metadata should be resolved.</typeparam>
/// <param name="options">The serializer options providing the type-info resolver chain.</param>
/// <returns>The resolved <see cref="JsonTypeInfo{T}"/>.</returns>
public static JsonTypeInfo<T> GetTypeInfo<T>(this JsonSerializerOptions options) =>
(JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));
internal static JsonElement DefaultMcpToolSchema { get; } = ParseJsonElement("""{"type":"object"}"""u8);
@ -120,12 +126,6 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
[JsonSerializable(typeof(RootsListChangedNotificationParams))]
[JsonSerializable(typeof(ToolListChangedNotificationParams))]
[JsonSerializable(typeof(TaskStatusNotificationParams))]
[JsonSerializable(typeof(WorkingTaskNotificationParams))]
[JsonSerializable(typeof(CompletedTaskNotificationParams))]
[JsonSerializable(typeof(FailedTaskNotificationParams))]
[JsonSerializable(typeof(CancelledTaskNotificationParams))]
[JsonSerializable(typeof(InputRequiredTaskNotificationParams))]
// MCP Request Params / Results
[JsonSerializable(typeof(CallToolRequestParams))]
@ -174,19 +174,6 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(IDictionary<string, InputRequest>))]
[JsonSerializable(typeof(IDictionary<string, InputResponse>))]
[JsonSerializable(typeof(GetTaskRequestParams))]
[JsonSerializable(typeof(GetTaskResult))]
[JsonSerializable(typeof(WorkingTaskResult))]
[JsonSerializable(typeof(CompletedTaskResult))]
[JsonSerializable(typeof(FailedTaskResult))]
[JsonSerializable(typeof(CancelledTaskResult))]
[JsonSerializable(typeof(InputRequiredTaskResult))]
[JsonSerializable(typeof(UpdateTaskRequestParams))]
[JsonSerializable(typeof(UpdateTaskResult))]
[JsonSerializable(typeof(CancelTaskRequestParams))]
[JsonSerializable(typeof(CancelTaskResult))]
[JsonSerializable(typeof(CreateTaskResult))]
// MCP Content
[JsonSerializable(typeof(ContentBlock))]
[JsonSerializable(typeof(TextContentBlock))]

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).
/// </remarks>
public const string SubscriptionId = "io.modelcontextprotocol/subscriptionId";
/// <summary>
/// The metadata key used to associate requests, responses, and notifications with a task.
/// </summary>
/// <remarks>
/// <para>
/// This constant defines the key <c>"io.modelcontextprotocol/related-task"</c> used in the
/// <c>_meta</c> field to associate messages with their originating task across the entire
/// request lifecycle.
/// </para>
/// <para>
/// For example, an elicitation that a task-augmented tool call depends on must share the
/// same related task ID with that tool call's task.
/// </para>
/// <para>
/// For <c>tasks/get</c>, <c>tasks/list</c>, and <c>tasks/cancel</c> operations, this
/// metadata should not be included as the taskId is already present in the message structure.
/// </para>
/// </remarks>
public const string RelatedTask = "io.modelcontextprotocol/related-task";
}

View file

@ -144,16 +144,6 @@ public static class NotificationMethods
/// </remarks>
public const string CancelledNotification = "notifications/cancelled";
/// <summary>
/// The name of the notification sent by the server to push task status updates to subscribed clients.
/// </summary>
/// <remarks>
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
/// Each notification carries a complete task state for the current status, identical to what
/// <c>tasks/get</c> would have returned at that moment.
/// </remarks>
public const string TaskStatusNotification = "notifications/tasks/status";
/// <summary>
/// The name of the notification sent first on a <see cref="RequestMethods.SubscriptionsListen"/>
/// response stream to indicate which notification types the server agreed to deliver.

View file

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

View file

@ -125,33 +125,6 @@ public static class RequestMethods
/// </remarks>
public const string Initialize = "initialize";
/// <summary>
/// The name of the request method sent from the client to poll for task completion.
/// </summary>
/// <remarks>
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
/// Clients poll for task status by sending this request with the task ID.
/// </remarks>
public const string TasksGet = "tasks/get";
/// <summary>
/// The name of the request method sent from the client to provide input responses to a task.
/// </summary>
/// <remarks>
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
/// Used when a task has <c>input_required</c> status and the client needs to fulfill outstanding requests.
/// </remarks>
public const string TasksUpdate = "tasks/update";
/// <summary>
/// The name of the request method sent from the client to signal intent to cancel a task.
/// </summary>
/// <remarks>
/// Part of the <c>io.modelcontextprotocol/tasks</c> extension.
/// Cancellation is cooperative — the server decides whether and when to honor it.
/// </remarks>
public const string TasksCancel = "tasks/cancel";
/// <summary>
/// The name of the request method sent from the client to discover the server's protocol versions,
/// capabilities, and metadata.

View file

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

View file

@ -8,8 +8,8 @@ namespace ModelContextProtocol.Protocol;
/// </summary>
public abstract class Result
{
/// <summary>Prevent external derivations.</summary>
private protected Result()
/// <summary>Initializes the base result type.</summary>
protected Result()
{
}
@ -27,11 +27,9 @@ public abstract class Result
/// </summary>
/// <remarks>
/// <para>
/// When set to <c>"complete"</c>, the result is a normal completed response.
/// When set to <c>"input_required"</c>, the result is an <see cref="InputRequiredResult"/> indicating
/// that additional input is needed before the request can be completed.
/// When set to <c>"task"</c>, the result is a <see cref="CreateTaskResult"/> indicating that the server
/// has created a long-running task in lieu of returning the result directly.
/// When absent or set to <c>"complete"</c>, the result is a normal completed response.
/// Other values discriminate alternate result subtypes so callers can choose the appropriate
/// concrete payload to deserialize.
/// </para>
/// </remarks>
/// <value>Defaults to <see langword="null"/>, which is equivalent to <c>"complete"</c>.</value>

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>
/// Registers a handler that may return either a standard result or a <see cref="CreateTaskResult"/>
/// for task-augmented execution.
/// Registers a handler that may return either a standard result or an alternate <see cref="Result"/>
/// subtype for scenarios like task-augmented execution.
/// </summary>
public void SetTaskAugmented<TParams, TResult>(
public void SetWithAlternate<TParams, TResult>(
string method,
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrCreatedTask<TResult>>> handler,
Func<TParams, JsonRpcRequest, CancellationToken, ValueTask<ResultOrAlternate<TResult>>> handler,
JsonTypeInfo<TParams> requestTypeInfo,
JsonTypeInfo<TResult> responseTypeInfo,
JsonTypeInfo<CreateTaskResult> taskResultTypeInfo)
JsonTypeInfo<TResult> responseTypeInfo)
where TResult : Result
{
Throw.IfNull(method);
Throw.IfNull(handler);
Throw.IfNull(requestTypeInfo);
Throw.IfNull(responseTypeInfo);
Throw.IfNull(taskResultTypeInfo);
this[method] = async (request, cancellationToken) =>
{
TParams typedRequest = JsonSerializer.Deserialize(request.Params, requestTypeInfo)!;
var augmented = await handler(typedRequest, request, cancellationToken).ConfigureAwait(false);
if (augmented.IsTask)
if (augmented.IsAlternate)
{
// Guard against a misconfiguration where a handler opts into task-augmented
// execution but the server has no task lifecycle handlers wired up. Without
// tasks/get, a client that received a CreateTaskResult would have no way to
// poll the task to completion. Configure McpServerOptions.TaskStore or set
// the task handlers explicitly via McpServerOptions.Handlers.
if (!ContainsKey(RequestMethods.TasksGet))
{
throw new InvalidOperationException(
$"Handler for '{method}' returned a {nameof(CreateTaskResult)}, but the server has no " +
$"'{RequestMethods.TasksGet}' handler registered. Configure McpServerOptions.TaskStore " +
"or set the task handlers explicitly in McpServerOptions.Handlers before starting the server.");
}
return JsonSerializer.SerializeToNode(augmented.TaskCreated!, taskResultTypeInfo);
return JsonSerializer.SerializeToNode(augmented.Alternate!, augmented.AlternateTypeInfo!);
}
return JsonSerializer.SerializeToNode(augmented.Result!, responseTypeInfo);
};
}
#pragma warning restore MCPEXP002
}

Some files were not shown because too many files have changed in this diff Show more