AddMcpServer() and fills the gaps that show up the moment the server is generic — more than one user, more than one node, more than a demo.
What the SDK already does well
A single-purpose server on one process can live on the SDK alone.InMemoryMcpTaskStore is process-wide by design. ElicitAsync sends whatever schema you hand it. Stateless HTTP is the right default for a tool that does not care who called it.
A production MCP server cares. Tasks are long-running. Clients poll tasks/get. Users share a host. Nodes share a database. Forms have fields that skip or collide. That is the surface McpEssentials owns.
What a real-world server still needs
Per-user isolation
The user who started a task is the only one who can poll, update, or cancel it. A guessed task id is not found.
Durable stores
Tasks survive process death and scale-out. Same contract on EF Core, Redis, Cosmos, and native Postgres.
Fluent forms
FormElicitation.Create and From<T>(). await server.ElicitAsync(form) parks the form on the task.Compile-time forms
MCPESS001–006. A nested
From<T>() property or a colliding wire name fails the build.Isolation is the first rule
The SDKInMemoryMcpTaskStore treats a task id as enough. Under Streamable HTTP that is correct for a single-purpose server. A generic remote server needs one more rule: the owner is recorded on create, and client-facing get / cancel / resolve require that owner.
InMemoryMcpTaskStorePlus is our own in-memory store. It does not wrap the SDK store. McpTaskStoreBase is the contract every durable provider implements.
Server-side complete, fail, and set-input skip the live-user check. The SDK finishes work after the HTTP request is gone, from a background DI scope. The owner on the snapshot is enough.
Identity stays a host concern. Core takes a factory (ClaimsPrincipal, user id, or IServiceProvider). HTTP hosts call AddHttpContextAccessor() and pass HttpContext.User. Core never takes IHttpContextAccessor.
Persistence on the engine you already run
In-memory dies with the process. A real host already has a database, a cache, or a document store. McpEssentials persists the sameStoredMcpTask snapshot there.
The identity factory stays the same when you swap. TTL, sweep, and
ServeAndDrop are Core knobs. Redis and native Postgres are the elicitation bus (NotifyingMcpTaskStoreBase). EF and Cosmos take an optional IMcpTaskInputSignalBus so several nodes waiting on ElicitAsync still wake.
Forms a compiler can see
MCP elicitation is a flat object of primitives. Hand-rolled JSON schema is easy to get slightly wrong.FormElicitation is a fluent builder: field by field, From<T>(), or a loop over metadata.
Inside a task, await server.ElicitAsync(form) is enough. The SDK interceptor parks the form. Analyzers in Core turn a nested lambda or a colliding camelCase name into dotnet build diagnostics.
How it sits on the SDK
AddMcpServer, WithHttpTransport, and WithTools are the SDK. WithTasksPlus is McpEssentials. Stdio is .WithTasksPlus("local-user"). Durable packages add WithRedisTasks, WithCosmosTasks, WithPostgreSqlTasks, or WithTasksPlus<TContext>.
What you keep
You keep the official SDK. You keep your auth, yourDbContext, your Redis multiplexer, your Cosmos client, your NpgsqlDataSource. McpEssentials ships the store, the form builder, and the analyzers. The host owns the connection.
Start here
Quickstart
Isolate a task, elicit a form, swap the store.
Authenticated tasks
Isolation rules, overloads, and the samples.
Form elicitation
Field-by-field,
From<T>(), and parking a form on a task.Choose a store
EF Core, Redis, Cosmos, native Postgres, and the specified family.