Skip to main content
The official MCP C# SDK is the right foundation: tools, resources, prompts, Streamable HTTP, stdio, and MCP Tasks (SEP-2663). McpEssentials is a companion, not a fork. It sits on 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 SDK InMemoryMcpTaskStore 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 same StoredMcpTask 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, your DbContext, 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.