Skip to main content
MCP Tasks (SEP-2663) let a client start long-running work, get a task id, and poll tasks/get until the work finishes. The C# SDK stores that state in IMcpTaskStore. The built-in InMemoryMcpTaskStore is shared across every request to the process. InMemoryMcpTaskStorePlus is our own in-memory store. It records the owner on create and never leaks another user’s task, payload, or existence. It implements IMcpTaskStorePlus, which extends IMcpTaskStore with ClaimsPrincipal overloads. McpTaskStoreBase takes a Func<string?> (or a principal factory / constant user id), fail-closed identity, and InputResponseReceived. Durable providers subclass that base and persist their own snapshots.
  1. CreateTaskAsync reads the current user and records it.
  2. GetTaskAsync, SetCancelledAsync, and ResolveInputRequestsAsync require the same user. Another user sees “not found”.
  3. SetCompletedAsync, SetFailedAsync, and SetInputRequestsAsync skip the live-user check. The SDK runs those from a background DI scope after the original HTTP request is gone.
No user on a client-facing call throws McpTaskUserRequiredException.

Wire it up

HTTP hosts register IHttpContextAccessor the platform way and pass a factory that resolves it from the built IServiceProvider. That is the same factory pattern as AddHttpClient, AddOptions, and AddInMemoryMcpTaskStorePlus.
The SDK WithTasks API needs the store object at registration time, before Build(). The factory overload creates the store immediately and captures the provider when the container is built (and again when McpServerOptions is composed on the first request). Each client-facing task call then reads HttpContext.User from the accessor already in DI. Async [McpServerTool] methods that return Task / Task<T> automatically advertise task support. A client that opts into io.modelcontextprotocol/tasks gets a task id owned by the user on that request. Stdio / single user
Store only — same factory, without IMcpServerBuilder:
Tests — a constant user, a callback, an explicit principal, or a service already in the test provider:
Claim lookup matches the SDK session identity order: NameIdentifier, sub, UPN, Name. Types are unsealed; subclass McpTaskStoreBase or InMemoryMcpTaskStorePlus to change isolation, TTL, or storage.

Overloads

Each overload has a matching AddInMemoryMcpTaskStorePlus on IServiceCollection. Pass McpTaskStoreOptions as the next argument to set DefaultTimeToLive and DefaultPollIntervalMs at registration. null TTL keeps tasks until the store discards them.

Sample

samples/CloudNimble.McpEssentials.Samples.Web is a Streamable HTTP MCP server that uses this wiring. Fake authentication stamps HttpContext.User from the X-User-Id header. One tool, HelloWorld, injects ClaimsPrincipal and returns Task<string> so a task-capable client gets a trackable task.
Point a harness at http://localhost:5280/ with X-User-Id: alice. Call HelloWorld with the Tasks extension, then tasks/get as alice and as bob. Bob sees not found.

Isolation rules

Owner comparison is ordinal. Register the store as a singleton. Stateless HTTP creates a fresh McpServer per POST; a scoped store looks empty on the next tasks/get.

Swap the backing store later

IMcpTaskStorePlus and McpTaskStoreBase are the contract durable providers implement. Pick a backing store in Stores. The first durable how-to is Entity Framework Core.