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
```bash
uv sync
```
## Usage
Run as an MCP server (stdio transport):
```bash
uv run python server.py
```
Or via the wrapper script:
```bash
./run.sh
```
### CLI
This package also installs a `memory` CLI:
```bash
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:
```bash
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:
```bash
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:
```bash
uv run memory ingest-logs codex --llm-max-tokens 4096
```
To also compute embeddings through a local embedding server:
```bash
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:
```bash
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`):
```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:
```bash
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
```bash
uv run pytest
```
## License
[WTFPL](LICENSE)
TDQS
Scored across 5 tools
Each tool has a clearly distinct purpose: recall, store, update, session management, and admin operations. There is no overlap between their scopes, and the descriptions make the boundaries explicit.
Tool names follow a consistent verb_noun pattern (e.g., store_memory, update_memory, manage_session). The only minor deviation is memory_admin which uses a noun_verb pattern, but it remains clear and readable.
With 5 tools, the set is well-scoped for a memory server. Each tool covers an essential operation—CRUD for memories, session management, and admin—without being excessively minimal or bloated.
The tool set provides complete lifecycle coverage for memories (store, retrieve, update, delete via admin) plus session management and administrative maintenance. No obvious gaps are present for the stated purpose of agent memory management.