mnimi-mcp
A Model Context Protocol server over one mnimi store: five tools and one resource, stdio transport, one SQLite file. Give Claude Code (or any MCP host) a long-term memory that lives on your disk, writes facts with a small local model, never calls a model when it reads, and can be read whole as Markdown.
The server is its own package so the library keeps its two dependencies (sqlite-vec,
numpy). It depends on mnimi-memory[embed,extract]>=3.1.1,<4 (the PyPI name of import mnimi) (the BGE embedder and the Qwen3-1.7B
extractor; 3.1.1 is the floor because the server uses mnimi.extract.wrappers — the sub-round
split, the backslash pre-escape and client facts, new in 3.1.0 — and 3.1.1 is where a fact that
mentions a \ path comes back spelled the turn's way) and on the
mcp SDK (written and tested against mcp 2.3.0, 2026-10-02).
Install
mnimi-memory[extract] pins llama-cpp-python==0.3.35, which PyPI ships only as a source
distribution (it needs a C++ compiler). The project's own index carries a prebuilt CPU wheel,
so pass it on the install line and nothing is compiled:
pip install mnimi-mcp --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
Before mnimi-memory 3.1.1 is on PyPI, install both from a checkout of the repository (the
release/v3 branch), into a venv of its own:
pip install -e . -e integrations/mcp --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
Either line puts a mnimi-mcp console script on the venv's Scripts/bin path. The GPU
profile (MNIMI_EXTRACTOR=gpu) needs a CUDA build of the same version instead
(docs/DECISIONS.md "Extractor runtime"); the CPU profile is the default and measured at
21.0 s per round on an 8-core laptop CPU (examples/README.md).
Register it with Claude Code
claude mcp add --scope user mnimi -e MNIMI_DB=/absolute/path/to/mnimi/agent.db -- mnimi-mcp
Give MNIMI_DB an absolute path: outside bash a ~ reaches the server literally (the server
does expand ~ and $VAR itself, and makes a relative path absolute against its working
directory, which under Claude Code is the project folder, not your home). --scope user makes
the memory follow you across projects; the default scope is one project. mnimi-mcp must
resolve on the PATH Claude Code starts the server with; give the venv's full path to the script
otherwise. Then claude mcp list shows the server connected and /mcp inside a session lists the
five tools. On the first tool call the server opens the DB,
loads the embedder (one 130 MB download, pinned by revision) and, under MNIMI_EXTRACTOR=cpu
or gpu, the extractor (one 1.8 GB download, pinned by revision). Listing the tools loads
nothing.
Tell the model to use it
The server sends an instructions string on connect, and hosts differ in how much weight they
give it. What makes the memory fire reliably is a rule in the file the host reads on every turn
(CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules, a system prompt). Paste this and adjust
the names:
## Memory (mnimi)
- Before answering anything that may depend on an earlier session, call mnimi `context` with the
question.
- When I state a durable fact, preference or decision, call mnimi `remember` with that exchange
(both turns).
- For a dense turn (results, decisions, many numbers), pass the facts yourself:
`remember(messages, facts=[...])`, one sentence each, starting with "The user" or "The assistant".
- Do not store secrets, keys or passwords in memory.
The first two lines are the whole protocol: read before you answer, write when something durable was said. Without them a model calls the tools only when asked to, and the memory stays empty.
One memory or several
One server process is one Memory over one DB and one user_id. Two hosts (two Claude Code
projects, Claude Desktop beside a CLI agent) are two processes, and three layouts are possible with
nothing but the environment:
| layout | settings | what you get |
|---|---|---|
| one DB per agent | MNIMI_DB=agent1.db for one, agent2.db for the other |
independent memories, independent files; each process loads its own models (≈ 2 GB of RAM each while both run) and keeps its own extraction cache beside its DB |
one DB, one user_id per agent |
the same MNIMI_DB; MNIMI_USER_ID=claude for one, codex for the other |
one file, still independent: recall, context, export, the logical clock and the decay pass are all per user_id; one shared extraction cache |
one DB, one user_id |
the same MNIMI_DB and the same MNIMI_USER_ID (or the default me) |
shared memory: what one host remembers the other recalls, and the session day either sees is the same day |
How the shared layout holds up. Each process opens its own connection in WAL mode with a 30 s
busy timeout, so a write from one host waits for the other's instead of failing; a second host
under other pins (another embedder, gpu on a cpu store) is refused with MemoryMetaError;
nothing about the store is cached in process memory, every call reads the file; the extraction
cache is a deterministic insert-or-replace, so two writers cannot disagree. A test starts two
servers over stdio against one DB and reads from the second what the first stored.
Two limits, stated plainly. The in-process lock does not span processes, and the dedup and
conflict screens are read-then-write: two remember calls storing the same round at the same
instant can both store it (a duplicate, never lost data; rare for one person talking to one
assistant at a time). And consolidate from either host decays the other's records too, which
is what shared means. The server is stdio-only: a host that wants a remote (HTTP) MCP server
cannot reach it today.
Configuration (environment)
| variable | values | default | what it is |
|---|---|---|---|
MNIMI_DB |
a path | required | the SQLite store; created with its memory_meta on first use |
MNIMI_USER_ID |
a string | me |
the user_id every record is stored and recalled under |
MNIMI_EXTRACTOR |
cpu · gpu · none |
cpu |
cpu = the measured recipe ({**DECODE, "n_gpu_layers": 0, "max_tokens": 2048}; the output budget doubled 2026-10-14 for dense turns); gpu = the pinned decode (CUDA build); none = no model: rounds only, plus the facts you pass to remember |
MNIMI_EMBEDDER |
bge · hashing |
bge |
bge = BAAI/bge-small-en-v1.5 (the real one); hashing = numpy only, for tests |
MNIMI_SPLIT_CHARS |
an integer | 600 |
the sub-round split: each round is extracted whole first, and only when that output is truncated, at the fact cap or empty are turns longer than this cut at sentence boundaries into sub-rounds and re-extracted (the reading with more facts is kept), so a long answer is not lost to the model's output budget; 0 = off |
A store remembers these pins in memory_meta. Opening it later under other pins (another
embedder, the GPU profile on a CPU store) is refused with MemoryMetaError, and the tool call
returns that message as its error text (so the model, and you, read it): re-point MNIMI_DB at
a fresh file or use the pins the store was built with. A model that cannot load
(MNIMI_EXTRACTOR=gpu on a CPU build) and a bad setting come back the same way. A store
written before mnimi 3.1.0's resolver_version = "v3" (the 2026-10-14 test-week fixes), or a
MNIMI_EXTRACTOR=none store from before client facts, is refused the same way: point MNIMI_DB
at a fresh file and re-remember the rounds (the extraction cache replays what it has). A
none store of this server carries the client-facts pins; examples/chat.py --extractor none
builds a store with no extractor at all, so the two are not interchangeable.
Tools
| tool | arguments | returns |
|---|---|---|
remember |
messages: [{role: user|assistant, content}] (at least one); facts: [{content, raw?, when?, subject?, predicate?, object?, salience?}] (optional) |
{round, fact, merged, superseded, ts, facts_supplied, facts_unused?}: the records this call added by kind (a duplicate round adds 0), the facts the dedup merged into a near-identical stored fact instead of adding (two facts quoting the same long raw excerpt collide this way — give each its own clause), the earlier facts it superseded, the session timestamp they were stamped with, how many facts you supplied, and — only when the pre-filter skipped the round — how many of them went unused |
recall |
query, k (default 10) |
[{score, salience, kind, text, session_date}], best first; the returned memories count as accessed today, which protects them from the next decay pass |
context |
query |
the context block the library hands a reader: session-date headers, user:/assistant: lines, the facts |
export |
— | the whole memory as Markdown (the same text mnimi export --format md writes) |
consolidate |
— | {conflict, decay, now_logical}: what this call changed (the conflict pass's counters; the decay pass's passes, decayed, at_floor) and the logical now; a second call changes nothing |
Resource: memory://export (text/markdown), the same Markdown as the export tool.
Supplying facts yourself. The local 1.7B extractor skips dense assistant turns (results,
decisions, commit ids) and extracts at most eight facts per sub-round. When you know the facts,
pass them: remember(messages, facts=[...]) stores your facts for that round and does not run
the model. They go through the same date resolution, the same dedup screens and the same
conflict pass as the model's facts; raw must be verbatim text of the turn (omit it and the
server picks the closest sentence), when must be the turn's own words (an expression the turn
does not contain is dropped and the fact is stored undated). Needs exactly one round per call.
The clock lives here, not in the library. Every message remember stores is stamped
ts = the server's calendar day at the call (YYYY-MM-DDT00:00:00): a day is a session, the
shape the benchmark has. A ts the model puts in a message is ignored. The same day reaches
recall and context as the library's documented query prefix ([Current date: <ts>], parsed
for the question's own time window and stripped before embedding), so the shipped
configuration's time-aware term fires on "last week" or "three days ago". The library reads
only what it is handed and never a clock.
stdout belongs to the transport. Everything the server and the library log goes to
stderr, including mnimi.memory's INFO lines (superseded …, routed to conflict …,
decayed …); Claude Code shows them in its MCP log.
Try it
- Tell it three things about yourself over a conversation, then ask about one of them
indirectly in a new session:
contextshould carry the right round. - Contradict one of them another day: the
superseded …line, andexportshows the old fact struck through. - Give it a relative date ("dentist next Thursday") and later ask when: the fact's
valid_timeis resolved from the session date. consolidatetwice: the second pass changes nothing.- Read
memory://exportas a resource.
Development
pip install -e integrations/mcp --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu
pytest integrations/mcp/tests # also run by the root pytest; skipped when mcp is absent
(The mcp package is not in the root [dev] extra; the index is the CPU wheel's, as above.)
The tests drive the server through the SDK's in-memory client (Client(server.mcp)) under
MNIMI_EMBEDDER=hashing MNIMI_EXTRACTOR=none over a temporary DB. The server imports
mnimi.extract.llama for the decode dict, mnimi.extract.wrappers for the split, the pre-escape
and client facts, and mnimi_cli.main for the Markdown export; it changes nothing under
src/mnimi/. The published numbers were measured on tag v2.14.0; 3.1.x departs from it in
extraction only.
Apache-2.0, like the library.
Metadata
Release files for mnimi-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 | |
|---|---|---|---|
| mnimi_mcp-0.2.0.tar.gz | 21.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mnimi_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.7 kB
Release files / mnimi_mcp-0.2.0.tar.gz
| Download URL | mnimi_mcp-0.2.0.tar.gz |
|---|---|
| Size | 21.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
abad25b63464805998632743bdd39963c4a302d088d4031895b1112c8d019e93
|
|
BLAKE2b-256 checksum How to use checksums |
d7cd2e783936cefb3232d53e844af09d92f5ef65052a42fa382c220837f732f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / mnimi_mcp-0.2.0-py3-none-any.whl
| Download URL | mnimi_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 17.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6f2e3099d02de82be87f1effa3186cd4bf73dd0fa0611dfd074da9cf206689b6
|
|
BLAKE2b-256 checksum How to use checksums |
c5f5a30d5f894bf905a0313ad713d55cec3e88ac9f329156c4785f5ec8687d02
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log