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

# Entity Framework Core

> Persist per-user MCP tasks on any EF Core database. The host owns the DbContext, the connection, and migrations.

`McpEssentials.EntityFrameworkCore` is the first durable authenticated task store. It subclasses [`McpTaskStoreBase`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskStoreBase) and persists [`McpTaskEntity`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/EntityFrameworkCore/Tasks/McpTaskEntity) rows through **your** `DbContext`.

> [card] Any EF provider
>
> SQL Server, SQLite, PostgreSQL, MySQL, MariaDB, Cosmos via EF, Oracle — whatever you already pass to `AddDbContext`.

> [card] Host owns the model
>
> Your context, your connection, your migrations. The store opens a scope per operation.

> [card] Optional bus
>
> Multi-node `ElicitAsync` takes [`IMcpTaskInputSignalBus`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskInputSignalBus).

> [!NOTE]
>
> This package works with **any EF Core database provider**. The store talks to `TContext.Set<McpTaskEntity>()`; it never binds a specific engine.

The host owns the context, the connection, and migrations. This package ships the entity, [`AddMcpTasksTable`](https://si.easyaf.dev/mcpessentials/api-reference/Microsoft/EntityFrameworkCore/ModelBuilder) on `ModelBuilder` and on [`DbContextOptionsBuilder`](https://si.easyaf.dev/mcpessentials/api-reference/Microsoft/EntityFrameworkCore/DbContextOptionsBuilder), and a singleton store that opens a scope per operation so a standard scoped `AddDbContext` works.

## Install

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

Register an EF Core provider on the context the way you already do:

```csharp UseSqlServer theme={"dark"}
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));
```

```csharp UseSqlite theme={"dark"}
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlite("Data Source=app.db"));
```

```csharp UseNpgsql theme={"dark"}
builder.Services.AddDbContext<AppDbContext>(o => o.UseNpgsql(connectionString));
```

```csharp UseCosmos theme={"dark"}
builder.Services.AddDbContext<AppDbContext>(o => o.UseCosmos(accountEndpoint, accountKey, databaseName));
```

```csharp UseMySql theme={"dark"}
builder.Services.AddDbContext<AppDbContext>(o => o.UseMySql(connectionString, serverVersion));
```

## Map the table

Keep MCP task rows on their own table; they expire in minutes. The default name is `McpTasks`. Pass a name and schema on either call to change it: `AddMcpTasksTable("AppTasks", "mcp")`.

The store talks to `TContext.Set<McpTaskEntity>()`. The host does not need a `DbSet`. Both options use the **same** host `DbContext`. Pick **one**.

> [tab] OnModelCreating
>
> Use this in a single-project app, or whenever the project that owns `AppDbContext` already references this package. Mapping lives with the rest of the model. Migrations from that project see the table.
>
> ```csharp theme={"dark"}
> protected override void OnModelCreating(ModelBuilder modelBuilder)
> {
>     modelBuilder.AddMcpTasksTable();
> }
> ```

> [tab] AddDbContext
>
> Use this when `AppDbContext` lives in a class library that must stay free of MCP. Only the application project takes this package. Call `AddMcpTasksTable` on the options next to `UseSqlServer` (or whatever provider you already use). EF still uses the same host `DbContext`; the mapping runs after `OnModelCreating`.
>
> ```csharp theme={"dark"}
> builder.Services.AddDbContext<AppDbContext>(o =>
> {
>     o.UseSqlServer(connectionString);
>     o.AddMcpTasksTable();
> });
> ```
>
> Run migrations with the application as the startup project so the options hook is in the model:
>
> ```bash theme={"dark"}
> dotnet ef migrations add AddMcpTasks --startup-project path/to/App
> ```
>
> > [!WARNING]
> >
> > A migration from the context project alone will not see `McpTasks` and can emit a drop.

Mapped columns include `TaskId`, `OwnerUserId`, `Status` (enum name as string), timestamps, `TimeToLive`, `ExpiresAt`, `DiscardAfter` (UTC ticks so every provider, including SQLite, can `ExecuteDelete`), JSON payloads, and an application-managed `RowVersion`. Indexes cover owner, `(OwnerUserId, TaskId)`, `ExpiresAt`, and `DiscardAfter`. SQL Server hosts may remap `RowVersion` as `rowversion` in their own configuration.

## Wire the store

Same identity factory as [Authenticated tasks](https://si.easyaf.dev/mcpessentials/guides/authenticated-tasks.md). The SDK `WithTasks` API captures the store before `Build()`; [`WithTasksPlus`](https://si.easyaf.dev/mcpessentials/api-reference/Microsoft/Extensions/DependencyInjection/IMcpServerBuilder) stamps the provider on first use.

Register the store as a **singleton**. The context stays scoped. Each operation opens an `IServiceScope`, resolves `TContext`, and disposes the scope.

> [tab] HTTP
>
> ```csharp theme={"dark"}
> builder.Services.AddHttpContextAccessor();
> builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));
>
> builder.Services.AddMcpServer()
>     .WithHttpTransport(o => o.Stateless = true)
>     .WithTools<MyTools>()
>     .WithTasksPlus<AppDbContext>(
>         sp => sp.GetRequiredService<IHttpContextAccessor>().HttpContext?.User,
>         o => o.DefaultTimeToLive = TimeSpan.FromMinutes(5));
> ```

> [tab] Stdio
>
> ```csharp theme={"dark"}
> builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlite("Data Source=tasks.db"));
> builder.Services.AddMcpServer()
>     .WithTasksPlus<AppDbContext>("local-user");
> ```

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

## Options

Pass `Action<McpTaskStoreOptions>` as the next argument after identity.

| Knob | Default | Role |
| - | - | - |
| `DefaultTimeToLive` | `null` (unlimited) | Work window ends at `CreatedAt + TTL`. The row is discarded at `CreatedAt + 2×TTL` (`DiscardAfter`), or sooner on a terminal owner get. |
| `DefaultPollIntervalMs` | `1000` | Hint for clients. |
| `AutoSweep` | `true` | Pulse a throttled flush after create and on get. |
| `SweepInterval` | 30 seconds | Minimum time between automatic pulses. `TimeSpan.Zero` skips the throttle. |
| `ServeAndDrop` | `true` | Owner get of a terminal snapshot serves it and then deletes the row. |

```csharp theme={"dark"}
.WithTasksPlus<AppDbContext>(currentUser, o =>
{
    o.DefaultTimeToLive = TimeSpan.FromMinutes(5);
    o.AutoSweep = false;          // you own the schedule
    o.ServeAndDrop = true;
});
```

Call [`SweepExpiredAsync`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskStorePlus) from a hosted service or SQL Agent wrapper when `AutoSweep` is off. The public handle always runs a full pass.

The EF drain is a set-delete: `DELETE WHERE DiscardAfter IS NOT NULL AND DiscardAfter <= now`. It does not load the table. SQLite stores `DiscardAfter` as UTC ticks so that comparison translates; SQL Server and PostgreSQL use the same path.

Per-row get and mutate still use `ShouldDiscard` (default `HasExpired`). Override `SweepExpiredCoreAsync` when bulk criteria must differ from the stamp.

## Events

A single instance raises `InputResponseReceived` in-process. Several nodes that wait on `ElicitAsync` need a bus: register [`IMcpTaskInputSignalBus`](https://si.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/IMcpTaskInputSignalBus) in DI (Redis, Service Bus, …). The store publishes after a successful resolve and subscribes so the originating process wakes up.

Payload is task id plus request id. Keep tokens and elicitation secrets off the bus; the row already has the response body.

> [!WARNING]
>
> Missing bus plus multiple nodes means elicitation waits until timeout. That is a host misconfiguration.

## Isolation

`CreateTaskAsync` stamps `OwnerUserId`. Client-facing get, cancel, and resolve require that user. Another user's task is not found. Server-side complete, fail, and set-input skip the live-user check because the SDK runs those from a background DI scope after the originating HTTP request is gone.

## When to use a native package instead

Stay on this package when the host already uses EF Core.

> [card] [PostgreSQL](https://si.easyaf.dev/mcpessentials/guides/stores/postgresql.md)
>
> Npgsql or Dapper, and `LISTEN` / `NOTIFY`, with no `DbContext`.

> [card] [SQL Server](https://si.easyaf.dev/mcpessentials/guides/stores/sql-server.md)
>
> ADO.NET or Dapper only. Specified; use this package with `UseSqlServer` until it ships.

## Related

> [card] [Sample](https://github.com/CloudNimble/McpEssentials/tree/main/samples/CloudNimble.McpEssentials.Samples.EntityFrameworkCore)
>
> Runnable SQLite host.

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

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

> [card] [Cosmos DB](https://si.easyaf.dev/mcpessentials/guides/stores/cosmos.md)
>
> Native Cosmos when you want in-partition point reads instead of `UseCosmos`.


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