Content
# Tool List
A hierarchical long-term memory MCP server for LLM Agents (e.g., Claude Code). This server precipitates conversations into three layers of memory that can be semantically searched, allowing the agent to recall previous conversations and provide context when asked "where did we leave off?".
- **Local Priority**: The embedding uses local `@xenova/transformers` (multilingual MiniLM, 384 dimensions), which does not rely on any cloud API.
- **Hierarchical Search**: When a fragment (L1) is hit, it automatically fills in the daily summary (L2) and topic context (L3).
- **Elegant Degradation**: If the model fails to load, it falls back to keyword (Jaccard) search and explicitly warns on stderr——it won't pretend to be normal.
## Memory Hierarchy
```
memory/ # Storage root, relative to the server process's working directory (CWD)
├── raw/<date>/turns.jsonl # Raw conversation, word-for-word, full retention
├── fragments/<date>/ # L1 task → result fragments (.md + .embedding vector)
├── daily/<date>.md # L2 daily summary
└── topics/<topic>.md # L3 cross-day topic index
```
Write order: `store_turn` (round by round) → `create_fragment` (package several rounds into a fragment, automatically calculate embedding) → `create_daily_summary` / `upsert_topic` (summary).
> **Important: The storage root is relative to CWD** (`path.resolve("memory/...")`). The server process writes memory to the `memory/` directory of the directory that the host (e.g., Claude Code) uses as its working directory.
## Installation & Build
Download and install to a certain path
```bash
npm install
npm run build # tsc → dist/
```
Requires Node ≥ 20 (developed and verified with 22).
## Register in Claude Code
`.mcp.json` in the project root:
```json
{
"mcpServers": {
"memory": {
"command": "node",
"args": ["/absolute/path/memory-mcp-server/dist/index.js"]
}
}
}
```
Or directly provide the installed path to Claude Code, let it intelligently register, and then restart Claude Code.
## ⚠ Embedding Model: Required for First Run, Manual Placement Required for Offline Environment
Semantic search uses `Xenova/paraphrase-multilingual-MiniLM-L12-v2` (quantized, about 118MB, multilingual, correct Chinese retrieval sorting) by default. **Online**, `transformers.js` will automatically download it to:
```
node_modules/@xenova/transformers/.cache/Xenova/paraphrase-multilingual-MiniLM-L12-v2/
```
> To switch models: set the environment variable `MEMORY_EMBED_MODEL=<repo/model>` to override the default (see `src/embedding/provider.ts`'s `MODEL_ID`). If you switch to a non-384-dimensional model, be sure to refill historical fragments (see below); vectors of different dimensions/models cannot be mixed.
> Early versions used `Xenova/all-MiniLM-L6-v2` (English model, about 23MB)——it has been discarded because it has incorrect sorting for Chinese semantics (cosine of irrelevant chats can suppress correct answers).
**If network access to `huggingface.co` is blocked** (common in domestic/isolated networks), automatic download will fail with `TypeError: fetch failed`, and the server will fall back to keyword search (recall quality significantly decreases). In this case, **manually place the model** using a mirror:
```bash
BASE="https://hf-mirror.com/Xenova/paraphrase-multilingual-MiniLM-L12-v2/resolve/main"
DEST="node_modules/@xenova/transformers/.cache/Xenova/paraphrase-multilingual-MiniLM-L12-v2"
mkdir -p "$DEST/onnx"
curl -sL "$BASE/config.json" -o "$DEST/config.json"
curl -sL "$BASE/tokenizer.json" -o "$DEST/tokenizer.json"
curl -sL "$BASE/tokenizer_config.json" -o "$DEST/tokenizer_config.json"
curl -sL "$BASE/onnx/model_quantized.onnx" -o "$DEST/onnx/model_quantized.onnx"
```
Verify offline loading:
```bash
node --input-type=module -e '
import { pipeline, env } from "@xenova/transformers";
env.allowRemoteModels = false; // Force only use local cache
const ex = await pipeline("feature-extraction","Xenova/paraphrase-multilingual-MiniLM-L12-v2",{quantized:true});
const r = await ex("Hello",{pooling:"mean",normalize:true});
console.log("OK dim=", r.data.length); // Expect 384
'
After placing it, **restart the MCP server** (常驻进程, not hot update; i.e., restart the client in Claude Code).
### How to Determine the Current Running Mode
Check the server's **stderr**:
- If you see `[embedding] ⚠ Running in degraded mode` → the model didn't load, using keyword search, fix it according to the above steps.
- If you don't see this line, `memory_search` returns a score generally above **0.2+** (and synonymous rewriting can hit) → semantic mode is normal.
- If still in degraded mode, `create_fragment`'s return will have `embedding_mode` as `"fallback"`.
## Refill Historical Fragments
If the server ran in degraded mode for a while, fragments from that period don't have vectors (or are empty). After fixing the model, refill them in one go:
```bash
cd <project root where memory is stored> # Must be, storage root is relative to CWD
node <absolute path>/backfill_embeddings.mjs
```
The script will traverse all fragments, recalculate and overwrite `.embedding` with the actual model, and print the number of successes/skipped at the end; if still in degraded mode, it will exit with a non-zero code and a prompt.
## MCP Tool List
| Tool | Function |
|---|---|
| `memory_store_turn` | Append a round of conversation to raw (full original text) |
| `memory_create_fragment` | Package several rounds into L1 fragments, automatically calculate embedding |
| `memory_create_daily_summary` | Write L2 daily summary |
| `memory_upsert_topic` | Create/update L3 cross-day topic index |
| `memory_search` | Semantic search → hit L1 and fill in L2/L3 context |
| `memory_get_fragment` / `memory_get_daily` / `memory_get_topic` | Read complete content by ID |
| `memory_list_dates` | List all recorded dates |
## Division of Labor with Host's Built-in Memory (Avoid Double Writing)
Many agent hosts (e.g., Claude Code) have their own lightweight memory that always loads into the context. This MCP has **different responsibilities and should not be duplicated**:
- **Host's built-in memory** = distilled permanent rules/preferences, need to be **in the context of each session**, no need to search. Few and refined, one line per item.
- **This MCP** = retrievable **plot archives**: complete conversations, task fragments, daily/topic context. **Search as needed**, not resident.
A rule of thumb: when a piece of information is worth remembering, ask yourself: *Does it need to be in every session, or only when I look it up?* The former goes into the host's memory (one line), the latter into this MCP (with evidence fragments). The host's one line can reference the MCP's topic name for drill-down, but not copy the body.
## Repository Hygiene
`memory/` contains **raw conversation records word-for-word**. If you put this server's memory library in a git project, remember to ignore it in the project's `.gitignore`, and don't commit conversation originals and vectors into the version library:
```gitignore
/memory/
```
## Known Trade-offs
- MiniLM's similarity is generally low, **0.2–0.35 is a reliable hit**, don't set thresholds based on intuition (e.g., 0.8).
- Retrieval quality heavily depends on the **quality of `task_desc`/`result_desc`/fragment condensation provided by the writer**——tools are responsible for structure and recall, but condensation quality is up to the user.
- embedding text = `task_desc + result_desc + turns_text` (query mostly targets conclusions, including them makes recall more accurate).
## Development
```bash
npm run dev # tsx runs src/index.ts directly
npm run check # tsc --noEmit type check
npm run watch # file listener (e.g., enable watcher)
```