Labsco
tejzpr logo

Smriti MCP

β˜… 6

from tejzpr

Smriti is a Model Context Protocol (MCP) server that provides persistent, graph-based memory for LLM applications. Built on LadybugDB (embedded property graph database), it uses EcphoryRAG-inspired multi-stage retrieval - combining cue extraction, graph traversal, vector similarity, and multi-hop association - to deliver human-like memory recall.

πŸ”₯πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedAccount requiredNeeds API keys
<p align="center"> <img src="smriti-logo.png" alt="Smriti Logo" width="200"/> </p> <h1 align="center">Smriti MCP</h1> <p align="center"> <a href="https://go.dev/"><img src="https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat&logo=go" alt="Go Version"></a> <a href="https://opensource.org/license/agpl-3-0"><img src="https://img.shields.io/badge/license-AGPL%20v3.0-green" alt="License: AGPL v3.0"></a> <a href="https://modelcontextprotocol.io/"><img src="https://img.shields.io/badge/MCP-Compatible-blue" alt="MCP"></a> <a href="https://hub.docker.com/r/tejzpr/smriti-mcp"><img src="https://img.shields.io/badge/Docker%20Hub-Container-blue?logo=docker" alt="Docker Hub"></a> <a href="https://github.com/tejzpr/smriti-mcp/actions"><img src="https://github.com/tejzpr/smriti-mcp/actions/workflows/codeql-analysis.yml/badge.svg" alt="CodeQL"></a> </p> <p align="center"><strong>Graph-Based AI Memory System with EcphoryRAG Retrieval and Leiden Clustering</strong></p>

Smriti is a Model Context Protocol (MCP) server that provides persistent, graph-based memory for LLM applications. It supports three database backends β€” LadybugDB (embedded), Neo4j, and FalkorDB β€” and uses EcphoryRAG-inspired multi-stage retrieval β€” combining cue extraction, graph traversal, vector similarity, and multi-hop association β€” to deliver human-like memory recall. Smriti uses the Leiden algorithm for automatic community detection, enabling cluster-aware retrieval that scales beyond thousands of memories.

Features

  • Graph-Based Memory β€” Engrams (memories) linked via Cues and Associations in a property graph
  • EcphoryRAG Retrieval β€” Multi-hop associative recall with cue extraction, vector similarity, and composite scoring
  • Leiden Community Detection β€” Automatic clustering of related memories using the Leiden algorithm with smart-cached resolution tuning, enabling cluster-aware scoring for efficient retrieval at scale
  • Multi-Backend Support β€” LadybugDB (embedded, zero-config), Neo4j (enterprise graph DB), or FalkorDB (Redis-based graph DB)
  • Multi-User Isolation β€” Per-file (LadybugDB), per-tenant property or per-database (Neo4j), or per-tenant property or per-graph (FalkorDB)
  • Automatic Consolidation β€” Exponential decay, pruning of weak memories, strengthening of frequently accessed ones, and periodic Leiden re-clustering
  • Flexible Backup β€” GitHub (system git) or S3 (AWS SDK) sync, plus noop for local-only
  • Lazy HNSW Indexing β€” Vector and FTS indexes created on-demand when dataset exceeds threshold
  • OpenAI-Compatible APIs β€” Works with any OpenAI-compatible LLM and embedding provider
  • 3 MCP Tools β€” smriti_store, smriti_recall, smriti_manage

Architecture

graph TD
    Client["MCP Client<br/>(Cursor / Claude / Windsurf / etc.)"]
    Client -->|stdio| Server

    subgraph Server["Smriti MCP Server"]
        direction TB

        subgraph Tools["MCP Tools"]
            Store["smriti_store"]
            Recall["smriti_recall"]
            Manage["smriti_manage"]
        end

        subgraph Engine["Memory Engine"]
            Encoding["Encoding<br/>LLM + Embed + Link"]
            Retrieval["Retrieval<br/>Cue Match + Vector + Multi-hop<br/>+ Cluster-Aware Scoring"]
            Consolidation["Consolidation<br/>Decay + Prune + Leiden Clustering"]
        end

        subgraph DB["Graph Database"]
            direction LR
            Graph["(Engram)──[:EncodedBy]──▢(Cue)<br/>(Engram)──[:AssociatedWith]──▢(Engram)<br/>(Cue)──[:CoOccurs]──▢(Cue)"]
            DBType["LadybugDB | Neo4j | FalkorDB"]
        end

        subgraph Backup["Backup Provider (optional)"]
            Git["GitHub (git)"]
            S3["S3 (AWS SDK)"]
            Noop["Noop"]
        end

        Store & Recall & Manage --> Engine
        Encoding & Retrieval & Consolidation --> DB
        DB --> Backup
    end

    LLM["LLM / Embedding API<br/>(OpenAI-compatible)"]
    Engine --> LLM

Recall Pipeline

The default recall mode performs multi-stage retrieval:

  1. Cue Extraction β€” LLM extracts entities and keywords from the query
  2. Cue-Based Graph Traversal β€” Follows EncodedBy edges to find engrams linked to matching cues
  3. Vector Similarity Search β€” Cosine similarity against all engram embeddings (HNSW index when available, fallback to brute-force)
  4. Multi-Hop Expansion β€” Follows AssociatedWith edges to discover related memories
  5. Cluster-Aware Composite Scoring β€” Blends vector similarity (40%), recency (20%), importance (20%), and decay (20%), with hop-depth penalty and soft-bounded cross-cluster penalty (0.5x for hop results outside the seed cluster)
  6. Access Strengthening β€” Recalled engrams get their access count and decay factor bumped (reinforcement)

Leiden Clustering

Smriti uses the Leiden algorithm β€” an improvement over Louvain that guarantees well-connected communities β€” to automatically detect clusters of related memories in the graph.

How it works:

  • Runs automatically during each consolidation cycle
  • Builds a weighted undirected graph from AssociatedWith edges between engrams
  • Auto-tunes the resolution parameter using community profiling on the first run
  • Uses a smart cache: the tuned resolution is reused across runs and only re-tuned when the graph grows by more than 10%
  • Assigns a cluster_id to each engram, stored persistently in the database
  • New engrams inherit the cluster_id of their strongest neighbor at encode time

How it improves retrieval:

  • The recall pipeline determines a seed cluster (most common cluster among direct-match results)
  • Multi-hop results that cross into a different cluster receive a 0.5x score penalty (soft-bounded: they are penalized, not dropped)
  • This keeps retrieval focused within the most relevant topic cluster while still allowing cross-topic discovery

Performance characteristics:

  • Gracefully skips on small graphs (< 3 nodes or 0 edges)
  • Clustering 60 nodes: ~40ms (first run with auto-tune), ~14ms (cached resolution)
  • Per-user: each Engine instance maintains its own independent cache

Consolidation Pipeline

Consolidation runs periodically (default: every 3600 seconds) and performs:

  1. Exponential Decay β€” Reduces decay_factor based on time since last access
  2. Weak Memory Pruning β€” Removes engrams below minimum decay threshold
  3. Frequency Strengthening β€” Boosts decay factor for frequently accessed memories
  4. Orphaned Cue Cleanup β€” Removes cues no longer linked to any engram
  5. Leiden Clustering β€” Re-clusters the memory graph (smart-cached, skips if graph hasn't changed significantly)
  6. Index Management β€” Creates HNSW vector and FTS indexes when engram count exceeds threshold (50)

Environment Variables

Core

VariableDefaultDescription
ACCESSING_USEROS usernameUser identifier (used for DB isolation)
STORAGE_LOCATION~/.smritiRoot storage directory (LadybugDB only)
DB_TYPEladybugDatabase backend: ladybug, neo4j, or falkordb

LLM

VariableDefaultDescription
LLM_BASE_URLhttps://api.openai.com/v1LLM API endpoint (OpenAI-compatible)
LLM_API_KEY(required)LLM API key
LLM_MODELgpt-4o-miniLLM model name

Embedding

VariableDefaultDescription
EMBEDDING_BASE_URLhttps://api.openai.com/v1Embedding API endpoint
EMBEDDING_API_KEY(falls back to LLM_API_KEY)Embedding API key
EMBEDDING_MODELtext-embedding-3-smallEmbedding model name
EMBEDDING_DIMS1536Embedding vector dimensions

Backup

VariableDefaultDescription
BACKUP_TYPEnonenone, github, or s3
BACKUP_SYNC_INTERVAL60Seconds between backup syncs (0 = disabled)
GIT_BASE_URL(empty)Git remote base URL (required if github)
S3_ENDPOINT(empty)S3 endpoint (for non-AWS providers)
S3_REGION(empty)S3 region (required if s3)
S3_ACCESS_KEY(empty)S3 access key (required if s3)
S3_SECRET_KEY(empty)S3 secret key (required if s3)

Neo4j (when DB_TYPE=neo4j)

VariableDefaultDescription
NEO4J_URI(required)Bolt URI (e.g. bolt://localhost:7687)
NEO4J_USERNAME(required)Neo4j username
NEO4J_PASSWORD(required)Neo4j password
NEO4J_DATABASEneo4jDatabase name (overridden by username in database isolation mode)
NEO4J_ISOLATIONtenanttenant (property-based, Community Edition) or database (per-DB, Enterprise Edition)

FalkorDB (when DB_TYPE=falkordb)

VariableDefaultDescription
FALKOR_ADDRlocalhost:6379FalkorDB Redis address
FALKOR_PASSWORD(empty)FalkorDB password (if auth enabled)
FALKOR_GRAPHsmritiGraph name (overridden by {user}_smriti in graph isolation mode)
FALKOR_ISOLATIONtenanttenant (property-based) or graph (per-graph isolation)

Consolidation

VariableDefaultDescription
CONSOLIDATION_INTERVAL3600Seconds between consolidation runs (0 = disabled)

MCP Tools

smriti_store

"Remember this" β€” Store a new memory. Content is automatically analyzed by the LLM, embedded, and woven into the memory graph. New engrams inherit the cluster_id of their most similar existing neighbor.

{
  "content": "Kubernetes uses etcd as its backing store for all cluster data",
  "importance": 0.8,
  "tags": "kubernetes,etcd,infrastructure",
  "source": "meeting-notes"
}
ParameterTypeRequiredDescription
contentstringyesMemory content
importancenumbernoPriority 0.0–1.0 (default: 0.5)
tagsstringnoComma-separated tags
sourcestringnoSource/origin label

smriti_recall

"What do I know about X?" β€” Retrieve memories using multi-stage EcphoryRAG retrieval with cluster-aware scoring.

{
  "query": "container orchestration tools",
  "limit": 5,
  "mode": "recall"
}
ParameterTypeRequiredDescription
querystringnoNatural language query (omit for list mode)
limitnumbernoMax results (default: 5)
modestringnorecall (deep multi-hop), search (fast vector-only), or list (browse)
memory_typestringnoFilter: episodic, semantic, procedural

Modes explained:

  • recall (default) β€” Full pipeline: cue extraction β†’ graph traversal β†’ vector search β†’ multi-hop β†’ cluster-aware composite scoring
  • search β€” Vector-only cosine similarity. Faster but shallower.
  • list β€” No search. Returns recent memories ordered by last access time.

smriti_manage

"Forget this / sync now" β€” Administrative operations.

{
  "action": "forget",
  "memory_id": "abc-123-def"
}
ParameterTypeRequiredDescription
actionstringyesforget (delete memory) or sync (push backup)
memory_idstringif forgetEngram ID to delete

Graph Schema

Smriti stores memories in a property graph with the following structure:

Node Tables:
  Engram   β€” id, content, summary, memory_type, importance, access_count,
              created_at, last_accessed_at, decay_factor, embedding, source,
              tags, cluster_id
  Cue      β€” id, name, cue_type, embedding

Relationship Tables:
  EncodedBy      β€” (Engram) β†’ (Cue)
  AssociatedWith β€” (Engram) β†’ (Engram)  [strength, relation_type, created_at]
  CoOccurs       β€” (Cue) β†’ (Cue)       [strength]

The cluster_id field on Engram nodes is managed by the Leiden algorithm. A value of -1 indicates the engram has not yet been assigned to a cluster (e.g., the graph is too small, or the engram has no associations).

Storage & Isolation

Smriti supports three database backends with different storage and isolation models:

LadybugDB (default)

Each user gets an isolated embedded database file:

~/.smriti/
└── {username}/
    └── memory.lbug     # LadybugDB property graph database

The STORAGE_LOCATION env var controls the root. The ACCESSING_USER env var selects which user's DB to open. Backup providers sync the user directory to remote storage.

Neo4j

Two isolation modes controlled by NEO4J_ISOLATION:

  • tenant (default) β€” All users share one database. Each node gets a user property and all queries filter by it. Works on Neo4j Community Edition.
  • database β€” Each user gets a separate Neo4j database. Requires Neo4j Enterprise Edition.

FalkorDB

Two isolation modes controlled by FALKOR_ISOLATION:

  • tenant (default) β€” All users share one graph. Each node gets a user property and all queries filter by it.
  • graph β€” Each user gets a separate graph (named {user}_smriti).

Schema migrations (e.g., adding cluster_id to existing databases) run automatically on startup.

Project Structure

smriti-mcp/
β”œβ”€β”€ main.go              # Entry point, server setup, signal handling
β”œβ”€β”€ config/              # Environment variable parsing
β”œβ”€β”€ llm/                 # OpenAI-compatible HTTP client (LLM + embeddings)
β”œβ”€β”€ db/                  # Database backends (LadybugDB, Neo4j, FalkorDB), schema, indexes, migrations
β”œβ”€β”€ memory/
β”‚   β”œβ”€β”€ engine.go        # Engine struct, consolidation loop
β”‚   β”œβ”€β”€ types.go         # Engram, Cue, Association, SearchResult structs
β”‚   β”œβ”€β”€ encoding.go      # Store pipeline: LLM extraction β†’ embed β†’ link β†’ cluster inherit
β”‚   β”œβ”€β”€ retrieval.go     # Recall pipeline: cue search β†’ vector β†’ multi-hop β†’ cluster scoring
β”‚   β”œβ”€β”€ search.go        # Search modes: list, vector-only, FTS, hybrid
β”‚   β”œβ”€β”€ consolidation.go # Decay, prune, strengthen, orphan cleanup
β”‚   └── leiden.go        # Leiden clustering: graph build, auto-tune, smart cache, batch write
β”œβ”€β”€ backup/              # Backup providers: noop, github (git), s3 (AWS SDK)
β”œβ”€β”€ tools/               # MCP tool definitions: store, recall, manage
└── testutil/            # Shared test helpers

Testing

# Run unit tests
CGO_ENABLED=1 go test ./...

# Verbose with all output
CGO_ENABLED=1 go test -v ./...

# Specific package
CGO_ENABLED=1 go test -v ./memory/...
CGO_ENABLED=1 go test -v ./tools/...

# Leiden clustering tests only
CGO_ENABLED=1 go test -v -run "TestRunLeiden|TestNeedsRetune|TestDetermineSeedCluster" ./memory/

E2E / Integration Tests

E2E tests require real LLM/embedding services and are gated behind the integration build tag:

# LadybugDB E2E (no external DB required)
CGO_ENABLED=1 go test -tags integration -v -run "TestE2E_LadybugDB" ./memory/

# Neo4j E2E (requires running Neo4j instance)
NEO4J_URI="bolt://localhost:7687" NEO4J_USERNAME="neo4j" NEO4J_PASSWORD="yourpass" \
  CGO_ENABLED=1 go test -tags integration -v -run "TestE2E_Neo4j" ./memory/

# FalkorDB E2E (requires running FalkorDB instance)
FALKOR_ADDR="localhost:6379" \
  CGO_ENABLED=1 go test -tags integration -v -run "TestE2E_FalkorDB" ./memory/

# All E2E tests
CGO_ENABLED=1 go test -tags integration -v -run "TestE2E_" ./memory/

All E2E tests require LLM_BASE_URL, LLM_API_KEY, LLM_MODEL, EMBEDDING_BASE_URL, EMBEDDING_MODEL, and EMBEDDING_API_KEY environment variables.

Contributing

Contributions are welcome! Please ensure:

  • All tests pass (CGO_ENABLED=1 go test ./...)
  • Code is properly formatted (go fmt ./...)
  • New code includes the SPDX license header

See CONTRIBUTORS.md for the contributor list.

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) from v1.0.7 onwards.

Versions prior to v1.0.7 are licensed under the Mozilla Public License 2.0.