Session Memory MCP Server
A Model Context Protocol (MCP) server that gives AI agents memory that lasts. Built in Rust with rmcp for the protocol layer and SQLite for storage — no database server to run.
14 tools covering semantic recall, embedding disclosure, provenance-tracked writes, confirmation of inferred facts, temporal supersession, PII redaction, typed session state, replay snapshots, and resumable human-in-the-loop workflows.
Install
cargo install --git https://github.com/zavora-ai/mcp-session-memory --tag v1.3.0
The tagged GitHub install provides the trained local model in this release immediately. The
unqualified cargo install mcp-session-memory command may resolve to an older crates.io release.
This compiles and installs the binary to ~/.cargo/bin/mcp-session-memory.
Requires Rust 1.94.1 or newer. The default embedding model downloads once on first start and then runs locally from cache.
Quick start
# Remember across restarts. Without MEMORY_DB, memory lasts only for this run.
export MEMORY_DB=~/.agent-memory.db
mcp-session-memory
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"session-memory": {
"command": "mcp-session-memory",
"env": { "MEMORY_DB": "/Users/you/.agent-memory.db" }
}
}
}
Kiro / Cursor
Same shape, in .kiro/settings/mcp.json or .cursor/mcp.json:
{
"mcpServers": {
"session-memory": {
"command": "mcp-session-memory",
"env": { "MEMORY_DB": "${HOME}/.agent-memory.db" }
}
}
}
Features
- SQLite persistence included by default — set
MEMORY_DBto keep memories, sessions, events and snapshots in one file; without it the server explicitly uses run-local storage - Workflows survive restarts — a run paused for a human approval is still paused tomorrow
- Semantic recall — "how should invoicing be handled" finds "always send invoices as a PDF"
- Trained local embeddings — quantized all-MiniLM-L6-v2 runs in process and recovers synonymy; model files download once and are then reusable offline
- Runtime disclosure —
get_embedding_statusreports the active backend, dimensions, locality, synonym support, and fallback limitation - Provenance on every write —
told,observed,correctedorimported, so you can audit why an agent believes something - Confirmation gate — anything inferred is held as
proposedand cannot influence the agent until confirmed - Temporal supersession — a memory that stops being true is superseded and linked, not erased
- Automatic deduplication — the same fact told twice reinforces one memory instead of creating two
- Settledness ranking — repeatedly confirmed memories are recalled first
- Subject scoping — memory is partitioned by subject; one user's facts never reach another's
- Five memory types — turn, session state, profile, project, artifact
- Data classification — public, internal, PII, financial, health, legal-sensitive
- PII redaction —
redact_memoryscrubs sensitive spans in place - Replay snapshots — freeze session state for deterministic replay
- Resumable HITL — pause for approval, resume where you left off
- MCP 2025 + 2026 — stdio compatibility for both revisions, with Tasks, structured results, annotations, cache metadata, and sealed MRTR approvals
Configuration
Cargo features: sqlite and local-model are enabled by default. all-backends is retained as an alias for both supported components; this release does not claim unimplemented Redis or Postgres storage.
Architecture
A request arrives over stdio and lands on one of 14 tools. Reads go through the recall path, which embeds the query and ranks stored memories by similarity. Writes go through the confirmation gate, which decides whether a memory acts immediately or waits to be agreed to. Both then reach the store traits, and whichever backend is configured behind them.
Everything durable lives in one file: memories with their vectors, session state, the event trail, and replay snapshots. One artefact to back up, move or delete, and no daemon to run.
src/
├── main.rs Entry point — transport, backend selection, MEMORY_DB
├── server.rs MCP tool router — 14 tools registered
├── lib.rs Crate root
├── error.rs Error types
├── types.rs Session, MemoryEntry, Provenance, MemoryStatus, DataClass
├── store.rs MemoryStore and SessionStore traits
├── semantic.rs Embedder trait, trained MiniLM default, explicit lexical fallback
├── memory_store.rs In-memory backend — this run only
├── sqlite_store.rs Memories and vectors in one file
└── sqlite_sessions.rs Sessions, events and snapshots in the same file
Where the design decisions live
How memory works
Three design choices are worth knowing about, because they change how you use the tools.
Recall is by meaning
retrieve_memory embeds your query and ranks stored memories by cosine similarity, so a
question worded differently still finds the right fact.
The default embedder is the quantized sentence-transformers/all-MiniLM-L6-v2 model through
FastEmbed and ONNX. Model files download on first use, are cached locally, and inference never
sends memory text to a hosted embedding API. A real inference smoke test verifies that a query
using cheap ranks a memory using inexpensive above an unrelated sentence.
MCP_MEMORY_EMBEDDER=lexical selects the legacy hashed word/trigram implementation explicitly.
It is deterministic and needs no model file, but it cannot reliably recover synonyms. That
limitation appears in the retrieve_memory tool description and in get_embedding_status, not
only in this README.
When a query matches nothing above the active model's relevance floor, recall returns an empty
list. It never presents an unrelated memory as a semantic hit. Omit query when the intended
operation is to list what is known about the subject, most settled first.
Inferred facts wait for confirmation
Every write carries a provenance:
Anything but told is stored as proposed, and proposed memories are never returned by
recall. So an agent cannot quietly acquire beliefs its user never gave it — and a stray
instruction inside a document cannot install itself as a permanent preference.
One file holds everything durable
MEMORY_DB covers memories and session state, events and replay snapshots. That matters
most for human-in-the-loop work: a workflow paused for an approval is still paused after a
restart, at the state version it reached, with its typed state intact — which is what makes
resume_session mean anything.
It also means an operator has one artefact to back up, move, or delete.
Nothing true is thrown away
When a fact changes, use supersede_memory rather than delete_memory. It writes the
replacement, marks the old entry superseded, and links the two. The old memory stops being
recalled but stays on the record, so an agent asked why did you do that? can still answer.
delete_memory is there for when erasure is genuinely what you want.
Tools (14)
Examples
Get session state
Prompt: "What's the current state of the refund session?"
{
"session_id": "ses_demo_001",
"agent_id": "support_agent",
"status": "paused",
"current_node": "manager_approval_gate",
"state_version": 3,
"workflow_state": {
"refund_amount": 89.99,
"eligibility": "approved",
"approval_required": true
},
"memory_refs": ["mem_demo_001"],
"data_classes": ["financial"]
}
Recall by meaning
Prompt: "How does this customer like to be contacted?"
Tool call: retrieve_memory
{
"subject_type": "customer",
"subject_id": "cust_1187",
"query": "how does this customer like to be contacted"
}
The stored memory says "prefers email follow-up" — no word in common with the question
except "customer", and it still comes back first. Omit query to get everything known
about the subject, most settled first.
Output:
[
{
"memory_id": "mem_demo_001",
"memory_type": "profile_memory",
"content": "Customer prefers email follow-up. Previous refund approved in March 2026.",
"data_class": "pii",
"confidence": 0.91,
"version": 1,
"status": "active"
}
]
Store a new memory
Prompt: "Remember that this customer's preferred language is Swahili"
{
"memory_type": "profile_memory",
"subject_type": "customer",
"subject_id": "cust_1187",
"content": "Preferred language: Swahili",
"data_class": "pii",
"provenance": "told",
"confidence": 0.95
}
Output:
{ "memory_id": "mem_a1b2c3d4...", "status": "stored", "message": "Stored." }
Told the same thing again, it reinforces rather than duplicating:
{
"memory_id": "mem_a1b2c3d4...",
"status": "already_known",
"reinforcements": 1,
"message": "A memory this close already exists; it has been reinforced rather than duplicated."
}
Store something you inferred
Prompt: "They've shortened my summaries three times — remember they like them brief"
{
"memory_type": "profile_memory",
"subject_type": "user",
"subject_id": "u_42",
"content": "Prefers summaries under 150 words",
"provenance": "observed"
}
Output:
{
"memory_id": "mem_9f8e7d6c...",
"status": "awaiting_confirmation",
"message": "Held until someone confirms it. It will not influence anything yet."
}
Call confirm_memory with that id and it starts being recalled.
Record that something changed
Prompt: "Payment terms moved from net 30 to net 60"
{
"memory_id": "mem_terms_001",
"content": "Net 60 terms for all customers",
"reason": "policy changed Q3"
}
Output:
{
"superseded": "mem_terms_001",
"replaced_by": "mem_terms_002",
"reason": "policy changed Q3",
"status": "superseded"
}
Resume a paused workflow
Prompt: "The manager approved the refund, resume the session"
{
"session_id": "ses_demo_001",
"resume_payload": { "approval_decision": "approved", "approver": "manager_jane" },
"reason": "Manager approved refund"
}
Output:
{ "session_id": "ses_demo_001", "status": "active", "resumed": true }
Create replay snapshot
Prompt: "Freeze this session state for replay testing"
{ "session_id": "ses_demo_001" }
Output:
{
"snapshot_id": "snap_f7a2b1c4...",
"session_id": "ses_demo_001",
"state_version": 3
}
Terminate with audit reason
Prompt: "Stop this session — suspected policy violation"
{
"session_id": "ses_demo_001",
"reason": "Suspected policy violation during refund flow",
"reason_code": "policy_violation"
}
Output:
{ "session_id": "ses_demo_001", "status": "terminated", "reason": "Suspected policy violation..." }
Memory Types
Build from source
git clone https://github.com/zavora-ai/mcp-session-memory
cd mcp-session-memory
cargo build --release
cargo test
Contributors
License
Apache-2.0 — see LICENSE for details.
Part of the ADK-Rust Enterprise MCP server ecosystem.
Built with ❤️ by Zavora AI
Registry Compliance
This server implements the ADK MCP SDK contract:
- HealthCheck — async health probe for registry monitoring
- mcp-server.toml — manifest declaring tools, risk classes, and credentials
- Structured tracing —
RUST_LOGenv-filter for observability
rmcp and MCP compatibility
This server is built with rmcp 3.1.2 and requires Rust 1.94.1 or newer. The rmcp 3 rollout retains legacy MCP initialization compatibility and targets MCP protocol revisions 2025-11-25 and 2026-07-28.
MCP 2026-07-28 rollout (P4 workflow/business)
This server uses exact rmcp 3.1.2 and adk-mcp-sdk 0.3 with a minimum supported
Rust version of 1.94.1. It accepts stateless MCP 2026 requests with
per-request protocol, client identity, and capability metadata while retaining
the legacy MCP 2025-11-25 initialize flow for ordinary tools.
- Structured results: all 14 tools advertise output schemas and annotations; MCP 2026 clients receive parsed
structuredContent, and{ "ok": false }responses are marked as errors. - Tasks:
retrieve_memory,store_memory,supersede_memory,update_memory, andredact_memory, each with a 10-minute TTL and progress status. - MRTR approvals:
delete_memory,redact_memory,resume_session,terminate_session - Discovery and routing: rmcp serves on-demand discovery and validates the
per-request protocol envelope; HTTP deployments can route with
Mcp-MethodandMcp-Name. The packaged binary currently uses stdio. - Caching:
tools/listreturns a publicttlMsof 86,400,000 for MCP 2026; rmcp omits the cache fields for legacy clients. - Deprecated extensions: this server does not add new Roots, Sampling, or dynamic client-registration dependencies.
Protected tools require MCP_REQUEST_STATE_KEY with at least 32 high-entropy
bytes. All replicas must share that key so sealed approval state can resume on
another instance. Approval state is bound to the client identity, tool, and
arguments and expires after two minutes. Missing identity, invalid state,
rejection, or legacy protocol use fails closed. Task records are process-local
for the current stdio runtime; use a durable task store before deploying the
server behind scale-to-zero HTTP infrastructure.