The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Th Memory MCP listing page.
Status: v2.2.8 — a temporal, conflict-aware, hybrid-retrieval memory engine. 16 MCP tools, 25 passing test suites. Non-destructive schema migration from v1 (all v1 data preserved). New in v2.2: lifecycle states, temporal validity, conflict/dedup resolution with USER/SESSION/PROJECT/GLOBAL scope, hybrid FTS+vector retrieval (RRF), memory graph, get_context assembly, periodic consolidation, and link_memory / merge_memory / update_memory / import_memory / extract_memories. New in v2.2.3: scope-enforced retrieval, graph scope isolation, export/import round-trip, hardened import path (realpath), strict import validation, N+1 query elimination, cold/ablation benchmark, and MEMORY_RETRIEVAL_MODE switch. New in v2.2.7: synced secret filter between Claude hook and capture-core (6-pattern redact instead of line-drop), fixed err() to return isError:true per MCP spec, fixed backup rotation (backup only when migrations pending + prune to 5 files), and added hook error logging for SessionEnd distill. New in v2.2.8: fixed scope contamination 0.75→0 (critical) and conflict false 0→1 (GLOBAL leak), fixed graph hop1 0.52→1.0 via includeGraph, and rescaled benchmark profiles to 5K/20K/100K/500K/1M (pre-commit now quick 5K + normal 20K only).
better-sqlite3 native build and import.meta.url resolution) and the MCP SDK requires a modern runtime. CI tests on Node 20.x and 22.x.npm install, npm run build, npm test).MEMORY_DB_PATH is easiest to set with setx; on macOS/Linux use export in your shell profile.No external services, accounts, or API keys are required — everything lives in a single local SQLite file.
Fastest path: after cloning, run npm run quickstart — it builds, wires opencode.json, deploys the plugin, and sets MEMORY_DB_PATH for you in one command. The steps below show exactly what it does (use them if you prefer manual control).
Install via npm (alternative): install the server globally with npm install -g th-memory-mcp (or run it on demand with npx th-memory-mcp), then point the mcp command in opencode.json to th-memory-mcp instead of the built dist/index.js. The auto-capture plugin still comes from this repo (copy src/plugin/learning-capture.ts as described in step 4 below).
~/.config/opencode/opencode.json (replace <REPO> with the absolute clone path):src/plugin/learning-capture.ts → ~/.config/opencode/plugins/See ARCHITECTURE_v2.md for the full architecture spec.
LLMs don't remember you between sessions — every new chat starts blank. th-memory-mcp gives your AI a private, local long-term memory:
th-memory-mcp is a standard MCP server, so the 9 tools run anywhere MCP-over-stdio is supported. Full auto-capture (background prompt/tool/error capture + profile injection) needs a hook runtime — OpenCode has it built in; Claude Code gets it via our hooks bridge; Codex and Cursor use the tools manually (no hook runtime yet).
| Feature | OpenCode | Claude Code | Qwen Code | Codex | Cursor |
|---|---|---|---|---|---|
| 16 MCP tools | ✅ | ✅ | ✅ | ✅ | ✅ |
| Auto-capture (background) | ✅ plugin | ✅ hooks | ⚠️ adapter | ❌ manual | ❌ Rules |
| Profile injection | ✅ compaction | ✅ UserPromptSubmit | ❌ get_profile | ❌ get_profile | ❌ get_profile |
| Lexical fuzzy matching | ✅ (v2.0) | ✅ (v2.0) | ✅ (v2.0) | ✅ (v2.0) | ✅ (v2.0) |
UserPromptSubmit/PreCompact, rule-based distill on SessionEnd).All harnesses share one SQLite file via MEMORY_DB_PATH, so memory captured
anywhere is readable everywhere.
lesson records (situation → mistake → correction) for capturing corrections,
not just flat facts.get_context blends FTS5 keyword search with a
dependency-free lexical fuzzy matching (hashed n-gram similarity, 512-dim FNV-1a) (RRF fusion + scoring), then assembles
a token-budgeted context with optional memory-graph expansion.derived_from links).api_key=, password:, token) filtered before storage.better-sqlite3, no extra native
extensions; every tool degrades gracefully so the AI keeps working if the DB
is unavailable.| Command | Description |
|---|---|
npm run build | compile TypeScript → dist/ |
npm start | run the MCP server (stdio) from dist/index.js |
npm run distill | rule-based distill: interactions → profile sections + prune old data (env RETENTION_DAYS default 30) |
npm test | full suite: capture, distill, lifecycle, temporal, conflict, retrieval, graph, context, consolidation, benchmark, security, tools_v21, smoke, e2e_transport, retrieval_benchmark, recall_regression, scope, profile, entity_extraction, conflict_benchmark, security_regression, export_import_roundtrip |
node test/capture.test.mjs | test capture-core (filter secrets, dedupe, truncate, insert SQL) |
node test/distill.test.mjs | test distill-core (Thai tokenize, stats, profile sections, prune) |
node test/lifecycle.test.mjs | test lifecycle engine (states, decay, supersession) |
node test/temporal.test.mjs | test temporal model (validity, historical retrieval) |
node test/conflict.test.mjs | test conflict & dedup resolution |
node test/retrieval.test.mjs | test hybrid FTS+vector+RRF retrieval |
node test/graph.test.mjs | test memory graph (entities, relations, traversal) |
node test/context.test.mjs | test context assembly + token budgeting |
node test/consolidation.test.mjs | test clustering + derived memories |
node test/benchmark.test.mjs | latency benchmark over 300 memories |
node test/security.test.mjs | injection / safety checks |
node test/smoke.mjs | end-to-end smoke test over JSON-RPC (16 tools) |
| Tool | Description |
|---|---|
remember | upsert preference (category+key) — re-saving the same key increases confidence by 0.1 (cap 1.0) |
recall | search preferences + lessons (FTS5) + recent matching interactions. Use before starting a new task |
get_profile | user profile overview: profile sections + top preferences + 5 most recent lessons |
save_lesson | record a lesson learned from a correction (situation / mistake / correction) |
search_history | search past user prompts by keyword (200-char snippets per row) |
forget | delete one memory row (preference/lesson/interaction) by id (+type prevents cross-table id clash) |
memory_stats | memory statistics: counts by kind, DB size, oldest/newest interaction, profile sections |
get_recent_interactions | list recent raw interactions (filter by kind) — feedstock for Smart Distill |
export_memory | export memory to JSON under data/exports/ only (filename auto-sanitized) |
get_context | assemble relevant memories for the current task via hybrid retrieval (+ optional graph expansion) with token budgeting |
consolidate | cluster similar memories via embedding similarity; optionally create derived/consolidated memories linked via derived_from |
link_memory | create a typed relationship between two memories in the graph (supports/contradicts/supersedes/derived_from/related_to/caused_by/depends_on) |
merge_memory | merge a duplicate/near-duplicate into a canonical memory (source becomes superseded, provenance in metadata.merged_from) |
update_memory | update mutable fields in place, or create a superseding memory when content changes (set supersede=false to edit in place) |
import_memory | import memories from JSON (validates type, dedupes against existing, never overwrites blindly); dry-run by default, apply=true to insert |
extract_memories | scan recent captured interactions for memory-intent phrases and propose memory candidates (deterministic, no LLM); dry-run by default, apply=true to create (source=captured) |
mcp section from opencode.example.json into your opencode.json (global or project-level)
MEMORY_DB_PATH to the SAME database file for both the server and the plugin (the example uses <ABSOLUTE_PATH>/th-memory-mcp/data/memory.db), otherwise the auto-capture plugin writes to a different DB than the one the AI readsenvironment (see example) — covers the MCP server onlysetx MEMORY_DB_PATH "D:/path/to/memory.db" on Windows) — covers both server and plugin, since the plugin runs in the same process as OpenCodeopencode.json:
(example rule content is in AGENTS.memory.example.md — can be attached at project level instead)src/plugin/learning-capture.ts → ~/.config/opencode/plugins/learning-capture.tsThe AI accepts both Thai and English interchangeably — you can switch languages at any time without warning.
| Example command | Tool / effect |
|---|---|
| "Remember that..." | remember — save a preference |
| "Summarize memory" / "distill memory" | Smart Distill — AI reads get_recent_interactions, finds patterns, and saves insights itself |
| "How is my memory?" / "memory status" | memory_stats |
| "Export memory" / "backup memory" | export_memory |
| "Search history..." | search_history |
| "Forget..." | forget |
Long-term care: run npm run distill occasionally to summarize stats and prune interactions older than 30 days.
MEMORY_DB_PATH env vardata/ is git-ignored⚠️ Internal self-reported benchmark — not third-party benchmark
- internal small-N: 180 records/30 topics (B.retrieval: 30 topics × 5 relevant + 30 distractors = 180; full run also uses small-N storage/temporal/context subsets)
- single-machine self-run: single developer machine, single OS/Node/better-sqlite3 build — not cross-machine, not independently verified
- not third-party benchmark: self-reported, not independently verified; do not compare as if from an external evaluator
- Dataset and harness are in
repro/(commitable) andbenchmark/(full framework, seeTH_MEMORY_MCP_BENCHMARK_SPEC.mdandbenchmark/README.md).
Two modes
| Mode | Command | Data | Suites | Use case |
|---|---|---|---|---|
| Normal | npm run benchmark | 180 records / 30 topics | retrieval | quick check (<5s) |
| Heavy | npm run benchmark:heavy | 600 records / 100 topics + 2k scale | all (storage/retrieval/temporal/context/performance/scalability/cold/ablation) | stress / regression |
Reproduce:
Viewer — compare last 3 versions (table + charts)
The viewer loads benchmark/results/history.jsonl, groups by version, takes the latest run of the 3 most recent versions (e.g. 2.2.2 / 2.2.3 / 2.2.4) and shows a highlighted table (1 row per version) + bar charts for Recall@5 / MRR / NDCG@5 and Latency p95. Results are also saved per version in result/v*_benchmark_result.md and benchmark/results/versions/<ver>/.
Last internal run (v2.2.4, warm, same dataset — not third-party): Recall@5=0.92, Precision@5=0.92, MRR=1.00, NDCG@5=0.94 over 30 topics/180 records. See result/v2.2.4_benchmark_result.md and repro/README.md for details and caveats (internal small-N, single-machine self-run).
data/memory.db (WAL mode, better-sqlite3) is a plain, unencrypted SQLite file. 100% local & private means no cloud or network exfiltration — it does not mean encrypted at rest. Anyone with filesystem access (shared machine, backup, malware, stolen device) can read preferences/lessons/interactions in plaintext. For sensitive data, use OS-level full-disk encryption (BitLocker / FileVault / LUKS) or an opt-in SQLCipher build (requires native rebuild and key management). No SQLCipher/in-code encryption is applied by default and src/db/index.ts documents this explicitly.MIT © 2026 worakorn-prince
This project is licensed under the MIT License — see the LICENSE file for the full text.