> ## Documentation Index
> Fetch the complete documentation index at: https://ai.easyaf.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticated tasks

> Record the user who started an MCP task and isolate later get, update, and cancel calls to that user.

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`.

```csharp theme={null}
builder.Services.AddHttpContextAccessor();

builder.Services.AddMcpServer()
    .WithHttpTransport(o => o.Stateless = true)
    .WithTools<MyTools>()
    .WithTasksPlus(
        sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
        o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

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**

```csharp theme={null}
builder.Services.AddMcpServer()
    .WithTasksPlus("local-user");
```

**Store only** — same factory, without `IMcpServerBuilder`:

```csharp theme={null}
builder.Services.AddHttpContextAccessor();
builder.Services.AddInMemoryMcpTaskStorePlus(
    sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
    o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
```

**Tests** — a constant user, a callback, an explicit principal, or a service already in the test provider:

```csharp theme={null}
IMcpTaskStorePlus store = new InMemoryMcpTaskStorePlus("user-1");

var store2 = new InMemoryMcpTaskStorePlus(() => currentUserId);

var created = await store2.CreateTaskAsync(httpContext.User);

builder.WithTasksPlus(sp => sp.GetRequiredService<ClaimsPrincipal>());
```

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

| Call | When |
| - | - |
| `WithTasksPlus("user-id")` | Stdio, tests, one user for the process |
| `WithTasksPlus(() => currentUserId)` | Tests that switch the user id |
| `WithTasksPlus(() => principal)` | A principal you already hold |
| `WithTasksPlus(sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User)` | ASP.NET Core. Call `AddHttpContextAccessor()` first. |

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`](https://github.com/CloudNimble/McpEssentials/tree/main/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.

```bash theme={null}
dotnet run --project samples/CloudNimble.McpEssentials.Samples.Web -f net11.0
```

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

| Operation | Current user required | Other user's task |
| - | - | - |
| `CreateTaskAsync` | Yes (throws) | n/a |
| `GetTaskAsync` | Yes (throws) | `null` |
| `SetCancelledAsync` | Yes (throws) | `false` |
| `ResolveInputRequestsAsync` | Yes (throws) | no-op, no event |
| `SetCompletedAsync` | No | allowed (server-side) |
| `SetFailedAsync` | No | allowed (server-side) |
| `SetInputRequestsAsync` | No | allowed (server-side) |

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](https://ai.easyaf.dev/mcpessentials/guides/stores.md). The first durable how-to is [Entity Framework Core](https://ai.easyaf.dev/mcpessentials/guides/stores/entity-framework-core.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.