Skip to main content
McpEssentials.Redis is the authenticated task store for hosts that already run Redis. It subclasses NotifyingMcpTaskStoreBase and persists StoredMcpTask snapshots as JSON strings. Prefer this over IDistributedCache when the cache is Redis and several nodes wait on ElicitAsync. Redis expires keys; the request-path sweep is off by default.

JSON strings

One key per task. OwnerUserId lives on the snapshot.

Native expiry

PEXPIREAT from DiscardAfter. AutoSweep stays off.

Pub/sub fan-out

Resolved input publishes on {prefix}:task:input. Echoes of this process are dropped.
The host owns IConnectionMultiplexer. This package ships the store and registration. Register the multiplexer as a singleton.

Install

1

Add the package

2

Register the multiplexer

Wire the store

Same identity factory as Authenticated tasks. Redis has no generic context type, so the MCP builder method is WithRedisTasks rather than WithTasksPlus<TContext>.

Keys and TTL

DiscardAfter is stamped at write as CreatedAt + 2×TimeToLive. Redis PEXPIREAT uses that instant. Unlimited TTL (null) leaves the key until a terminal owner get (ServeAndDrop) or an explicit delete. AutoSweep defaults to false. SweepExpiredAsync is a no-op; Redis owns expiry. Per-row get still honors ShouldDiscard so a custom clock can drop a key before Redis does. A custom ShouldDiscard that disagrees with DiscardAfter should also override SweepExpiredCoreAsync and skip native expiry.
Replica sets: set WaitReplicas so CreateTaskAsync does not return until a later get on another node can see the key. Standalone Redis leaves this at 0.

Isolation

CreateTaskAsync stamps OwnerUserId. Client-facing get/cancel/resolve require that user. Server-side complete/fail/input-request skip the live-user check. Another user’s task is not found. A missing key is not found; get does not distinguish expiry from isolation. Creates use a Lua EXISTS then SET. Updates compare the expected JSON then SET + PEXPIREAT. A terminal owner get with ServeAndDrop (default) deletes the key after serving it.

Events

After a successful resolve, the publishing node raises InputResponseReceived locally, then publishes taskId + newline + requestId on {prefix}:task:input. Other nodes subscribe and raise a stub response so ElicitAsync waiters wake. The publisher drops its own pub/sub echo.
Redis is the bus. Elicitation fan-out is pub/sub on {prefix}:task:input.

Sample

Runnable host. Expects Redis at localhost:6379.

Stores

Pick a backing store by the engine you already run.

PostgreSQL

The other native channel: LISTEN / NOTIFY on Npgsql.

IDistributedCache

Specified abstraction over whatever cache you already registered.