Refine 2.0.0 concept documentation (#1742)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 782b5bad-4046-49ec-93a6-72768b5b7521
Copilot-Session: 4b938335-418c-4ee7-8287-6c70d9148b89
This commit is contained in:
Jeff Handley 2026-07-27 20:15:16 -07:00 • committed by GitHub
commit 314bdb2f49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 178 additions and 63 deletions

View file

@ -83,11 +83,11 @@ if (client.ServerCapabilities.Resources is { Subscribe: true })
// Check if the server supports prompts with list-changed notifications
if (client.ServerCapabilities.Prompts is { ListChanged: true })
{
mcpClient.RegisterNotificationHandler(
client.RegisterNotificationHandler(
NotificationMethods.PromptListChangedNotification,
async (notification, ct) =>
{
var prompts = await mcpClient.ListPromptsAsync(cancellationToken: ct);
var prompts = await client.ListPromptsAsync(cancellationToken: ct);
});
}

View file

@ -87,7 +87,7 @@ builder.Services.AddMcpServer()
{
// Stateless mode is recommended for servers that don't need
// server-to-client requests like sampling or elicitation.
// See the Sessions documentation for details.
// See the Stateless and Stateful documentation for details.
options.Stateless = true;
})
.WithToolsFromAssembly();

View file

@ -20,6 +20,7 @@ To install the SDK and build your first MCP client and server, see [Getting star
| - | - |
| [Capabilities](capabilities/capabilities.md) | Learn how client and server capabilities are negotiated during initialization, including protocol version negotiation. |
| [Transports](transports/transports.md) | Learn how to configure stdio, Streamable HTTP, and SSE transports for client-server communication. |
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
| [Ping](ping/ping.md) | Learn how to verify connection health using the ping mechanism. |
| [Progress tracking](progress/progress.md) | Learn how to track progress for long-running operations through notification messages. |
| [Cancellation](cancellation/cancellation.md) | Learn how to cancel in-flight MCP requests using cancellation tokens and notifications. |
@ -43,7 +44,6 @@ To install the SDK and build your first MCP client and server, see [Getting star
| [Completions](completions/completions.md) | Learn how to implement argument auto-completion for prompts and resource templates. |
| [Logging](logging/logging.md) | Learn how to implement logging in MCP servers and how clients can consume log messages. |
| [Pagination](pagination/pagination.md) | Learn how to use cursor-based pagination when listing tools, prompts, and resources. |
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
| [HTTP Context](httpcontext/httpcontext.md) | Learn how to access the underlying `HttpContext` for a request. |
| [MCP Server Handler Filters](filters.md) | Learn how to add filters to the handler pipeline. Filters let you wrap the original handler with additional functionality. |

View file

@ -11,6 +11,9 @@ MCP servers can expose log messages to clients through the [Logging utility].
[Logging utility]: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging
> [!IMPORTANT]
> Logging is **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
This document describes how to implement logging in MCP servers and how clients can consume log messages.
### Logging levels
@ -32,8 +35,8 @@ different set of logging levels. The following table shows the levels and how th
| `emergency` | | System is unusable | |
**Note:** .NET's [ILogger] also supports a `Trace` level (more verbose than Debug) log level.
As there is no equivalent level in the MCP logging levels, Trace level logs messages are silently
dropped when sending messages to the client.
As there is no more verbose level in the MCP logging levels, Trace level log messages are mapped to
the MCP `debug` level when sent to the client.
[ILogger]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger
[ILoggerProvider]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerprovider

View file

@ -247,20 +247,130 @@ public static string WizardTool(
}
```
### Providing custom error messages
### Supporting down-level clients
When MRTR is not supported, you can provide domain-specific guidance:
When <xref:ModelContextProtocol.Server.McpServer.IsMrtrSupported> is `false`, the tool
can't obtain client input through an input request — but that doesn't have to be a dead
end. Handle down-level and stateless callers along a spectrum, from a helpful message to a
fully functional non-interactive path.
#### Return actionable guidance
At minimum, tell the caller how to proceed instead of failing opaquely. Explain what kind
of session would enable the interactive path, and — when the tool exposes one — how to
invoke the non-interactive path described below:
```csharp
if (!server.IsMrtrSupported)
{
return "This tool requires interactive input. To use it:\n"
+ "1. Connect with a client that negotiates MCP protocol revision 2026-07-28, or\n"
+ "2. Use a stateful session using protocol revision 2025-11-25 so the server can resolve the input requests for you.\n"
+ "\nStateless sessions using protocol revision 2025-11-25 and earlier cannot resolve MRTR input requests.";
return "This tool needs interactive input. Connect with a client that negotiates MCP "
+ "protocol revision 2026-07-28, or use a stateful session using revision 2025-11-25 "
+ "so the server can resolve the input requests for you.";
}
```
#### Offer a non-interactive fallback
When a tool *can* complete without a prompt, expose an explicit argument that lets a
down-level or stateless caller opt in directly. The tool stays usable everywhere: it
elicits input when MRTR is available, and otherwise returns guidance the caller
(or its model) can act on by resending with the argument set.
The tool below is just an example that has a sensible non-interactive fallback. It also
shows how to branch on the elicitation **action** rather than assuming acceptance: the
deserialized <xref:ModelContextProtocol.Protocol.ElicitResult> carries an
<xref:ModelContextProtocol.Protocol.ElicitResult.Action> of `"accept"`, `"decline"`, or
`"cancel"`, surfaced through the
<xref:ModelContextProtocol.Protocol.ElicitResult.IsAccepted> shorthand.
```csharp
[McpServerTool, Description("Closes a support ticket, recording why it was closed.")]
public static string CloseSupportTicket(
McpServer server,
RequestContext<CallToolRequestParams> context,
[Description("The ID of the ticket to close")] long ticketId,
[Description("Why the ticket is being closed")] string? closeReason = null)
{
// Handles four client scenarios:
// 1. Provided up-front: client sends `closeReason` in the initial call
// 2. MRTR round-trip request: client confirms via `InputResponses["closeReason"]`
// 3. MRTR initial request: server proposes a default reason and asks for confirmation
// (with automatic SDK down-level bridge)
// 4. Session-less down-level: server returns a guidance message requesting the reason up-front
// The default reason proposed to the caller and used if none is provided.
string defaultCloseReason = "completed";
// (1) Provided up-front. Works on any client, including down-level session-less.
// These requests are typically sent after (4) returns a guidance message.
var confirmedReason = closeReason;
// (2) MRTR round-trip request. Works with native MRTR support or the automatic down-level
// SDK bridge after (3) throws an `InputRequiredException` to request a `closeReason`.
if (string.IsNullOrWhiteSpace(confirmedReason) &&
context.Params?.InputResponses?.TryGetValue("closeReason", out var reasonResponse) is true)
{
var reasonResult = reasonResponse.Deserialize(InputResponse.ElicitResultJsonTypeInfo);
// Branch on the elicitation action: `decline` or `cancel` leaves the ticket open.
if (reasonResult?.IsAccepted is not true) return "Ticket close cancelled";
// Accepted: use the reason the caller confirmed, falling back to the proposed default.
confirmedReason = reasonResult.Content?.TryGetValue("closeReason", out var reasonValue) is true
? reasonValue.GetString()
: null;
confirmedReason = string.IsNullOrWhiteSpace(confirmedReason) ? defaultCloseReason : confirmedReason;
}
// (1) or (2) A reason is in hand; proceed with closing the ticket.
if (!string.IsNullOrWhiteSpace(confirmedReason))
return $"Closed ticket {ticketId}: {confirmedReason}";
// (3) MRTR initial request: propose "completed" as the default reason and ask the caller
// to confirm (or adjust) it. This uses the 2026-07-28 MRTR input request, but the SDK
// provides an automatic bridge to a legacy elicitation on a down-level, stateful
// session. When the bridge can be provided, `server.IsMrtrSupported` is `true` and the
// exception leads to a legacy elicitation response automatically.
if (server.IsMrtrSupported)
{
throw new InputRequiredException(
inputRequests: new Dictionary<string, InputRequest>
{
["closeReason"] = InputRequest.ForElicitation(new ElicitRequestParams
{
Message = $"Close ticket '{ticketId}'? Accept the default reason or provide your own.",
RequestedSchema = new()
{
Properties =
{
["closeReason"] = new ElicitRequestParams.StringSchema
{
Title = "Close reason",
Description = "The reason for closing the ticket",
Default = defaultCloseReason,
},
},
},
})
},
requestState: ticketId.ToString()); // opaque; echoed back to us on the retry
}
// (4) Down-level and stateless: we can't prompt an elicitation through an MRTR
// round-trip request or an elicitation. Return a natural language response
// with guidance for providing the reason up-front.
return "Closing a ticket requires a reason. Resend with `closeReason`.";
}
```
> [!IMPORTANT]
> Form-mode elicitations must set `RequestedSchema`. This tool proposes a default, so its
> schema declares an optional `closeReason` string with a `Default` the caller can accept or
> override. A confirmation prompt that collects no fields still needs a schema — set
> `RequestedSchema = new()` (an empty object schema): it's accepted by native `2026-07-28` MRTR
> and **required** by the down-level stateful elicitation bridge under `2025-11-25`, which
> otherwise throws `ArgumentException: "Form mode elicitation requests require a requested schema."`.
## Compatibility
The SDK supports `InputRequiredException` across two protocol revisions and two session modes:

View file

@ -11,6 +11,9 @@ MCP [roots] allow clients to inform servers about the relevant locations in the
[roots]: https://modelcontextprotocol.io/specification/2025-11-25/client/roots
> [!IMPORTANT]
> Roots are **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
### Overview
Roots provide a mechanism for the client to tell the server which directories, projects, or repositories are relevant to the current session. A server might use roots to:
@ -57,7 +60,7 @@ await using var client = await McpClient.CreateAsync(transport, options);
### Requesting roots from the server
Servers can request the client's root list using <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>. This is a server-to-client request, so it requires [stateful mode or stdio](xref:stateless) — it is not available in [stateless mode](xref:stateless#stateless-mode-recommended).
Servers can request the client's root list using <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>. This is a server-to-client request, so it requires [stateful mode or stdio](xref:stateless) — it is not available in [stateless mode](xref:stateless#stateless-mode-recommended). For a stateless-compatible alternative, throw `InputRequiredException` from your handler through MRTR — see [Multi round-trip requests (MRTR)](#multi-round-trip-requests-mrtr).
```csharp
[McpServerTool, Description("Lists the user's project roots")]

View file

@ -11,8 +11,11 @@ MCP [sampling] allows servers to request LLM completions from the client. This e
[sampling]: https://modelcontextprotocol.io/specification/2025-11-25/client/sampling
> [!IMPORTANT]
> Sampling is **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
> [!NOTE]
> Sampling is a **server-to-client request** — the server sends a request back to the client over an open connection. This requires [stateful mode or stdio](xref:stateless). Sampling is not available in [stateless mode](xref:stateless#stateless-mode-recommended) because stateless servers cannot send requests to clients.
> Sampling is a **server-to-client request** — the server sends a request back to the client over an open connection. This requires [stateful mode or stdio](xref:stateless). Sampling is not available in [stateless mode](xref:stateless#stateless-mode-recommended) because stateless servers cannot send requests to clients. For a stateless-compatible alternative, throw `InputRequiredException` from your handler through MRTR — see [Multi round-trip requests (MRTR)](#multi-round-trip-requests-mrtr).
### How sampling works

View file

@ -14,25 +14,6 @@ Tasks are provided by the `ModelContextProtocol.Extensions.Tasks` package and re
version `2026-07-28` or later. The implementation follows
[SEP-2663 (Tasks Extension)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md).
### Compatibility with v1 experimental Tasks
The Tasks extension in v2.0.0 replaces the experimental Tasks implementation shipped in v1.3.0 and
v1.4.x. The implementations are not compatible at either the API or protocol level. A v2 Tasks
client or server must use a connection negotiated to `2026-07-28` or later; it cannot fall back to
the down-level implementation.
On a connection negotiated to the down-level `2025-11-25` protocol:
- A v2 client calling a v1 server receives an ordinary tool result. The v2 client does not opt in
to the down-level Tasks protocol, and `GetTaskAsync` rejects use before a `2026-07-28`
connection is negotiated.
- A v1 client calling a v2 server likewise receives an ordinary tool result. The v2 server does
not create Tasks on a down-level connection, and its `tasks/get` endpoint rejects the legacy
request with a method-not-found error.
Upgrade both peers to the v2 Tasks extension before using Tasks. The extension provides no
compatibility bridge for the previous experimental API.
### Overview
A client opts into tasks on a per-request basis by including the `io.modelcontextprotocol/tasks`
@ -346,6 +327,25 @@ scope, the SDK intentionally skips the normal client-capability negotiation chec
the client opted in by including the extension marker in the originating request, so it's
responsible for handling — or rejecting — the input requests surfaced through `tasks/get`.
#### Compatibility with v1 experimental Tasks
The Tasks extension in v2.0.0 replaces the experimental Tasks implementation shipped in v1.3.0 and
v1.4.x. The implementations are not compatible at either the API or protocol level. A v2 Tasks
client or server must use a connection negotiated to `2026-07-28` or later; it cannot fall back to
the down-level implementation.
On a connection negotiated to the down-level `2025-11-25` protocol:
- A v2 client calling a v1 server receives an ordinary tool result. The v2 client does not opt in
to the down-level Tasks protocol, and `GetTaskAsync` rejects use before a `2026-07-28`
connection is negotiated.
- A v1 client calling a v2 server likewise receives an ordinary tool result. The v2 server does
not create Tasks on a down-level connection, and its `tasks/get` endpoint rejects the legacy
request with a method-not-found error.
Upgrade both peers to the v2 Tasks extension before using Tasks. The extension provides no
compatibility bridge for the previous experimental API.
### Known limitations
- **Server-push task status notifications (SEP-2575)**: not yet implemented. Clients rely on

View file

@ -57,3 +57,7 @@ items:
uid: tasks
- name: Identity and Roles
uid: identity
- name: API Reference
uid: ModelContextProtocol
- name: Experimental APIs
uid: experimental

View file

@ -183,7 +183,7 @@ app.MapMcp();
app.Run();
```
By default, the HTTP transport runs **statelessly** — the server does not assign an `Mcp-Session-Id` or track transport session state in memory. This simplifies deployment, enables horizontal scaling without session affinity, and matches the `2026-07-28` Streamable HTTP wire format. Set `Stateless = false` explicitly when your server needs stateful sessions for unsolicited notifications, resource subscriptions, or per-client isolation. For a detailed guide on when to use stateless vs. stateful mode, configure session options, and understand [cancellation and disposal](xref:stateless#cancellation-and-disposal) behavior during shutdown, see [Sessions](xref:stateless).
By default, the HTTP transport runs **statelessly** — the server does not assign an `Mcp-Session-Id` or track transport session state in memory. This simplifies deployment, enables horizontal scaling without session affinity, and matches the `2026-07-28` Streamable HTTP wire format. Set `Stateless = false` explicitly when your server needs stateful sessions for unsolicited notifications, resource subscriptions, or per-client isolation. For a detailed guide on when to use stateless vs. stateful mode, configure session options, and understand [cancellation and disposal](xref:stateless#cancellation-and-disposal) behavior during shutdown, see [Stateless and Stateful](xref:stateless).
#### Host name validation
@ -328,7 +328,7 @@ app.MapMcp();
app.Run();
```
See [Sessions — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details on SSE session lifetime and configuration.
See [Stateless and Stateful — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details on SSE session lifetime and configuration.
### Transport mode comparison
@ -345,7 +345,7 @@ See [Sessions — Legacy SSE transport](xref:stateless#legacy-sse-transport) for
| Authentication | Process-level | HTTP auth (OAuth, headers) | HTTP auth (OAuth, headers) | HTTP auth (OAuth, headers) |
| Best for | Local tools, IDE integrations | Remote servers, production deployments | Local HTTP debugging, server-to-client features | Legacy client compatibility |
For a detailed comparison of stateless vs. stateful mode — including deployment trade-offs, security considerations, and configuration — see [Sessions](xref:stateless).
For a detailed comparison of stateless vs. stateful mode — including deployment trade-offs, security considerations, and configuration — see [Stateless and Stateful](xref:stateless).
### In-memory transport
@ -380,7 +380,7 @@ var echo = tools.First(t => t.Name == "echo");
Console.WriteLine(await echo.InvokeAsync(new() { ["arg"] = "Hello World" }));
```
Like [stdio](#stdio-transport), the in-memory transport is inherently single-session — there is no `Mcp-Session-Id` header, and server-to-client requests (sampling, elicitation, roots) work naturally over the bidirectional pipe. This makes it ideal for testing servers that depend on these features. For information about how session behavior varies across transports, see [Sessions](xref:stateless).
Like [stdio](#stdio-transport), the in-memory transport is inherently single-session — there is no `Mcp-Session-Id` header, and server-to-client requests (sampling, elicitation, roots) work naturally over the bidirectional pipe. This makes it ideal for testing servers that depend on these features. For information about how session behavior varies across transports, see [Stateless and Stateful](xref:stateless).
## Cross-application access

View file

@ -42,7 +42,7 @@ When APIs are marked as obsolete, a diagnostic is emitted to warn users that the
| `MCP9001` | In place | The `EnumSchema` and `LegacyTitledEnumSchema` APIs are deprecated as of specification version 2025-11-25. Use the current schema APIs instead. |
| `MCP9002` | Removed | The `AddXxxFilter` extension methods on `IMcpServerBuilder` (for example, `AddListToolsFilter`, `AddCallToolFilter`, `AddIncomingMessageFilter`) were superseded by `WithRequestFilters()` and `WithMessageFilters()`. |
| `MCP9003` | In place | The `RequestContext<TParams>(McpServer, JsonRpcRequest)` constructor is obsolete. Use the overload that accepts a `parameters` argument: `RequestContext<TParams>(McpServer, JsonRpcRequest, TParams)`. |
| `MCP9004` | In place | <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> opts into the legacy SSE transport which has no built-in HTTP-level backpressure. Use Streamable HTTP instead. See [Stateless — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details. |
| `MCP9005` | In place | The Roots, Sampling, and Logging features are deprecated as of specification version 2026-07-28 and may be removed in a future version. See SEP-2577 for more information. |
| `MCP9004` | In place | <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> opts into the legacy SSE transport which has no built-in HTTP-level backpressure. Use Streamable HTTP instead. See [Stateless and Stateful — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details. |
| `MCP9005` | In place | The Roots, Sampling, and Logging features are deprecated as of specification version 2026-07-28 and may be removed in a future version. See [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) for more information. |
| `MCP9006` | In place | The stateful Streamable HTTP configuration knobs on <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions> — `EventStreamStore`, `SessionMigrationHandler`, `PerSessionExecutionContext`, `IdleTimeout`, and `MaxIdleSessionCount` — only apply when `Stateless = false`. Starting with the `2026-07-28` protocol revision, Streamable HTTP no longer supports sessions, and the SDK now defaults `Stateless` to `true`. These knobs remain available for back-compat with the legacy stateful Streamable HTTP transport but new code should target the stateless path. |
| `MCP9007` | In place | `AuthorizationRedirectDelegate` and `ClientOAuthOptions.AuthorizationRedirectDelegate` are retained for source and binary compatibility but cannot provide the authorization-response state or RFC 9207 issuer. State and issuer validation are skipped when these APIs are used. Use `ClientOAuthOptions.AuthorizationCallbackHandler` for response-bound, issuer-aware authorization flows. |

View file

@ -7,7 +7,5 @@ items:
href: roadmap.md
- name: Versioning
href: versioning.md
- name: Experimental APIs
href: experimental.md
- name: GitHub
href: https://github.com/ModelContextProtocol/csharp-sdk

View file

@ -71,3 +71,8 @@ If APIs within the SDK become obsolete due to changes in the MCP spec or other e
3. Within a MAJOR version update, obsolete APIs might be removed. API removals are expected to be rare and avoided wherever possible, and `[Obsolete]` attributes will be applied ahead of the API removal.
Beginning with the 1.0.0 release, all obsoletions will use diagnostic codes specific to the MCP SDK APIs, using an `MCP` prefix.
## See also
- [Experimental APIs](experimental.md)
- [List of diagnostics](list-of-diagnostics.md)

View file

@ -24,9 +24,9 @@ var store = new InMemoryMcpTaskStore { DefaultPollIntervalMs = 250 };
var services = new ServiceCollection();
services.AddMcpServer()
.WithStreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream())
.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>();

View file

@ -6,16 +6,12 @@ The server is configured with an in-memory `IMcpTaskStore`, which is sufficient
`[McpServerTool]` method automatically run as a background task when the client opts into the
tasks extension on a per-request basis.
The client invokes the same `run-report` tool two ways:
1. **`CallToolAsync` (auto-poll)** — the SDK injects the opt-in marker into `_meta`, polls
`tasks/get` at the cadence the server suggests, dispatches any input requests through
registered handlers, and returns the final `CallToolResult` (or throws on a terminal
`Failed`/`Cancelled`).
2. **`CallToolRawAsync` (manual poll)** — the caller receives the raw
`ResultOrCreatedTask<CallToolResult>` and drives the lifecycle directly with
`GetTaskAsync`, `UpdateTaskAsync`, and `CancelTaskAsync`. Useful when you need to surface
progress to a UI or stream task status updates between polls.
The client invokes the `run-report` tool with **`CallToolAsTaskAsync` (manual poll)**. It
receives a `ResultOrCreatedTask<CallToolResult>` and, when the server runs the call as a
background task, drives the lifecycle directly with `GetTaskAsync` — polling at the server's
suggested cadence until the task reaches a terminal state (`Completed`, `Failed`, or
`Cancelled`). If the server returns an inline result instead of creating a task, the sample
surfaces that result and stops.
Both ends of the conversation are connected in-process over an in-memory `Pipe`, so no separate
server process or HTTP transport is required.
@ -29,10 +25,7 @@ dotnet run --project samples/TasksExtension/TasksExtension.csproj
Expected output:
```
=== CallToolAsync (auto-poll) ===
result: report ready
=== CallToolRawAsync (manual poll) ===
=== CallToolAsTaskAsync (manual poll) ===
task created: id=… status=Working pollIntervalMs=250
poll 1: still working …
…
@ -41,10 +34,6 @@ Expected output:
## Notes
- The `MCPEXP001` warning is suppressed because the tasks extension is still experimental. The
project's `<NoWarn>` already includes it; if you copy this pattern into your own project,
either suppress the diagnostic or wrap the experimental APIs in
`#pragma warning disable MCPEXP001`.
- For production deployments — especially stateless HTTP servers — implement
`IMcpTaskStore` against durable storage and register it as a singleton (see
[docs/concepts/tasks/tasks.md](../../docs/concepts/tasks/tasks.md) for the contract).