aleth-mcp
The MCP face for the memory engine: the ten memory verbs, remember,
recall, show, search, retract, forget, supersede, connect,
status, nodes: exposed as MCP tools, as a thin shell over the
aleth-client wire core.
Three of those correct a belief and they are NOT interchangeable:
retract says it was wrong, supersede says it was true and something
newer replaced it, forget destroys it.
Any MCP-speaking agent gets persistent, associative, erasable memory with one config entry:
{
"mcpServers": {
"memory": { "command": "aleth-mcp" }
}
}
What it is (and is not)
- Thin. Every tool is one client call; results are the frozen wire models as plain dicts. No state of its own, no extra verbs.
- Lazy. The server starts and lists tools even before the engine is up;
the first call connects. Connection problems come back in-band
(
{"error": ...}), so the agent sees them instead of a dead server. - Engine-agnostic on the wire. It speaks the frozen memory wire, so it
works against the standalone memory server and the full daemon alike --
set
MEMORY_DISCOVERY_DIRif the engine's discovery file lives somewhere non-default. forgetreally forgets. Permanent erasure (graph + storage + vacuum), distinct fromretract(kept but flagged). That split is the product's thesis, not a convenience.
Install / run
pip install aleth-mcp
aleth-mcp # stdio MCP server (bare invocation -- how hosts launch it)
Register with an AI host
The install subcommand writes the server entry into a host's own config file
(merge-only, backed up, idempotent). The bare aleth-mcp invocation is
unchanged, only the install / uninstall / hosts subcommands trigger the
writer.
aleth-mcp hosts # list hosts + which are detected here
aleth-mcp install claude-code # add "memory" to ./.mcp.json
aleth-mcp install --all # install into every detected host
aleth-mcp install cursor --project --instructions # config + rules file
aleth-mcp install codex --dry-run # show the diff, write nothing
aleth-mcp uninstall claude-code # remove exactly our entry
Flags: --all (detected hosts only), --project / --user (scope, where the
host supports both), --dry-run (unified diff, no write), --instructions
(also inject the memory instruction block), --hooks (also wire automatic
capture, see below).
Behaviour: existing config is parsed and only our memory entry is
added/replaced (unknown keys preserved); a timestamped .bak is written next
to the file before every change; re-running with identical content writes
nothing. JSON hosts are reserialized (2-space indent; all other keys survive).
TOML and comment-bearing files are edited by surgical text splice. If a
JSONC-capable file (VS Code, Zed) actually contains comments, the writer
fails soft: it refuses to touch the file and prints the exact snippet to
paste, rather than dropping your comments.
What gets written where
| Host | Config file (default scope) | Container key | Source |
|---|---|---|---|
claude-code |
./.mcp.json (project) or ~/.claude.json (--user) |
mcpServers |
own-knowledge |
claude-desktop |
%APPDATA%\Claude\claude_desktop_config.json / ~/Library/Application Support/Claude/... / ~/.config/Claude/... |
mcpServers |
own-knowledge |
cursor |
~/.cursor/mcp.json (global) or ./.cursor/mcp.json (--project) |
mcpServers |
own-knowledge |
codex |
~/.codex/config.toml |
[mcp_servers.memory] (TOML) |
OpenAI docs |
vscode |
./.vscode/mcp.json |
servers (note: not mcpServers; stdio type required) |
VS Code docs |
gemini-cli |
~/.gemini/settings.json |
mcpServers |
own-knowledge |
windsurf |
~/.codeium/windsurf/mcp_config.json |
mcpServers |
own-knowledge |
zed |
~/.config/zed/settings.json / %APPDATA%\Zed\settings.json |
context_servers ({command:{...}} shape) |
Zed docs |
cline |
<VS Code User>/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json |
mcpServers |
own-knowledge + cline/cline |
Instruction-line injection (--instructions) writes a short, marker-fenced
memory usage block into the host's instruction file where one exists:
CLAUDE.md (claude-code), AGENTS.md (codex), .cursor/rules/memory.mdc
(cursor), GEMINI.md (gemini-cli).
Automatic capture
An agent that has to remember to call remember will sometimes not, and what
it did not store is gone. Capture closes that gap at the host, not in the
engine: the host runs a command at each lifecycle event, and four of those
events are wired to aleth-mcp capture.
| Lifecycle moment | Command | What it does |
|---|---|---|
| the user sends a prompt | aleth-mcp capture prompt |
spools what the user said, verbatim |
| a tool finishes | aleth-mcp capture tool |
spools one line per action: the tool and the argument that names it, never the tool's output |
| the turn ends | aleth-mcp capture stop |
stores the user's words (origin user) and the agent's record (its final message plus the action list, origin agent), links the two, and links the turn to the previous one |
| the session ends | aleth-mcp capture session-end |
a bounded last attempt for anything still spooled |
Wiring it into a host
aleth-mcp capture hook is the same thing behind one name. It reads the event
out of the payload and routes it, so a host config names one command four times
instead of four different ones. --hooks writes that wiring for you:
aleth-mcp install claude-code --hooks # ./.claude/settings.json
aleth-mcp install codex --hooks # ~/.codex/hooks.json
aleth-mcp install gemini-cli --hooks # ~/.gemini/settings.json
aleth-mcp install cursor --hooks # ./.cursor/hooks.json
aleth-mcp install --all --hooks # every host detected here
aleth-mcp uninstall cursor --hooks # remove exactly our hooks
Same discipline as the server entry: merge only, timestamped .bak, idempotent,
your other hooks and settings untouched, and a file with comments in it is left
alone with the snippet printed for you to paste. --project / --user pick the
scope; the default is the one that makes sense per host, so Cursor's hooks go in
the repo while Codex's go in your home directory. Ship a project's .cursor/ or
.codex/ hooks file in version control and everyone working in that repo gets
capture.
The Claude Code plugin (packaging/claude-code-plugin/) and the Gemini CLI
extension (packaging/gemini-cli-extension/) already carry their hooks, so
installing either of those needs no --hooks at all.
What the record is, and is not:
- It is the record. Nothing is summarised or rewritten by a model. The user's words are stored as said; the agent's record is its own final message and the list of what it ran. Recall gets what happened, not a reading of it.
- It is selective only where selection is mechanical. A go-signal ("ok",
"continue"), a slash command or a host-injected payload is not stored as the
user's words; the actions it triggered still are. The agent's own
memory_*calls are not recorded as actions, because they are writes already. Long texts are clipped, never dropped. - It never costs a turn. Every path exits 0 and reports a failure as one
line on stderr. With the engine down, the turn waits in a local spool and is
stored at the next
stop, of this session or any other; a window that dies mid-turn leaves a spool that the nextstopanywhere sweeps up. - Off is one variable.
ALETH_CAPTURE=0in the shell the host runs in. The spool lives underALETH_CAPTURE_DIR, else Claude Code's per-plugin data directory, else~/.aleth/capture. The engine's own settings are untouched. - Forgetting still works. Something said in passing is in the store;
memory_forgeterases it for real, as it always did. - Nothing goes to stdout. Some hosts parse a hook's stdout, so every word the capture has to say goes to stderr and every path exits 0. A hook never costs you a turn.
The distilled fact, the correction (supersede / retract) and the erasure
stay the agent's job; the plugin's skill and the extension's context file say
so at every session start.
Claude Code's payload was measured on a real run. Codex, Gemini CLI and Cursor are wired from their published references and have not been observed on a live install; if one of them renames a field the record gets shorter, never broken.
Testing
tests/test_install.py drives the host-config writers against real temp
dirs (HOME / APPDATA redirected to tmp_path, no mocked file IO, no test
touches a real config dir). The handler layer is exercised end-to-end
against a real in-process engine + RPC server + wire client, no mocks,
the stdio transport the only layer not under test.
python -m pytest tests/ -q
Release files for aleth-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aleth_mcp-0.2.0.tar.gz | 42.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aleth_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.8 kB
Release files / aleth_mcp-0.2.0.tar.gz
| Download URL | aleth_mcp-0.2.0.tar.gz |
|---|---|
| Size | 42.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d3c264302b9b42aa865c68f57233923890d2e515184aab0b3e13b512d8666e0
|
|
BLAKE2b-256 checksum How to use checksums |
fee7fb75782f9a43c3968ea0ad471151565ce2a10950eaa6283ffee6ce1e8ace
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / aleth_mcp-0.2.0-py3-none-any.whl
| Download URL | aleth_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 34.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1143c01d6815d1c0f5075aa15368b2c4cb829a25a21e9cb69642090414ffe354
|
|
BLAKE2b-256 checksum How to use checksums |
e5d25daeafb380066349d637fee5d838a89c6b4fe220b0e76c43ced0a57beabe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|