Production-grade persistence backends for the MCP Python SDK
Project description
mcp-persist
Production-grade persistence backends for the MCP Python SDK.
The MCP SDK ships an EventStore interface but only an in-memory reference implementation. mcp-persist provides backends for real deployments where you need durability across process restarts and multi-worker environments.
Backends
| Backend | Extra | Use case |
|---|---|---|
SQLiteEventStore |
sqlite |
Single-process SSE resumability across restarts, with no external service |
RedisEventStore |
redis |
Multi-process / multi-worker SSE resumability |
Installation
# SQLite backend (no external service needed)
pip install "mcp-persist[sqlite]"
# Redis backend
pip install "mcp-persist[redis]"
# Both
pip install "mcp-persist[sqlite,redis]"
Quickstart
SQLite
import aiosqlite
from mcp_persist import SQLiteEventStore
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
conn = await aiosqlite.connect("events.db")
store = SQLiteEventStore(conn, ttl=3600) # 1 hour TTL
await store.initialize()
session_manager = StreamableHTTPSessionManager(
app=mcp_server,
event_store=store,
)
Redis
import redis.asyncio as aioredis
from mcp_persist import RedisEventStore
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
redis_client = aioredis.from_url("redis://localhost:6379")
store = RedisEventStore(redis_client, ttl=3600) # 1 hour TTL
session_manager = StreamableHTTPSessionManager(
app=mcp_server,
event_store=store,
)
SQLiteEventStore
Stores MCP SSE events in a SQLite database so a single-process server can resume interrupted streams across restarts and redeploys — without running Redis or any other external service. Ideal for single-node deployments, local development, and edge/embedded hosts.
For load-balanced or multi-worker deployments, use
RedisEventStoreinstead — SQLite is single-writer and not designed for shared multi-process access.
How it works
One row per event:
{table}.event_id — INTEGER PRIMARY KEY AUTOINCREMENT, monotonic event IDs (never reused)
{table}.stream_id — TEXT, the stream the event belongs to
{table}.payload — TEXT, serialized JSONRPCMessage ("" for priming events)
{table}.created_at — REAL, unix timestamp used for TTL expiry
- Monotonic IDs via
AUTOINCREMENT— strictly increasing, never reused, same guarantee as RedisINCR - Indexed replay —
WHERE stream_id = ? AND event_id > ?over a(stream_id, event_id)index - Durable across restarts — WAL journaling; events survive process exit
- TTL support — expired events are skipped on replay and removed by
purge_expired() - Multi-tenant isolation via configurable
table_name - Priming event handling — sentinel empty-string payloads are stored but never replayed
Configuration
SQLiteEventStore(
conn, # an open aiosqlite.Connection
table_name="mcp_events", # isolate multiple servers in one database file
ttl=3600, # seconds; None = never expire (not recommended)
)
TTL note: SQLite has no automatic key expiry. Events past ttl are skipped on
replay, but to reclaim disk space call await store.purge_expired() periodically
(e.g. from a background task). It returns the number of rows deleted.
Multi-tenant deployments
If multiple MCP servers share a database file, use different table names:
store_a = SQLiteEventStore(conn, table_name="server_a")
store_b = SQLiteEventStore(conn, table_name="server_b")
RedisEventStore
Stores MCP SSE events in Redis so clients can resume interrupted streams — even across worker restarts or load-balanced deployments.
How it works
Redis data layout:
{prefix}counter — atomic INCR source for monotonic event IDs
{prefix}event:{event_id} — HASH: stream_id + serialized payload
{prefix}stream:{stream_id} — ZSET: event IDs sorted by score for O(log N) range queries
- Atomic monotonic IDs via Redis
INCR— collision-free across concurrent workers - O(log N) replay via sorted set
ZRANGEBYSCORE - TTL support — automatic key expiry to prevent unbounded memory growth
- Multi-tenant isolation via configurable
key_prefix - Priming event handling — sentinel empty-string payloads are stored but never replayed to clients
Configuration
RedisEventStore(
redis, # redis.asyncio.Redis instance
key_prefix="mcp:", # isolate multiple servers on one Redis instance
ttl=3600, # seconds; None = never expire (not recommended)
)
TTL guidance: Set ttl to at least 2× your session idle timeout. If you leave it as None, a warning is logged and events accumulate indefinitely.
Multi-tenant deployments
If multiple MCP servers share a Redis instance, use different prefixes:
store_a = RedisEventStore(redis_client, key_prefix="server-a:")
store_b = RedisEventStore(redis_client, key_prefix="server-b:")
Examples
The examples/ directory contains minimal, runnable MCP servers
that wire each backend into a real
StreamableHTTPSessionManager:
| File | Backend | Run |
|---|---|---|
sqlite_server.py |
SQLiteEventStore |
python examples/sqlite_server.py |
redis_server.py |
RedisEventStore |
python examples/redis_server.py |
Each example is a self-contained note-taking MCP server (tools, resources) that
you can connect to with any MCP client at http://localhost:8000/mcp.
See examples/README.md for prerequisites, setup, and a
client snippet.
Development
git clone https://github.com/Ar-maan05/mcp-persist
cd mcp-persist
pip install -e ".[redis,sqlite,dev]"
pytest tests/
Tests use fakeredis and in-memory SQLite (aiosqlite) — no external servers required.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_persist-0.2.0.tar.gz.
File metadata
- Download URL: mcp_persist-0.2.0.tar.gz
- Upload date:
- Size: 66.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaaa0d5887f7d77576de9bffd2fcc656f60ccbbcda4689ee760173ee6f073dcf
|
|
| MD5 |
b630b310ec116e9fabc9f6ef6154f388
|
|
| BLAKE2b-256 |
b5e63f47916e206c85a9208392f28f2d8a305ea844db7ffc4357d1605bad10ce
|
File details
Details for the file mcp_persist-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp_persist-0.2.0-py3-none-any.whl
- Upload date:
- Size: 9.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
311f1f39f1a59302adab393d84f53384d3ce8f8ef9ab8a4abb832e4d74b92b97
|
|
| MD5 |
e06e397457bcb3759a7b633cdfb5baf5
|
|
| BLAKE2b-256 |
7387a2a5a8202b0985edf81aa10737849b68b216fabb1f3ccc08474bbbd77e3f
|