MCP integration

Agent Memory

Give your agent a durable memory that survives context-window resets — facts, decisions and procedures, local-first.

@prom.codes/memory-mcp is the second prom.codes MCP server. Where the context engine answers "what does this codebase look like?", the memory server answers "what did we already learn, decide, and agree on?" — and keeps that knowledge across sessions, context-window resets, and agent restarts.

How it works

  • Local-first SQLite. All records live in one database on your machine (~/.prometheus/memory.db), partitioned per project. Nothing is uploaded; the same PROMETHEUS_API_KEY that unlocks the context engine unlocks the memory server.
  • Scope chain. Every record is filed under one of four scopes — project → workspace → tenant → system. Recall walks that chain and, when two records share the same type and key, the narrowest scope wins: a project-level decision overrides a workspace default, which overrides a tenant or system default. That gives you team-wide defaults that any single project can locally override. Both MCP servers derive the project identity the same way (a hash of the workspace root), so they always agree on what "this project" is.
  • Git-versioned project memories. Project-scoped durable facts are mirrored to .prometheus/memories/*.md under your workspace root — plain markdown, human-editable, reviewable in pull requests, shared with the team via git. Deleting a record removes its markdown file too, so the mirror never drifts from the database.
  • Secrets rejected on every write. Before a value reaches the database or a markdown file it is scanned against a deny-list of secret shapes — API keys, JWTs, private-key blocks, Authorization headers and connection strings with inline credentials. A match is refused outright, so memory never becomes a place secrets leak into git.
  • Token-capped recall block. memory_read doesn't just return raw rows — it weave()s the resolved records into a single prompt-ready markdown block with a token budget (≈1,500 tokens by default). Records are packed greedily until the cap is hit, so the agent gets the most relevant memory without blowing up the context window.

Memory types

TypeWhat it holdsExample
semanticDurable facts that stay true across sessions"The staging DB uses connection pooling via pgbouncer"
proceduralRepeatable how-to knowledge"Run integration tests with pnpm test:int from the repo root"
episodicWhat happened in a session — plan and outcome"Refactored the billing module; plan + outcome of the session"
workingShort-lived scratch notes for the current task"Currently bisecting the flaky watcher test"

Tools

The server registers the tools under their short names — read, write, capture, search, list, delete, setup, status — and your MCP client namespaces them with the server name you registered (Claude Code: mcp__memory__read, mcp__memory__write, …). These docs write them as memory_read etc. for readability; agents resolve either form.

ToolWhat it does
memory_readRecall along the scope chain; returns records plus a prompt-ready, token-capped markdown block
memory_writeUpsert one record (identity: scope + type + key); mirrors project facts to markdown
memory_captureSession-end consolidation: plan/outcome → episodic, facts → semantic, procedures → procedural
memory_searchFull-text search (FTS5) over keys and values, ranked, with highlighted snippets
memory_listFlat admin listing with scope/type/key filters
memory_deleteDelete one record by identity; removes the mirrored markdown file too
memory_setupIdempotently install the memory-protocol rules into CLAUDE.md, Cursor, Augment and AGENTS.md configs
memory_statusHealth check: resolved workspace root, project id, DB path, record counts (total + by scope), embedding provider with a zero-cost key probe, and which levers are active

Setup

Add the memory server next to the context engine — same API key:

claude mcp add memory --env PROMETHEUS_API_KEY=prom_live_… -- npx -y @prom.codes/memory-mcp@latest

The -- separator is required before the command. The workspace root and database path are auto-detected — don't set them unless you need to point at a different folder. Mint a real prom_live_… key at /app/api-keys; that one key unlocks both the context engine and memory. Full install options (scopes, .mcp.json, Cursor/VS Code) are on the Claude Code install page.

Making the agent actually use memory (awareness)

An MCP server only offers tools — the agent uses them only if it knows it should. Nobody is sitting there typing "now read memory," so memory has to make the agent proactive on its own. Three layers do that, strongest last:

  1. Server instructions — on connect, the server tells the agent the protocol (read at session start, write durable facts as they come up, capture at the end). Hosts that surface MCP instructions (Claude Code) inject this into the model's context immediately. Nudge — same session, host-dependent.
  2. Auto-installed rule (default on). On startup, the server writes a small, marked memory-protocol rule block into the runtime config files that already exist in your project — CLAUDE.md, AGENTS.md, .cursor/rules/, .augment/rules/. Those files are loaded every session by every host, so the awareness becomes durable and tool-independent. It is idempotent (re-running updates the marked block in place, never touches your own content), never creates a new config file unprompted (only appends to ones you already have), and is skipped when no project is open (home/root). Opt out with PROMETHEUS_MEMORY_AUTO_SETUP=off.
  3. memory_setup (explicit). Run it to install the rule into all detected runtimes (incl. creating AGENTS.md if you have no config yet) — the manual equivalent of layer 2 with full coverage.

The rule block is the canonical one: recall at session start → store durable preferences/decisions/corrections as they happen → search when recall is not specific enough → capture at session end → never store secrets. Check what's installed (and everything else) with memory_status (rules.installed).

Net effect: add the server to a project that already has a CLAUDE.md (or AGENTS.md) and memory becomes self-using on the next session — no manual step. In a project with no config file yet, run memory_setup once.

Environment variables

VariableRequiredDefaultPurpose
PROMETHEUS_API_KEYyesprom_live_… key minted at /app/api-keys; validated at startup
PROMETHEUS_WORKSPACE_ROOTnoautoAnchors the project identity + .prometheus/memories/ mirror. Auto-detected (0.3.1+) from CLAUDE_PROJECT_DIR / MCP roots; set only to override
PROMETHEUS_MEMORY_DB_PATHno~/.prometheus/memory.dbOverride the shared memory database location
PROMETHEUS_MEMORY_EMBEDnoautoVector channel: auto (Voyage if VOYAGE_API_KEY, else proxy, else keyword) · voyage · prometheus · off
PROMETHEUS_MEMORY_RERANK_PROVIDERnoprometheus (when key set)(0.3.2+; proxy default 0.10.0+) Second-stage cross-encoder for memory_search: prometheus (metered proxy, on by default with your PROMETHEUS_API_KEY — no extra key, provider hidden) · voyage (direct, your own VOYAGE_API_KEY) · bge (self-hosted /rerank via PROMETHEUS_MEMORY_RERANK_ENDPOINT) · none (opt out)
PROMETHEUS_MEMORY_EXTRACT_PROVIDERnonone(0.4.0+) Extraction-at-write LLM for memory_capture: mistral (EU, MISTRAL_API_KEY) · openai (OPENAI_API_KEY) · generic (PROMETHEUS_MEMORY_EXTRACT_ENDPOINT) · none
PROMETHEUS_MEMORY_REWRITE_PROVIDERnonone(0.5.0+) HyDE query-rewrite LLM for memory_search: mistral · openai · generic (PROMETHEUS_MEMORY_REWRITE_ENDPOINT) · none
PROMETHEUS_MEMORY_TEMPORALnoon(0.7.0+, on by default) Temporal-intent ranking — promotes the newest/oldest matching record for "latest"/"earliest" queries. Pure-local, free; set off to disable.
PROMETHEUS_MEMORY_TEMPORAL_WEIGHTno2.0Blend strength of the timestamp rank vs relevance order (only when temporal is on).
PROMETHEUS_MEMORY_AUTO_SETUPnoon(0.9.0+) On startup, install the memory-protocol rule into runtime config files that already exist (idempotent, marked, never creates a new file; skipped for home/root). Set off to disable auto-writing.
PROMETHEUS_MEMORY_DEDUPnooff(0.8.0+) on collapses near-duplicate records in read/search (restatements of the same fact under different keys / across scopes) before the limit. Pure-local.
PROMETHEUS_MEMORY_DEDUP_THRESHOLDno0.92Embedding-cosine cut above which two records are treated as duplicates (only the text-equality signal applies without embeddings).

Re-ranking (optional, sharper recall)

memory_search is hybrid: FTS5 keyword vector cosine, fused with RRF. On top of that runs a cross-encoder second stage — and with just your PROMETHEUS_API_KEY it's on by default through the metered proxy (PROMETHEUS_MEMORY_RERANK_PROVIDER=prometheus): no extra key, and the upstream model stays hidden. It scores each candidate's key + value jointly against your query and reorders the top pool before returning. Best-effort: if the reranker is offline it silently falls back to the hybrid order, so it never breaks recall. Set PROMETHEUS_MEMORY_RERANK_PROVIDER=none to opt out, or =voyage to call Voyage rerank-2.5 directly with your own VOYAGE_API_KEY.

On the standard LoCoMo agent-memory benchmark, turning rerank on lifted judge accuracy +8.4 points (38.3% → 46.7%) and matched the full-context ceiling at ≈28× fewer tokens (paired run, n=60).

Extraction at write (optional, cleaner facts)

By default memory_capture stores the facts you pass verbatim. Set PROMETHEUS_MEMORY_EXTRACT_PROVIDER (mistral for EU, openai, or a generic OpenAI-compatible endpoint) to also mine durable atomic facts from the freeform plan/outcome with one LLM call — turning prose like "we moved deploys to Railway and switched the test command" into clean, retrievable facts. This is the lever that separates a note-taking memory from a learning one (what Mem0/Zep do).

It is opt-in (default off → no LLM call, fully local). Mined facts are merged with your explicit facts (your keys win on collision), each is secret-gated independently, and the whole step is best-effort — an extractor error leaves capture storing your facts only.

Query rewriting (optional, HyDE)

Set PROMETHEUS_MEMORY_REWRITE_PROVIDER (mistral for EU, openai, or a generic endpoint) to rewrite a search into question + a short hypothetical memory entry before the FTS/vector channels — pulling the query toward the wording of the record that answers it. Re-ranking still scores the original question. Opt-in, best-effort (a failure falls back to the raw query).

It helps most where the model can plausibly generate the answer's vocabulary (temporal/domain questions) and little where it can't (unknowable specifics). On a LoCoMo A/B it was a modest +2.7 pt overall (temporal category 20% → 60%); for coding-project questions it should help more — measure on your workload.

Temporal queries (optional, recency-aware)

memory_search honours an explicit time intent in the query. "What's our latest decision on auth", "how do we currently deploy", "the first database we tried" — the answer is the most- (or least-) recently updated record, but keyword/semantic ranking scores by wording, not time. When (and only when) the query carries a clear recency/earliest cue, the engine applies a decisive recency (or antiquity) blend over the records' updated_at as the final step.

It is on by default because it is pure-local and free (no LLM, no network) and has no effect on non-temporal queries — a query without a time cue is returned in its normal relevance order (measured: enabling it leaves ordinary recall byte-for-byte unchanged). Set PROMETHEUS_MEMORY_TEMPORAL=off to disable, or tune the strength with PROMETHEUS_MEMORY_TEMPORAL_WEIGHT (default 2.0).

On a dedicated timestamp probe (planting evolving versions of the same fact), it lifts Recall@1 on time-intent queries from 0.43 → 0.88 (recency queries 0.15 → 0.90). LoCoMo can't measure this — its temporal answers live in the record text, not the updated_at metadata this lever reads.

Deduplication (optional, denser recall)

Over a project's lifetime, memory accumulates restatements of the same fact — "we deploy via Railway" under one key, "deployment target: Railway" under another, or the same fact written at both project and workspace scope. Scope resolution only collapses records that share type+key, so these each take a top-k slot and dilute recall. Set PROMETHEUS_MEMORY_DEDUP=on to collapse them in memory_read / memory_search before the limit, keeping the highest-priority representative. Two signals: normalized-text equality (free, exact) and embedding cosine ≥ PROMETHEUS_MEMORY_DEDUP_THRESHOLD (default 0.92, conservative — only when embeddings are on).

Pure-local and best-effort. On a restatement-polluted recall probe it lifted distinct facts in the top-12 from 5 to 12, and on a set of distinct facts it was byte-for-byte unchanged (it never merges things that aren't duplicates). It is off by default because, unlike temporal ranking, it drops records — opt in once your memory has grown enough to benefit.

What stays on your machine

The index does. Records live in SQLite and project memories live as markdown in your repo; the only thing committed is what you choose to commit to git. In the keyword-only default the server makes no network calls at all. If you enable the vector channel (PROMETHEUS_MEMORY_EMBED) or re-ranking (PROMETHEUS_MEMORY_RERANK_PROVIDER), only the short query/record text is sent to the embedding/rerank provider you configured — never your whole database.