Skip to main content

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 RedisEventStore instead — 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 Redis INCR
  • Indexed replayWHERE 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_persist-0.2.0.tar.gz (66.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_persist-0.2.0-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

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

Hashes for mcp_persist-0.2.0.tar.gz
Algorithm Hash digest
SHA256 aaaa0d5887f7d77576de9bffd2fcc656f60ccbbcda4689ee760173ee6f073dcf
MD5 b630b310ec116e9fabc9f6ef6154f388
BLAKE2b-256 b5e63f47916e206c85a9208392f28f2d8a305ea844db7ffc4357d1605bad10ce

See more details on using hashes here.

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

Hashes for mcp_persist-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 311f1f39f1a59302adab393d84f53384d3ce8f8ef9ab8a4abb832e4d74b92b97
MD5 e06e397457bcb3759a7b633cdfb5baf5
BLAKE2b-256 7387a2a5a8202b0985edf81aa10737849b68b216fabb1f3ccc08474bbbd77e3f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page