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.
CreateTaskAsyncreads the current user and records it.GetTaskAsync,SetCancelledAsync, andResolveInputRequestsAsyncrequire the same user. Another user sees “not found”.SetCompletedAsync,SetFailedAsync, andSetInputRequestsAsyncskip the live-user check. The SDK runs those from a background DI scope after the original HTTP request is gone.
McpTaskUserRequiredException.
Wire it up
HTTP hosts registerIHttpContextAccessor 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.
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
IMcpServerBuilder:
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.
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.