> ## Documentation Index
> Fetch the complete documentation index at: https://si.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`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/InMemoryMcpTaskStorePlus) records the owner on create and never leaks another user's task, payload, or existence. Durable providers persist the same contract.

> [card] Stamp the owner
>
> `CreateTaskAsync` reads the current user and records it on the snapshot.

> [card] Isolate later calls
>
> Get, cancel, and resolve require that user. Anyone else sees not found.

> [card] Complete in the background
>
> The SDK finishes work after the HTTP request is gone. Complete, fail, and set-input skip the live-user check.

## Wire it up

HTTP hosts register `IHttpContextAccessor` the platform way and pass a factory that resolves it from the built `IServiceProvider`.

> [tab] HTTP
>
> ```csharp theme={"dark"}
> 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. Each client-facing task call then reads `HttpContext.User`.

> [tab] Stdio
>
> ```csharp theme={"dark"}
> builder.Services.AddMcpServer()
>     .WithTasksPlus("local-user");
> ```

> [tab] Store only
>
> Same factory, without `IMcpServerBuilder`:
>
> ```csharp theme={"dark"}
> builder.Services.AddHttpContextAccessor();
> builder.Services.AddInMemoryMcpTaskStorePlus(
>     sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
>     o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
> ```

> [tab] Tests
>
> ```csharp theme={"dark"}
> IMcpTaskStorePlus store = new InMemoryMcpTaskStorePlus("user-1");
>
> var store2 = new InMemoryMcpTaskStorePlus(() => currentUserId);
>
> var created = await store2.CreateTaskAsync(httpContext.User);
>
> builder.WithTasksPlus(sp => sp.GetRequiredService<ClaimsPrincipal>());
> ```

> [!TIP]
>
> 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.

No user on a client-facing call throws [`McpTaskUserRequiredException`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskUserRequiredException).

## 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`. `null` TTL keeps tasks until the store discards them.

Claim lookup matches the SDK session identity order: `NameIdentifier`, `sub`, `UPN`, `Name`. Types are unsealed; subclass [`McpTaskStoreBase`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskStoreBase) to change isolation, TTL, or storage.

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

> [!WARNING]
>
> Do not store MCP task rows on domain tables. Tasks expire in minutes; orders and users do not.

## Samples

Each sample is a standalone Streamable HTTP host. Fake authentication is the `X-User-Id` header. One tool, `HelloWorld`, injects `ClaimsPrincipal` and returns `Task<string>` so a task-capable client gets a trackable task. The store is the only thing that changes.

> [tab] Basic
>
> In-memory. No extra infrastructure.
>
> ```bash theme={"dark"}
> dotnet run --project samples/CloudNimble.McpEssentials.Samples.Basic -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.
>
> [`samples/…Samples.Basic`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.Basic)

> [tab] Entity Framework Core
>
> SQLite file next to the app. Swap `UseSqlite` for any EF provider in a real host.
>
> ```bash theme={"dark"}
> dotnet run --project samples/CloudNimble.McpEssentials.Samples.EntityFrameworkCore -f net11.0
> ```
>
> MCP is at `http://localhost:5290/`.
>
> [`samples/…Samples.EntityFrameworkCore`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.EntityFrameworkCore)

> [tab] Redis
>
> Requires Redis at `localhost:6379` (or the connection string in `appsettings.json`).
>
> ```bash theme={"dark"}
> dotnet run --project samples/CloudNimble.McpEssentials.Samples.Redis -f net11.0
> ```
>
> MCP is at `http://localhost:5300/`.
>
> [`samples/…Samples.Redis`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.Redis)

> [tab] Cosmos DB
>
> Requires Cosmos DB or the emulator (connection string in `appsettings.json`).
>
> ```bash theme={"dark"}
> dotnet run --project samples/CloudNimble.McpEssentials.Samples.Cosmos -f net11.0
> ```
>
> MCP is at `http://localhost:5310/`.
>
> [`samples/…Samples.Cosmos`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.Cosmos)

> [tab] PostgreSQL
>
> Native Npgsql (not EF). Requires Postgres at the connection string in `appsettings.json`. `LISTEN`/`NOTIFY` wakes other nodes.
>
> ```bash theme={"dark"}
> dotnet run --project samples/CloudNimble.McpEssentials.Samples.PostgreSQL -f net11.0
> ```
>
> MCP is at `http://localhost:5320/`.
>
> [`samples/…Samples.PostgreSQL`](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.PostgreSQL)

## Swap the backing store

[`IMcpTaskStorePlus`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskStorePlus) and [`McpTaskStoreBase`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskStoreBase) are the contract durable providers implement. Pick a backing store in [Stores](https://si.easyaf.dev/mcpessentials/guides/stores.md). HTTP, Stdio, and Store only tabs on each store page match the wiring tabs above.

> [card] [Entity Framework Core](https://si.easyaf.dev/mcpessentials/guides/stores/entity-framework-core.md)
>
> Any EF provider. Host owns the DbContext and migrations.

> [card] [Redis](https://si.easyaf.dev/mcpessentials/guides/stores/redis.md)
>
> Native expiry and pub/sub for multi-node elicitation.

> [card] [Cosmos DB](https://si.easyaf.dev/mcpessentials/guides/stores/cosmos.md)
>
> Point reads by owner, native document TTL, index for background complete.

> [card] [PostgreSQL](https://si.easyaf.dev/mcpessentials/guides/stores/postgresql.md)
>
> Npgsql without EF. LISTEN/NOTIFY wakes elicitation on other nodes.

## Related

> [card] [Form elicitation](https://si.easyaf.dev/mcpessentials/guides/form-elicitation.md)
>
> Ask the client for a form while a task is running.

> [card] [Analyzers](https://si.easyaf.dev/mcpessentials/guides/analyzers.md)
>
> Catch broken forms at compile time.


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