Skip to main content
McpEssentials.EntityFrameworkCore is the first durable authenticated task store. It subclasses McpTaskStoreBase and persists McpTaskEntity rows through your DbContext.

Any EF provider

SQL Server, SQLite, PostgreSQL, MySQL, MariaDB, Cosmos via EF, Oracle — whatever you already pass to AddDbContext.

Host owns the model

Your context, your connection, your migrations. The store opens a scope per operation.

Optional bus

Multi-node ElicitAsync takes IMcpTaskInputSignalBus.
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 on ModelBuilder and on DbContextOptionsBuilder, and a singleton store that opens a scope per operation so a standard scoped AddDbContext works.

Install

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

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.
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.
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. The SDK WithTasks API captures the store before Build(); WithTasksPlus 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.

Options

Pass Action<McpTaskStoreOptions> as the next argument after identity.
Call SweepExpiredAsync 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 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.

PostgreSQL

Npgsql or Dapper, and LISTEN / NOTIFY, with no DbContext.

SQL Server

ADO.NET or Dapper only. Specified; use this package with UseSqlServer until it ships.

Sample

Runnable SQLite host.

Stores

Pick a backing store by the engine you already run.

Authenticated tasks

Identity factory, isolation rules, and the in-memory store.

Cosmos DB

Native Cosmos when you want in-partition point reads instead of UseCosmos.