Skip to main content
Glama
hz1ulqu01gmnZH4

universal-memory-mcp

universal-memory-mcp

Persistent memory MCP server for single and multi-agent LLM systems. Gives AI agents long-term memory backed by SQLite with hybrid keyword + semantic search.

Features

  • Memory types: episodic (events/logs), semantic (facts/knowledge), procedural (how-to/workflows)

  • Hybrid search: FTS5 keyword search + cosine similarity over embeddings, with configurable weights

  • Knowledge graph: directed links between memories (caused_by, related_to, contradicts, supports, follows) with BFS traversal

  • Session checkpoints: save/restore agent state across conversations

  • Multi-agent support: scope memories by agent_id, session_id, or share globally

  • Optimistic locking: safe concurrent updates with version conflict detection

  • Pluggable embeddings: HuggingFace transformers (in-process) or llama-server (external HTTP)

Install

uv sync

Usage

Run as an MCP server (stdio transport):

uv run python server.py

Or via the wrapper script:

./run.sh

CLI

This package also installs a memory CLI:

uv run memory doctor

Ingest Claude/Codex logs

The log ingester reads local Claude/Codex JSONL/text logs, redacts common secrets, extracts durable memories, and stores only the extracted memories with log provenance metadata. Raw logs are not stored.

Dry-run first:

uv run memory ingest-logs codex --dry-run --root ~/.codex/sessions --extractor heuristic
uv run memory ingest-logs claude --dry-run --root ~/.claude/projects --extractor heuristic

Use a local llama-server OpenAI-compatible chat endpoint for extraction:

llama-server --model /path/to/chat-model.gguf --port 8080
uv run memory ingest-logs all --llm-url http://localhost:8080 --llm-model local

By default the CLI does not send max_tokens to the chat endpoint, which avoids truncating extraction responses from thinking models. Set an explicit cap only when you need one:

uv run memory ingest-logs codex --llm-max-tokens 4096

To also compute embeddings through a local embedding server:

llama-server --model embeddinggemma-300m-Q4_0.gguf --port 8787 --embedding --ctx-size 512
MEMORY_ENABLE_EMBEDDINGS=true \
MEMORY_EMBEDDING_BACKEND=llama-server \
MEMORY_EMBEDDING_DIMENSION=768 \
MEMORY_LLAMA_SERVER_URL=http://localhost:8787 \
uv run memory ingest-logs codex --llm-url http://localhost:8080

Incremental checkpoints are stored in SQLite, so later runs only consume new events. For polling:

uv run memory watch-logs all --interval 30 --llm-url http://localhost:8080

Claude Code config

Add to your MCP settings (~/.claude/settings.json or project .mcp.json):

{
  "mcpServers": {
    "memory": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/universal-memory-mcp", "python", "server.py"]
    }
  }
}

Configuration

All settings via environment variables (prefix MEMORY_):

Variable

Default

Description

MEMORY_DATABASE_PATH

./memory.db

SQLite database path

MEMORY_EMBEDDING_BACKEND

transformers

transformers or llama-server

MEMORY_EMBEDDING_MODEL

sentence-transformers/all-MiniLM-L6-v2

HuggingFace model name

MEMORY_EMBEDDING_DIMENSION

384

Embedding vector size

MEMORY_LLAMA_SERVER_URL

http://localhost:8787

llama-server endpoint

MEMORY_ENABLE_EMBEDDINGS

true

Set false for keyword-only search

MEMORY_KEYWORD_WEIGHT

0.4

Hybrid search keyword weight

MEMORY_SEMANTIC_WEIGHT

0.6

Hybrid search semantic weight

MEMORY_RECALL_MIN_RELEVANCE

0.25

Minimum cosine similarity for semantic-channel recall results (0 = disabled). Keyword matches always survive.

MEMORY_RECALL_SNIPPET_CHARS

600

Truncate recalled contents to this many chars (0 = disabled). Full text via exact fetch or full_content=true.

Using llama-server backend

For lower memory usage with a GGUF model:

llama-server --model embeddinggemma-300m-Q4_0.gguf --port 8787 --embedding --ctx-size 512
MEMORY_EMBEDDING_BACKEND=llama-server MEMORY_EMBEDDING_DIMENSION=768 uv run python server.py

MCP Tools

The surface is deliberately small — five tools, tiered by call frequency:

Tool

Description

recall_memories

One retrieval tool, three selectors: query (hybrid/keyword/semantic search), memory_id (exact fetch, full content), or entity (memories mentioning src/foo.py, an identifier, etc.). expand_links=N attaches graph neighbors to each result. Long contents are returned as snippets unless full_content=true.

store_memory

Store a memory with type, agent/session scope, importance; optional links creates graph edges to existing memories in the same call

update_memory

Update with optimistic locking (expected_version)

manage_session

action: create | checkpoint | restore — save/restore agent state across conversations

memory_admin

action: stats | dream_status | run_dream_jobs | extract_entities | check_contradictions | link | delete — statistics, background-job maintenance, manual graph links, and deletion (destructive ops gated behind one tool for per-tool permission allowlists)

Errors are raised as MCP tool errors (isError: true), never returned as data.

Breaking changes in 0.2.0

get_memory, delete_memory, link_memories, get_linked_memories, create_session, checkpoint_session, restore_session, get_stats, extract_entities, get_entity_neighbors, check_contradictions, dream_status, and run_dream_jobs were folded into the five tools above. recall_memories now returns {mode, count, memories} instead of a bare list, truncates long contents by default, and applies a semantic relevance cutoff (MEMORY_RECALL_MIN_RELEVANCE).

Tests

uv run pytest

License

WTFPL