memory-mcp-lite
# memory-mcp-lite
A small, opinionated memory server for AI coding assistants (Windsurf, Cursor, Claude Desktop — anything that speaks MCP).
It runs locally, stores durable knowledge on your disk, and tries very hard to stay out of your agent's way until you actually need it.
## Why this exists
Most AI clients already have some form of short-term memory. They remember the current conversation, maybe a few rules you've set, and that's about it. What they don't give you is a place to park things that should outlive the session — the architectural decision you made last week, the one weird build command for this repo, the gotcha that bit you three times in a row.
memory-mcp-lite is that place. It stores:
- technical decisions and the reasoning behind them,
- project architecture and conventions,
- commands, env notes, links, and gotchas,
- task state so you can resume work later,
- rolled-up summaries at the global / project / task level.
It deliberately does **not** store raw chat transcripts, replace your client's built-in rules, run embeddings or vector search, or need a server or cloud connection.
## How it's organised
Memory lives in a tree:
```
global
└── project
├── [project_summary]
└── task
├── [task_summary]
└── atomic // decision | fact | gotcha | command | link | convention
```
On top of the tree you can draw optional graph-lite edges between any two nodes — `related_to`, `depends_on`, `affects`, `caused_by`, `supersedes`, `references`. Handy when one decision obsoletes another, or a gotcha only matters in the context of a specific command.
The retrieval side is built to be cheap. The server's instructions push agents through three stages, from least to most expensive:
```
Stage 1 — summaries get_global_summary / get_project_summary / get_task_summary
│
▼ (only if summaries aren't enough)
Stage 2 — FTS5 light search search_memory_light → compact candidates
│
▼ (only for the 1–3 most relevant hits)
Stage 3 — full detail get_memory_detail
```
In practice this means your agent asks for a summary first, and only pays for the big payload when it has a specific reason to. If you skip this policy, you just end up dumping a bunch of stringly-typed JSON into context for no reason.
## Stack
- TypeScript, Node ≥ 20
- Drizzle ORM over libSQL (`@libsql/client`)
- SQLite FTS5 for lexical search
- A closure table for efficient subtree traversal
- The MCP TypeScript SDK (`@modelcontextprotocol/sdk`)
You can point it at a local file, a remote libSQL instance, or a Turso database — they all work the same.
## Install
The fast path is to let your MCP client fetch the package via `npx`.
**Windsurf** — `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"memory-mcp-lite": {
"command": "npx",
"args": ["memory-mcp-lite"]
}
}
}
```
**Claude Desktop** — `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"memory-mcp-lite": {
"command": "npx",
"args": ["memory-mcp-lite"]
}
}
}
```
Same pattern for any other MCP-compatible client; only the config file path changes.
### From source
```bash
npm install
npm run build # outputs dist/index.js; the schema is created on first run
```
Then point your client at the compiled bundle:
```json
{
"mcpServers": {
"memory-mcp-lite": {
"command": "node",
"args": ["/absolute/path/to/memory-mcp-lite/dist/index.js"]
}
}
}
```
If you want to iterate on the code without a build step, `tsx` works:
```json
{
"mcpServers": {
"memory-mcp-lite": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/memory-mcp-lite/apps/server/src/index.ts"]
}
}
}
```
### Where the data lives
By default: `~/.memory-mcp/memory.db`. Override it with any of:
| Env var | Purpose |
| ------------------------ | ------------------------------------------ |
| `MEMORY_DB_PATH` | Full path or `libsql://…` / `file:` URL. |
| `MEMORY_DATA_DIR` | Directory; the file is still `memory.db`. |
| `DATABASE_URL` | Accepted for backwards compatibility. |
| `MEMORY_DB_AUTH_TOKEN` | Bearer token for remote libSQL / Turso. |
So running against Turso is just:
```bash
MEMORY_DB_PATH="libsql://your-db.turso.io" \
MEMORY_DB_AUTH_TOKEN="eyJhbGci..." \
npm run dev
```
## Tools
Nine tools, all returning both a human-readable JSON block and a `structuredContent` object for programmatic clients. The server also ships a strict `description` and `annotations` payload for each tool so agents can pick the right one without guessing.
| Tool | Reach for it when… |
| ------------------------ | ------------------------------------------------------- |
| `get_global_summary` | recurring preferences, cross-project conventions |
| `get_project_summary` | architecture, key decisions, long-term project context |
| `get_task_summary` | resuming a specific piece of work |
| `search_memory_light` | summaries aren't enough; you want compact candidates |
| `get_memory_detail` | you've picked a candidate and need the full body |
| `remember_decision` | an architecture choice, trade-off, or rejected path |
| `remember_fact` | a command, env note, gotcha, link, or convention |
| `upsert_project_summary` | after an arch change or new convention worth recording |
| `upsert_task_summary` | after progress, blockers, or a plan change |
The retrieval discipline the server asks agents to follow:
1. summaries first,
2. light search only if summaries aren't enough,
3. full detail for at most 1–3 hits,
4. never dump every memory just because you can.
## Project identity
Projects are looked up in this priority order:
1. **Normalised git remote URL** — the most stable; survives directory moves and clones.
2. **Git root path** — used when there's no remote.
3. **Normalised workspace path** — the fallback.
This means the same project keeps the same memory even if different clients hand you slightly different paths, and moving a repo doesn't orphan everything you've stored.
## Development
```bash
npm run typecheck # TypeScript
npm run lint # oxlint
npm run test # vitest
npm run build # esbuild bundle to dist/
npm run dev # tsx watch
npm run db:studio # Drizzle Studio for poking at the DB
npm run db:generate # generate migration SQL when the schema changes
```
The schema is defined in `apps/server/src/db/schema.ts` and re-asserted on every startup by `ensureSchema()` (see `apps/server/src/db/migrate.ts`). That function is also where the FTS5 virtual table and its triggers get created — Drizzle doesn't manage virtual tables, so we do it ourselves with plain SQL. It's idempotent, so there's nothing to run manually.
## Roadmap
- Optional semantic fallback (local embeddings, feature-flagged).
- Node archival / cleanup for long-lived projects.
- Shared-team memory, once there's a good story for auth.
TDQS
Scored across 9 tools
Each tool targets a distinct scope or action: summaries by level (global/project/task), retrieval (search vs detail), and persistence (fact vs decision vs summary upsert). No two tools have ambiguous boundaries.
All tool names follow a consistent verb_noun pattern in snake_case (get_*, search_*, remember_*, upsert_*). The naming is predictable and clearly conveys each tool's purpose.
With 9 tools, the server covers reading (3), searching (1), retrieving (1), remembering (2), and updating (2) summaries. This is well-scoped for a lightweight memory system.
The tool set covers the core memory operations: read summaries at three levels, search, detail retrieval, and persist facts/decisions/summaries. Minor omission: no upsert_global_summary, but the domain is 'lite' and the gap is acceptable.