feat: add RedisEventStore for production SSE resumability#2685
feat: add RedisEventStore for production SSE resumability#2685Ar-maan05 wants to merge 12 commits into
Conversation
…deprecation warnings
|
Thanks for the thorough work here — the design writeup and test coverage are well done. We're going to decline this one, for two reasons that compound: The upcoming spec revision removes SSE resumability from the streamable HTTP transport entirely (SEP-2575, merged into the draft on 2026-05-11 — the Separately, and this would apply even without the spec change: we keep concrete infrastructure backends out of the SDK and ship only the abstraction. That matches the other official SDKs — the TypeScript SDK ships the If you'd like this to live on while 2025-11-25 is still the current revision, publishing it as its own package on PyPI is the right home — the |
|
Thanks for the detailed feedback. I have taken your suggestion and published this as a standalone package: mcp-persist (pip install "mcp-persist[redis]"). It builds against the stable EventStore ABC as you recommended. Happy to hear any thoughts. |
Description
Closes #2570
This PR adds a production-grade, Redis-backed EventStore ( RedisEventStore ) to support SSE stream resumability in multi-process/multi-worker deployments.
The implementation resides in a new mcp.server.contrib package designed for production-grade add-ons requiring optional dependencies.
Design & Architecture
• Atomic Monotonic IDs: Uses a Redis string counter ( {prefix}counter ) and INCR to generate unique, monotonic integer event IDs safely across concurrent processes.
• Event Metadata: Uses a Redis Hash ( {prefix}event:{event_id} ) storing stream_id and the serialized JSONRPCMessage payload.
• Stream Ordering: Uses a Sorted Set ( {prefix}stream:{stream_id} ) to map event IDs to their integer score, allowing ZRANGEBYSCORE to perform
• Storing a message = None sentinel writes an empty string to Redis to identify priming events, which are skipped on replay.
• Cleans up keys using an optional ttl (Time to Live) on all keys generated ( counter , event , stream ). Logs a warning if ttl=None to prevent unbounded memory growth.
• Supports dynamic key_prefix to isolate multiple MCP servers sharing a single Redis instance/cluster.
• redis is placed in [project.optional-dependencies] under the redis extra.
• Type annotations/imports of redis.asyncio are enclosed inside if TYPE_CHECKING: guards. This ensures the MCP SDK remains fully importable and functional at runtime for users who do not install the redis extra.
How to Use
Install the optional dependency:
Configure your SSE manager:
Verification & Testing
Unit Tests
Wrote 21 extensive async unit tests using fakeredis to avoid external server dependencies in CI/CD.
• Monotonic ID generation and collision-free concurrency.
• Correct message serialization and deserialization roundtrips.
• Stream boundary isolation and replay sort order.
• Expiry/TTL setting correctness.
• Key prefix multi-tenant isolation.
• Warning log emissions.
Result: 21 passed.
Quality & Linting
• Checked and validated with pyright : 0 errors.
• Checked and formatted with ruff : All checks passed.