chat-memory
Local SQLite-backed MCP memory server that lets an AI persist, search, retrieve, update, hide, and review memories across sessions.
Save memories with title/content plus optional kind, tags, project, and stable
topic_key; duplicates/near-duplicates are detected and topic-key saves update in place.Search memories by keyword and meaning, with optional kind/project filters and inclusion of superseded memories.
Fetch a single memory in full, optionally with its earlier versions.
Edit a memory’s content/title/tags/kind or pin it; prior text is kept as a version.
Mark an old memory as superseded by a newer one while keeping it.
Soft-delete/forget a memory with an optional reason.
List recent memories, optionally by project.
Get a session-start profile of pinned memories, preferences, and most-used facts.
Review memories for stale, superseded, or repeated items to tidy up the store.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@chat-memory search my memory for my preferred code editor"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
keepsake
A small, local MCP server that gives AI chats a memory that lasts between sessions. It needs no cloud service, no GPU and no background service: just one SQLite file and Python.
Local only. Memories live in a single SQLite file on your machine (default
~/.local/share/keepsake/memory.db, or%LOCALAPPDATA%\keepsakeon Windows; readable only by you).Hybrid search. Keyword search (SQLite FTS5), plus optional meaning-based search with a small CPU model (
all-MiniLM-L6-v2, ONNX). "Where's my vegetable patch?" finds the allotment.No duplicates piling up. Exact duplicates are counted, not re-saved. Near-duplicates are shown to the AI so it can update the existing memory instead. Subjects that evolve use a stable
topic_keyand are updated in place, with every earlier version kept.Nothing silently lost. Out-of-date memories are superseded (hidden but kept), and
forgetis a soft delete.Secrets refused. Anything that looks like a token, key, password or credential-bearing URL is rejected before it reaches disk. Card expiry dates, PINs and security codes are removed automatically. Wrap other sensitive parts in
<private>…</private>to have them redacted.Exact matching. IPs, emails, card and gift card numbers, phone numbers, order and reference numbers, UUIDs and hashes are indexed, so
matchcan check a list of values from elsewhere and report exact hits only:203.0.113.45never matches203.0.113.46.
Renamed from chat-memory in 0.6. Existing databases, models, exports and
CHAT_MEMORY_* settings are still found automatically; KEEPSAKE_* settings take priority.
The AI you're chatting with decides what is worth remembering, guided by the server's
instructions (see INSTRUCTIONS in src/keepsake/server.py). The server stores, searches
and guards against duplicates. That split keeps it light enough for an ordinary laptop.
Install
Requires Python 3.11+ and works on Linux, macOS and Windows. (Windows: see the Windows section for the commands.)
git clone https://github.com/mav-rix/keepsake.git
cd keepsake
python3 -m venv .venv
.venv/bin/pip install -e .Optional: semantic search
.venv/bin/pip install -e '.[semantic]'
.venv/bin/keepsake download-model # ~90 MB, pinned revision, SHA-256 checkedPrivacy: the model runs entirely on your CPU. The only network access is
download-model fetching two files (an HTTPS download from huggingface.co; nothing is sent
except the request itself). Your memories and searches never leave the machine, and ONNX
Runtime's optional telemetry is switched off. On a locked-down network, download the two
files another way and set KEEPSAKE_MODEL_DIR (see below).
The model is stored in ~/.cache/keepsake/models/ (Windows: %LOCALAPPDATA%\keepsake\models) and loads on the first search, so a
session that never searches stays at about 75 MB of RAM (about 275 MB once loaded). Existing
memories are embedded automatically the first time they're needed, or run
keepsake reindex. Set KEEPSAKE_SEMANTIC=0 to switch it off.
Claude Code
claude mcp add --scope user keepsake -- "$PWD/.venv/bin/keepsake"GitHub Copilot (VS Code, Copilot CLI)
keepsake works with any model Copilot offers (OpenAI, Anthropic, Google...): MCP is
model-neutral. Add it to Copilot's MCP config, ~/.copilot/mcp-config.json
(Windows: %USERPROFILE%\.copilot\mcp-config.json), which VS Code's Copilot agent and the
Copilot CLI both read:
{
"mcpServers": {
"keepsake": {
"type": "stdio",
"command": "C:\\Users\\you\\keepsake\\.venv\\Scripts\\keepsake.exe",
"args": [],
"env": { "KEEPSAKE_MODE": "work" },
"tools": ["*"]
}
}
}(Older VS Code setups use MCP: Open User Configuration, with the same entry under a
"servers" key instead of "mcpServers".) Then, in Copilot Chat, check the tools list shows
keepsake.
Business/Enterprise seats: an organisation policy, MCP servers in Copilot, controls whether MCP servers can be used at all, and is off by default on some plans. Ask your admin.
Instructions: VS Code passes the server's instructions to the model. If a model still doesn't save or recall when it should, run
keepsake instructions --mode workand paste the output into your Copilot custom instructions.Models differ: how readily a model calls
rememberormatchvaries between models; asking directly ("check these against my cases", "log this case") always works.Data flow: recalled memories become part of the conversation, which Copilot sends to the model you've selected, under your organisation's Copilot agreement.
Claude Desktop and other MCP clients
Add to the client's MCP config (adjust the path):
{
"mcpServers": {
"keepsake": {
"command": "/path/to/keepsake/.venv/bin/keepsake"
}
}
}Set KEEPSAKE_DB to store the database somewhere else (see .env.example).
Windows
In PowerShell. Check py --version shows 3.11 or newer first (otherwise
winget install Python.Python.3.12).
git clone https://github.com/mav-rix/keepsake.git
cd keepsake
py -3 -m venv .venv
.venv\Scripts\pip install -e ".[semantic]" # or just -e . for keyword-only search
.venv\Scripts\keepsake download-model # optional, ~90 MB
claude mcp add --scope user keepsake -- "$PWD\.venv\Scripts\keepsake.exe"
# work mode: add -e KEEPSAKE_MODE=work after "--scope user keepsake"
claude mcp list # should show keepsake: ConnectedWhere things live: the database is
%LOCALAPPDATA%\keepsake\memory.dband the model is under%LOCALAPPDATA%\keepsake\models. They're protected by your Windows user account's folder permissions (the Linux/macOS "owner only" file mode doesn't apply).Claude Desktop: in its JSON config, double the backslashes:
"command": "C:\\Users\\you\\keepsake\\.venv\\Scripts\\keepsake.exe".Don't move the folder after registering it; Claude Code runs the server from there. If you do, run
claude mcp remove keepsake --scope userand add it again.Keep the database out of OneDrive/Dropbox. Syncing a live SQLite file can corrupt it. Use
export/importto copy memories between machines.
Behind a company network
pip: use your company's package mirror or proxy (
pip install --proxy ...orpip config set global.index-url ...).Model download blocked? Many company filters block huggingface.co. You don't need it: keyword search and
matchwork without the model. To get semantic search, use an approved internal copy; every file is still checked against its pinned SHA-256, whatever the source:# an Artifactory/Nexus "Hugging Face" repository (any HF-compatible endpoint; HF_ENDPOINT works too) keepsake download-model --mirror https://artifacts.example.com/artifactory/api/huggingfaceml/hf-remote # a plain file store: {name} becomes model.onnx / tokenizer.json ({path}: onnx/model.onnx) keepsake download-model --mirror "https://artifacts.example.com/generic/minilm/{name}" # files IT has given you keepsake download-model --from C:\path\to\folderIf the mirror needs a token, set
KEEPSAKE_MODEL_MIRROR_TOKEN(sent as a Bearer token; not passed on the command line so it stays out of shell history).KEEPSAKE_MODEL_MIRRORsets a default mirror. Files to request:onnx/model.onnxandtokenizer.jsonfromsentence-transformers/all-MiniLM-L6-v2at revision1110a243fdf4706b3f48f1d95db1a4f5529b4d41(Apache-2.0); their SHA-256 values are insrc/keepsake/embed.py.Managed Claude Code: your IT team may only allow approved MCP servers. If
claude mcp listdoesn't show it, that's likely why. Check that keeping work chat memories in a local file is allowed.
Related MCP server: simple-memory-mcp
Tools
Tool | What it does |
| Session-start briefing: pinned memories, preferences, most-used facts |
| Save a memory (handles topic keys, duplicates and near-duplicates) |
| Hybrid keyword + meaning search; the strongest hits come back in full, the rest as previews |
| One memory in full, optionally with its version history |
| Edit a memory (previous text kept), or pin it |
| Mark an old memory as replaced by a newer one |
| Soft-delete a memory |
| Bring back a forgotten or superseded memory |
| Exact lookup of identifiers or names (e.g. data received from another party) |
| Latest saved or updated memories |
| Near-duplicates and possible conflicts (with suggestions), plus stale, superseded, repeated and forgotten memories |
Your data stays private
The database is never inside the repository. .gitignore blocks database files and
.jsonl exports anyway, as a second line of defence.
Work mode: case work and exact matching
For investigation work (fraud, disputes, support cases), where you need to check data from other parties against what you've seen before, run the server in work mode:
claude mcp add --scope user keepsake -e KEEPSAKE_MODE=work -- "$PWD/.venv/bin/keepsake"Work mode changes only what the AI is asked to save: customer names, emails, phone numbers,
IP addresses, order IDs, transaction references, device IDs and card or gift card numbers are
saved in full (matching depends on exact values), one evolving record per case
(kind="case", topic_key="case/<id>"). In every mode:
Card expiry dates, PINs and security codes (CVV/CVC) are removed before saving and replaced with
[REDACTED]; the response says what was removed. Card and gift card numbers are kept. Labelled values are caught (exp 12/27,PIN: 4821,CVV 123), as is an unlabelledMM/YYstraight after a card number.Credentials are refused, as always.
How matching works
When a memory is saved, the identifiers in it are indexed in a normalised form, so different ways of writing the same value match:
Kind | Examples that match each other |
IP (v4/v6) |
|
| |
Card / gift card (12–19 digits) |
|
Phone (compared on the last 9 digits) |
|
Order / reference (letters and digits) |
|
Number (6–11 digits), UUID, hash (32+ hex) | exact, case-insensitive |
Anything else, such as a customer name, is matched as a whole phrase, ignoring case
(Jane Example matches; Jan doesn't match Jane). Superseded memories are included and
marked, because old records still count as evidence; forgotten ones are not.
In a chat, give the AI the values and it calls match. From the command line, any text or
CSV file works; every cell is checked:
keepsake match partner-report.csv # identifiers only
keepsake match partner-report.csv --text # also names and other plain cellsFOUND ref ORD-2026-118843 → [12] Fraud case FR-1042
not found ip 203.0.113.46
1 of 2 values found (exact matches only).Before using this for real case data, check it fits your organisation's rules: card numbers and customer details will sit in a local SQLite file (not encrypted by keepsake, so rely on disk encryption such as BitLocker or FileVault), and recalled memories are sent to the AI as part of the conversation. Keep work and personal memories in separate databases.
Backups, moving machines, tidying up
keepsake export ~/memory-backup.jsonl # readable only by you; warns if inside a git repo
keepsake import ~/memory-backup.jsonl --dry-run
keepsake import ~/memory-backup.jsonl
keepsake import ~/.claude/projects/<project>/memory # bring in Claude Code's memory files
keepsake import other-tool.db --dry-run # another tool's SQLite memory databaseMoving from another SQLite-based memory tool
Many memory tools keep their data in SQLite, each with its own tables. inspect shows the
tables, columns and row counts, and which columns it would use. It never prints row
contents, so its output is safe to share with whoever is helping you migrate:
keepsake inspect old-memory.db
# memories (1520 rows) ← suggested
# id INTEGER, content TEXT, tags TEXT, memory_type TEXT, created_at REAL, ...
# Suggested: --table memories --map content=content --map kind=memory_type ...
keepsake import old-memory.db --dry-run
keepsake import old-memory.db # add --kind case for case records
keepsake import old-memory.db --table kb --map content=note_text --map title=headingFields you can map: content (required), title (else the first line of the content),
kind, tags (JSON list or comma-separated), topic_key, project, created_at and
updated_at (ISO text, or Unix seconds/milliseconds), and deleted_at (rows with a value are
skipped). A type that isn't a keepsake kind is kept as a type:<name> tag; every imported
memory is tagged imported. Full-text (FTS5/FTS4) tables are read when they hold the
memories themselves; ones that only index another table, or hold no text, are skipped. The
old database is opened read-only and never changed; close
the old tool first (or import a copy). Records go through the same checks as any save, so
card expiry dates, PINs and security codes are removed and credentials are refused. Importing
again adds nothing new.
Exports are JSON Lines: a header line, then one memory per line with its version history
(embeddings are rebuilt on import). Import is all or nothing and safe to repeat: exact
duplicates are skipped, a topic_key that already exists keeps whichever version is newer
(the other goes into history), superseded links are kept, and every record passes the same
secret filter as remember.
keepsake review lists pairs that look like the same fact (cosine ≥ 0.80) or a changed fact
(≥ 0.60), each with a suggested supersede, plus stale, repeated, superseded and forgotten
memories. In a chat, ask the AI to tidy up your memory; it calls review and asks before
changing anything. keepsake purge permanently deletes memories forgotten more than 30
days ago (it asks first).
How duplicates are judged
Signal | Threshold | Effect |
Same normalised text | exact | counted, not re-saved |
Same | exact | updated in place, old version kept |
Word overlap (Jaccard) | ≥ 0.80 | not saved; AI asked to update instead ( |
Meaning (cosine) | ≥ 0.80 | not saved; |
Word overlap ≥ 0.25 or meaning ≥ 0.45 | saved, but listed as similar so the AI can |
The cosine thresholds were calibrated on sample pairs with all-MiniLM-L6-v2: paraphrased
duplicates scored 0.82–0.92, changed facts 0.61–0.74, related topics 0.21–0.38, unrelated
≤ 0.11. Embeddings use the memory's content only; titles count through keyword search.
Search scores mix 0.7 × meaning + 0.3 × keyword (BM25, normalised), with a small boost for pinned memories. On a 2021 laptop CPU, 10,000 memories search in about 45 ms.
Command line
keepsake # run the MCP server (stdio)
keepsake download-model # fetch the embedding model (--mirror URL / --from FOLDER)
keepsake reindex # embed memories that lack embeddings
keepsake stats # counts and search mode
keepsake usage # estimated tokens used by memory tool calls
keepsake review # what needs tidying
keepsake match FILE # exact hits for identifiers in a text/CSV file (--text for names)
keepsake export [FILE] # back up as JSON Lines (stdout if no file)
keepsake import PATH # merge an export, a Claude Code memory folder or a SQLite db (--dry-run)
keepsake inspect DB # another tool's tables/columns and the suggested mapping (no content)
keepsake instructions # the AI instructions, for clients' custom-instructions settings
keepsake purge # permanently delete long-forgotten memories (--days, --yes)Token use
Every tool call means the AI re-reads the whole conversation, so the number of round trips
matters far more than the size of each answer. recall therefore returns the full text of its
best hits (up to 3, within 8,000 characters), and the instructions ask for a single recall
only when past context could matter, not a get_profile at every session start. In tests
with Claude Code this cut tokens on memory-backed questions by roughly 45%, and added
nothing measurable to questions that need no memory (Claude Code loads MCP tool definitions
on demand). keepsake usage shows what the tools have returned; it logs only tool names
and sizes, never content.
Development
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest
git config core.hooksPath .githooks # runs gitleaks before each commitThe pre-commit hook needs gitleaks on your PATH.
Roadmap
Keyword search, topic keys, duplicate and secret guards(0.1)Optional meaning-based search, mixed with keyword scores(0.2)Export and import, near-duplicate and conflict review, restore(0.3)Usage log, fewer round trips(0.4)Work mode, card-detail redaction, exact identifier matching(0.5)
Credits
The design borrows ideas from these projects (no code copied):
Engram (MIT): topic keys, normalized-hash duplicate counting,
<private>redaction, version historymcp-memory-service (Apache-2.0): superseding instead of deleting, similarity bands for duplicates vs. related memories, the 0.7 / 0.3 hybrid weighting, the choice of
all-MiniLM-L6-v2The reference memory server (MIT): one fact per observation, a prompt that guides what to remember
License
MIT. The optional embedding model, all-MiniLM-L6-v2, is Apache-2.0 and is downloaded separately, not bundled.
Available Tools
9 toolsforgetC
Hide a memory that should not have been saved or that the user asked to remove.
Inputs: memory_id (required), reason. Returns: object
get_memoryB
Get one memory in full, optionally with its earlier versions.
Inputs: memory_id (required), with_history. Returns: object
get_profileB
Session-start briefing: pinned memories, preferences and the most-used facts.
Inputs: project. Returns: object
list_recentC
Most recently saved or updated memories.
Inputs: limit, project. Returns: result
recallA
Search memories by keywords and meaning (when the embedding model is installed). Returns short previews ranked by score; use get_memory for full text.
Inputs: query (required), kind, limit, project, +1 more. Returns: result
rememberB
Save a memory for future sessions. Returns action: created, updated (same topic_key), duplicate, unchanged, or needs_review (a near-identical memory exists; nothing saved).
Inputs: title (required), content (required), kind, tags, +3 more. Returns: object
reviewA
Memories worth tidying: unused for stale_days, superseded, or saved repeatedly. Use when the user asks to clean up memory.
Inputs: stale_days. Returns: object
supersedeA
Mark old_id as replaced by new_id. The old memory is hidden from recall but kept.
Inputs: new_id (required), old_id (required). Returns: object
update_memoryB
Change an existing memory. The previous text is kept as a version. pinned=true puts it in every session's profile.
Inputs: memory_id (required), kind, tags, title, +2 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool maps to a fairly distinct memory lifecycle operation, and descriptions clarify the subtle cases (remember's duplicate/needs_review outcomes, supersede vs forget both hiding but for different reasons). The main soft spot is the boundary between remember (which can update on same topic_key) and update_memory, plus forget vs supersede, which an agent could occasionally misselect.
Half the tools follow verb_noun (get_memory, update_memory, list_recent, get_profile) while the other half are bare verbs (remember, recall, supersede, forget, review). Each name is readable and reasonably self-describing, but the two conventions are mixed without a clear rule for which applies.
Nine tools is well within the sweet spot and every one earns its place in a memory lifecycle (save, search, read, edit, supersede, forget, list, profile, review). No filler tools and no obviously merged concerns.
The surface covers full lifecycle: create (remember), read (recall/get_memory), update (update_memory/supersede), soft-delete (forget), plus listing, profile briefing, and cleanup review. Minor gaps remain: no hard/permanent delete or purge, and no batch operations, but core workflows are fully served.
Maintenance
Related MCP Connectors
Persistent memory for AI agents. Search, store, and recall across sessions.
Long-term memory for AI assistants. Isolated per-user storage, recall across conversations.
Persistent memory for AI assistants: store, search, and connect knowledge across conversations.
Private memory for AI agents: save and recall facts across Claude Code, Codex, Cursor and ChatGPT.
Related MCP Servers
- -licenseNot gradedqualityNot gradedmaintenanceProvides persistent local memory functionality for AI assistants, enabling them to store, retrieve, and search contextual information across conversations with SQLite-based full-text search. All data stays private on your machine while dramatically improving context retention and personalized assistance.3-
- AlicenseNot gradedqualityDmaintenanceProvides AI assistants with persistent memory across sessions using local SQLite and keyword search, allowing storage and retrieval of user preferences, project context, and decisions.27 npm9MIT

fimemoryofficial
FlicenseNot gradedqualityCmaintenanceEnables AI assistants to read and write a local, encrypted-by-default memory store, so they can share persistent notes and context across sessions and models on your machine.48 npm-- AlicenseNot gradedqualityBmaintenanceEnables AI agents to save, recall, and forget persistent memories stored entirely on the local machine in SQLite, using hybrid semantic and keyword search with on-device embeddings, plus spaces and profile context injection.MIT