memory-mcp
README.md
# memory-mcp
Local-first MCP server that gives Claude (or any MCP client) long-term memory over past dev sessions. Session metadata is embedded and stored in Postgres/pgvector; semantic search returns cheap metadata hits, and only the sessions you judge relevant get their full content resolved and loaded.
Runs entirely on your machine: local Postgres via Docker, local embedding model via `@huggingface/transformers`. No external API calls, no data leaves the box.
## How it works
Three MCP tools, mirrored 1:1 by `MemoryStore` (`src/core/memoryStore.ts`):
| Tool | Purpose |
|---|---|
| `index_session` | Embed a session's structured metadata (summary, topics, decisions, files touched) and upsert it, keyed by `sessionId`. The full session content itself is never embedded or stored here — only a `sessionPath` pointing to it. |
| `search_sessions` | Vector-similarity search over indexed sessions. Returns metadata only (no file contents), unranked beyond cosine distance — the caller decides what's actually relevant. |
| `get_session_files` | Resolve chosen session ids to their `sessionPath` and `filesTouched`, for loading into context. |
The intended flow: `search_sessions` first (cheap, metadata-only) → the agent decides which hits matter → `get_session_files` only for those → read the resolved paths last. This keeps token spend proportional to relevance instead of reading every candidate up front.
This project pairs naturally with a "write a session summary to a file" workflow (e.g. a `/session-summary` command that writes to an Obsidian vault) — `index_session` is called after the summary file is written, with `sessionPath` pointing at it. Sample commands for exactly this are included — see [Commands](#commands) below.
### Architecture
- `src/server.ts` — registers the three MCP tools against a `MemoryStore`, transport-agnostic.
- `src/core/memoryStore.ts` — execution layer: SQL + embedding calls, independent of transport.
- `src/transport/stdio.ts` — stdio transport (the only one wired up today).
- `src/embeddings/` — embedding provider abstraction; `workerEmbeddingProvider.ts` runs the model (`@huggingface/transformers`, default `Xenova/bge-small-en-v1.5`) in a worker thread so embedding doesn't block the main event loop.
- `src/db/` — Postgres client, connection pooling, and `schema.sql` (pgvector table + HNSW index).
- `src/scripts/` — one-off setup scripts (`downloadModel.ts`, `registerMcp.ts`).
## Setup
Prerequisites: Node >= 20, pnpm, Docker.
```bash
pnpm setup
```
This runs, in order: `pnpm install` → start Postgres via Docker (`db:up`) → download the embedding model locally → `tsc` build → register the server with the Claude CLI (`claude mcp add`, prompts for global vs. project scope).
Each step can also be run individually:
```bash
pnpm install
pnpm db:up # docker compose up -d --wait
pnpm model:download # caches the embedding model into .models/
pnpm build # tsc + copy schema.sql into dist/
pnpm mcp:register # claude mcp add memory-mcp -> node dist/index.js
```
`mcp:register` detects an existing registration and offers to reconfigure it; pass `--global` or `--local` to skip the interactive prompt.
`pnpm setup` then asks a final question — "Register commands?" — see below.
## Commands
`templates/commands/` ships two sample Claude Code slash commands, generalized for any user:
| Command | Does |
|---|---|
| `/session-summary` | Writes a structured note about the current session to a vault directory, then calls `index_session` so it's semantically searchable later. |
| `/session-context <query>` | Answers a question from prior sessions: calls `search_sessions` for cheap metadata hits, resolves only the relevant ones via `get_session_files`, then reads those files — falling back to a plain directory grep if the MCP tools aren't available. |
Both templates reference the vault directory as `__VAULT_DIR__` — wherever you want session notes written (an Obsidian vault, a plain folder, anything readable/writable).
Run `pnpm commands:register` (or answer "yes" at the end of `pnpm setup`) to install them:
1. Prompts for the vault directory (defaults to `~/Documents/obsidian/Claude`) and substitutes it for every `__VAULT_DIR__` in the templates.
2. Writes the rendered files to `$CLAUDE_CONFIG_DIR/commands/` if `CLAUDE_CONFIG_DIR` is set, otherwise `~/.claude/commands/` — the same resolution order the `claude` CLI itself uses (see `mcp:register` above).
3. Asks before overwriting a command file that already exists at the destination.
Re-run `pnpm commands:register` any time to update an existing install or point it at a different vault directory.
### Migrating pre-existing sessions (opt-in, manual)
If you already have a vault of session notes written before memory-mcp existed (or before `/session-summary` started calling `index_session`), `templates/commands/session-migrate.md` backfills them. It's deliberately **not** installed by `commands:register` or `pnpm setup` — it's a one-off maintenance operation, not part of the standard install:
```bash
cp templates/commands/session-migrate.md "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/commands/"
```
Then run `/session-migrate` and give it the vault directory when asked. It reads each note, extracts the structured fields (date, branch, tags, decisions, files touched), writes a proper summary, and calls `index_session` — capped at 100 notes per run, since indexing requires reading and summarizing every note in full and the token cost stops being trivial past that. It tracks what it's already indexed in `<vault>/.memory-mcp-migrated.json`, so re-running only picks up notes added since the last pass.
## Configuration
Copy `.env.example` to `.env` to override defaults:
| Variable | Default | Notes |
|---|---|---|
| `DATABASE_URL` | `postgres://memory:memory@localhost:5433/memory` | Matches `docker-compose.yml`'s exposed port (`5433` on the host, to avoid clashing with a local Postgres on `5432`). |
| `EMBEDDING_DIM` | `384` | Must match the embedding model's output dimension and the `VECTOR(384)` column in `schema.sql` — change both together. |
| `EMBEDDING_MODEL` | `Xenova/bge-small-en-v1.5` | Any `@huggingface/transformers`-compatible model. |
| `EMBEDDING_DTYPE` | `q8` | `q8` = ~4x smaller weights, faster CPU inference, small accuracy tradeoff. Use `fp32` for full precision. |
| `MODEL_CACHE_DIR` | `.models/` | Populated ahead of time by `pnpm model:download`; otherwise fetched lazily on first use. |
The schema (`src/db/schema.sql`) is applied automatically on server startup (`ensureSchema`) — no separate migration step.
## Development
```bash
pnpm dev # tsx watch src/index.ts
pnpm typecheck # tsc --noEmit
pnpm db:logs # docker compose logs -f postgres
pnpm db:down # docker compose down
```
The server speaks MCP over stdio (`src/index.ts` → `startStdioTransport`), so `pnpm dev` alone won't do much interactively — point an MCP client (e.g. Claude Code via `mcp:register`) at it, or drive `MemoryStore` directly for testing.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues