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

# 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://ai.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/Core/Tasks/McpTaskStoreBase) and persists [`McpTaskEntity`](https://ai.easyaf.dev/mcpessentials/api-reference/CloudNimble/McpEssentials/EntityFrameworkCore/Tasks/McpTaskEntity) rows through **your** `DbContext`.

> [!NOTE]
>
> This package works with **any EF Core database provider**. SQL Server, SQLite, PostgreSQL (Npgsql), MySQL, MariaDB, Cosmos via EF, Oracle, and whatever else you already pass to `AddDbContext` are all valid. 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://ai.easyaf.dev/mcpessentials/api-reference/Microsoft/EntityFrameworkCore/ModelBuilder) on `ModelBuilder` and on [`DbContextOptionsBuilder`](https://ai.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={null}
dotnet add package McpEssentials.EntityFrameworkCore
```

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

```csharp theme={null}
builder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(connectionString));
// or: o.UseSqlite("Data Source=app.db");
// or: o.UseNpgsql(connectionString);
// or: o.UseCosmos(accountEndpoint, accountKey, databaseName);
// or: 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**.

| Call | When to pick it |
| - | - |
| `modelBuilder.AddMcpTasksTable()` in `OnModelCreating` | The project that owns `AppDbContext` already references this package. Mapping lives with the rest of the model. Migrations from that project see the table. |
| `options.AddMcpTasksTable()` in `AddDbContext` | `AppDbContext` lives in a class library that must stay free of MCP. Only the application project takes this package. Migrations must use the application as `--startup-project`. |

### `OnModelCreating`

Use this in a single-project app, or whenever the context project is already allowed to depend on MCP.

```csharp theme={null}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.AddMcpTasksTable();
}
```

### `AddDbContext`

Use this when the context type lives in a library (data, persistence, domain) that other projects consume without pulling MCP. 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={null}
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={null}
dotnet ef migrations add AddMcpTasks --startup-project path/to/App
```

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://ai.easyaf.dev/mcpessentials/guides/authenticated-tasks.md). The SDK `WithTasks` API captures the store before `Build()`; [`WithTasksPlus`](https://ai.easyaf.dev/mcpessentials/api-reference/Microsoft/Extensions/DependencyInjection/IMcpServerBuilder) stamps the provider on first use.

```csharp theme={null}
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));
```

**Stdio / single user**

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

**Store only**, without `IMcpServerBuilder`:

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

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

## 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={null}
.WithTasksPlus<AppDbContext>(currentUser, o =>
{
    o.DefaultTimeToLive = TimeSpan.FromMinutes(5);
    o.AutoSweep = false;          // you own the schedule
    o.ServeAndDrop = true;
});
```

Call [`SweepExpiredAsync`](https://ai.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.

## Multi-node elicitation

A single instance raises `InputResponseReceived` in-process. Several nodes that wait on `ElicitAsync` need a bus: register [`IMcpTaskInputSignalBus`](https://ai.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.

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. Reach for [PostgreSQL](https://ai.easyaf.dev/mcpessentials/guides/stores/postgresql.md) when you want `LISTEN` / `NOTIFY` without EF. Reach for [SQL Server](https://ai.easyaf.dev/mcpessentials/guides/stores/sql-server.md) when you want ADO.NET or Dapper only.

See [Stores](https://ai.easyaf.dev/mcpessentials/guides/stores.md) for the rest of the family.


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