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

# Cosmos DB

> Persist per-user MCP tasks as Cosmos documents. Partitioned by owner, native document TTL, and an index so background completion still finds the row.

`McpEssentials.Cosmos` is the authenticated task store for hosts that already run Azure Cosmos DB for NoSQL. It subclasses [`McpTaskStoreBase`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskStoreBase) and persists [`StoredMcpTask`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/StoredMcpTask) snapshots as documents.

Prefer this over [Entity Framework Core](https://si.easyaf.dev/mcpessentials/guides/stores/entity-framework-core.md) with `UseCosmos` when you want in-partition point reads and native document TTL. Cosmos expires documents; the request-path sweep is off by default.

> [card] Partition by owner
>
> Task documents live under `/ownerUserId`. Client-facing get is a point read.

> [card] Native document TTL
>
> `ttl` is `DiscardAfter` as whole seconds. `AutoSweep` stays off.

> [card] Index for complete
>
> `McpTaskIndex` maps `taskId` → `ownerUserId` so background complete still finds the row.

The host owns [`CosmosClient`](https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-dotnet-get-started). This package ships the store and registration. Register the client as a singleton.

## Install

**1. Add the package**

```bash theme={"dark"}
dotnet add package McpEssentials.Cosmos
```

**2. Register the client**

Pass [`CosmosMcpTaskStore.DefaultClientOptions()`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Cosmos/Tasks/CosmosMcpTaskStore). The Cosmos SDK default serializer is Newtonsoft; without these options, `id` / `ttl` / camelCase enum names on task documents do not map.

```csharp theme={"dark"}
builder.Services.AddSingleton(_ => new CosmosClient(
    connectionString,
    CosmosMcpTaskStore.DefaultClientOptions()));
```

## Wire the store

Same identity factory as [Authenticated tasks](https://si.easyaf.dev/mcpessentials/guides/authenticated-tasks.md). Cosmos has no generic context type, so the MCP builder method is [`WithCosmosTasks`](https://si.easyaf.dev/mcpessentials/api-reference/Microsoft/Extensions/DependencyInjection/IMcpServerBuilder) rather than `WithTasksPlus<TContext>`.

> [tab] HTTP
>
> ```csharp theme={"dark"}
> builder.Services.AddHttpContextAccessor();
> builder.Services.AddSingleton(_ => new CosmosClient(
>     connectionString,
>     CosmosMcpTaskStore.DefaultClientOptions()));
>
> builder.Services.AddMcpServer()
>     .WithHttpTransport(o => o.Stateless = true)
>     .WithTools<MyTools>()
>     .WithCosmosTasks(
>         sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
>         o =>
>         {
>             o.DefaultTimeToLive = TimeSpan.FromMinutes(5);
>             o.DatabaseId = "mcpessentials";
>             o.TasksContainerId = "McpTasks";
>             o.IndexContainerId = "McpTaskIndex";
>         });
> ```

> [tab] Stdio
>
> ```csharp theme={"dark"}
> builder.Services.AddSingleton(_ => new CosmosClient(
>     connectionString,
>     CosmosMcpTaskStore.DefaultClientOptions()));
>
> builder.Services.AddMcpServer()
>     .WithCosmosTasks("local-user");
> ```

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

## Containers and TTL

| Container | Partition | Role |
| - | - | - |
| `McpTasks` | `/ownerUserId` | Task document, `id` = task id |
| `McpTaskIndex` | `/id` | `taskId` → `ownerUserId` for server-side complete |

`CreateTaskAsync` writes the task, then the index. If the index write fails, the task document is deleted. Client-facing get/cancel/resolve are in-partition. Background complete/fail/input-request read the index first, then point-read the task.

`DiscardAfter` is stamped at write as `CreatedAt + 2×TimeToLive`. The document `ttl` property is that instant as whole seconds from now (at least 1). The index row gets the same `ttl` so it does not outlive the task. Unlimited TTL (`null`) leaves both documents until a terminal owner get (`ServeAndDrop`) or an explicit delete.

Both containers enable per-document TTL (`DefaultTimeToLive = -1`). `AutoSweep` defaults to `false`. [`SweepExpiredAsync`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskStorePlus) is a no-op; Cosmos owns expiry. Per-row get still honors `ShouldDiscard` so a custom clock can drop a document before Cosmos does.

On first use the store creates the database and containers when they are missing. Default throughput is 400 RU/s (`CosmosMcpTaskStoreOptions.Throughput`).

> [!NOTE]
>
> Serverless accounts should pre-create the containers and set `Throughput` to `null`.

## Isolation

`CreateTaskAsync` stamps `OwnerUserId`. Client-facing get/cancel/resolve require that user. Server-side complete/fail/input-request skip the live-user check. Another user's task is not found. A missing document is not found; get does not distinguish expiry from isolation.

Updates replace the task document with an ETag `IfMatch`. A terminal owner get with `ServeAndDrop` (default) deletes the task and the index row after serving it.

Do not encode the user id in the task id.

## Events

v1 does not subscribe to the change feed. After a successful resolve, the node raises `InputResponseReceived` locally.

> [!WARNING]
>
> Multi-node `ElicitAsync` needs an [`IMcpTaskInputSignalBus`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskInputSignalBus). Azure Service Bus is the usual pair. Missing bus plus multiple nodes means `ElicitAsync` waits until timeout.

## Related

> [card] [Sample](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.Cosmos)
>
> Runnable host. Expects the emulator at `https://localhost:8081/`.

> [card] [Stores](https://si.easyaf.dev/mcpessentials/guides/stores.md)
>
> Pick a backing store by the engine you already run.

> [card] [Entity Framework Core](https://si.easyaf.dev/mcpessentials/guides/stores/entity-framework-core.md)
>
> Stay on EF when the host already maps Cosmos with `UseCosmos`.

> [card] [Authenticated tasks](https://si.easyaf.dev/mcpessentials/guides/authenticated-tasks.md)
>
> Identity factory, isolation rules, and the in-memory store.


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