FOR CLAUDE CODE

Add persistent memory to Claude Code

Claude Code talks to the Kireo memory MCP server over stdio. One claude mcp add command registers it at user scope, giving every project on this machine eight memory tools — save, search, recall, get, update, delete, list_namespaces, health. Memories persist across sessions, repos and machines, unlike CLAUDE.md and auto memory, which are files.

Install

You need Node 20 or newer on PATH (the package declares "node": ">=20.0.0") and a key shaped ki_sk_…. This is the exact one-liner that was run end to end against claude 2.1.220 on 2026-08-03 — copy it verbatim, including --package=, and read gotcha 1 before you shorten it.

terminal
bash
claude mcp add kireo --scope user --env KIREO_API_KEY=ki_sk_xxx -- npx -y --package=@kireo/mcp-server kireo-mcp

Claude Code prints Added stdio MCP server kireo with command: npx -y --package=@kireo/mcp-server kireo-mcp to user config and writes the entry into ~/.claude.json. There is no third file to edit by hand: this CLI touches ~/.claude.json for local and user scope, and .mcp.json at the repository root for project scope.

Then confirm it actually connected. "Added" and "connected" are two different events, and only the second one means the tools exist:

terminal
bash
claude mcp list # kireo: npx -y --package=@kireo/mcp-server kireo-mcp - ✔ Connected claude mcp get kireo # Scope: User config (available in all your projects) # Status: ✔ Connected # Type: stdio
With the npx cache already warm, spawn → tools/list took 791–1106 ms over five runs (median 812 ms), returning 8 tools every time — macOS 26.2 arm64, Node v25.8.2, npm 11.11.1, @kireo/mcp-server 0.2.1, measured 2026-08-03. The first ever run also downloads the package and was not measured, so expect it to be slower.

Where this sits next to CLAUDE.md and auto memory

Claude Code already ships two memory surfaces, and Anthropic's documentation presents them as complementary rather than competing: CLAUDE.md is written by you and holds instructions and rules, while auto memory is written by Claude and holds learnings and patterns (code.claude.com/docs/en/memory, checked 2026-08-03). Both are on by default and cost nothing. Installing the Kireo memory MCP does not replace either.

What it adds is a third property neither file has. The same documentation states plainly: "Auto memory is machine-local. All worktrees and subdirectories within the same git repository share one auto memory directory. Files are not shared across machines or cloud environments." CLAUDE.md crosses machines the moment you commit it, but only as text you maintain by hand — the docs suggest keeping each file under 200 lines because longer files consume more context and reduce adherence, and @path imports do not help, since imported files load at launch too.

Keep it in the file
  • Rules that must apply in every turn.
  • Build, test and lint commands.
  • Style and review conventions your team enforces.
  • Anything you would happily open an editor and type by hand.
Put it in the Kireo memory MCP
  • Facts you look up occasionally, not on every turn.
  • Decisions you need in a different repository.
  • Context that has to follow you to a second machine.
  • Anything you also want reachable from Cursor or another MCP client.

If your only problem is "auto memory does not follow me between machines", try the official autoMemoryDirectory setting first — it points the auto memory directory at any absolute path, including a synced drive, and costs you nothing. The longer version of this comparison, including where each approach genuinely wins, is in CLAUDE.md vs a memory MCP.

The eight tools 8

Read off packages/mcp-server/src/tools/index.ts on 2026-08-03, in registration order. A live tools/list against the published package returned the same eight names, character for character. Every schema is strict: an unknown field is rejected with InvalidParams, not silently dropped.

memory_savecontent required
Writes one memory. content is 1–8,000 characters; type is one of fact, decision, preference, event, goal, insight, relationship, other (default fact); optional namespace (default "default"), up to 20 entities, up to 10 tags, importance 0–1, occurred_at, and metadata capped at 2,048 bytes once serialised. Returns { id, created_at, schema_version, embedding_status } — not the full record.
memory_searchquery required
Hybrid ranked retrieval. query is 1–2,000 characters; limit defaults to 10 and caps at 50. Omitting namespace searches every namespace you own, which is usually what you want when you cannot remember which project a decision belongs to.
memory_recallno required fields
Browses a namespace instead of searching it. namespace defaults to "default", limit defaults to 20 and caps at 50, order is recency or importance, and since takes an ISO-8601 timestamp.
memory_getid required
Fetches one full record by id. The id must match ^mem_[A-Za-z0-9]+$ — the same shape memory_save handed back.
memory_updateid + at least one field
PATCH semantics: only the fields you pass change. Passing nothing but id is rejected with "Provide at least one field besides id".
memory_deleteid required
Soft delete only. A hard flag is accepted for compatibility and ignored; the memory stays recoverable for 30 days, then is purged automatically.
memory_list_namespacesno input
Returns { name, created_at } per namespace — names and creation times, no per-namespace counts.
memory_healthno input
Returns { local: { server_version, node_version, platform }, remote: { status } }. This is the fastest way to tell "the server never started" apart from "the server started and the API is unreachable".
Two behaviours worth knowing before you rely on them. min_score on memory_search is not an API parameter — the server applies it locally after the API has already honoured limit, so a filtered result set is legitimately shorter than the limit you asked for. And memory_recall with order: "importance" scans at most the 1,000 most recent rows and sorts those, always returning next_cursor: null — it is a recency-weighted ranking, not a ranking of your whole history, and it cannot be paged.

Three Claude Code gotchas

All three are properties of the claude CLI and its scope model. None of them apply to clients that read a JSON file directly. Everything below was reproduced against claude 2.1.220 on 2026-08-03 and has not been regression-tested against other versions.

1. A bare -p after -- breaks the command

The npm package ships two binaries, kireo and kireo-mcp, so npx cannot infer an executable from the package name and needs to be told which one to run. The natural way to say that is -p @kireo/mcp-server — and that is exactly the token Claude Code's own parser grabs, after which it rejects the flags you passed it:

terminal
bash
# WRONG — the bare -p is eaten by Claude Code's own parser claude mcp add kireo --env KIREO_API_KEY=ki_sk_xxx -- npx -y -p @kireo/mcp-server kireo-mcp error: unknown option '--env'

The same command fails with unknown option '-e' or unknown option '--scope' depending on which flag you used first. Swapping -p for npx's long form --package= makes the identical command succeed.

Dropping the flag entirely is worse than an error, because it looks like success. The CLI accepts the entry and writes it to your config; only claude mcp list reveals that it never starts:

terminal
bash
# WRONG — accepted by the CLI, then never connects claude mcp add kireo --env KIREO_API_KEY=ki_sk_xxx -- npx -y @kireo/mcp-server claude mcp list # kireo: npx -y @kireo/mcp-server - ✘ Failed to connect — -32000: MCP error -32000: Connection closed

If you would rather not think about the parser at all, claude mcp add-json skips option parsing completely, and inside JSON the short -p is perfectly fine:

terminal
bash
claude mcp add-json kireo '{"command":"npx","args":["-y","--package=@kireo/mcp-server","kireo-mcp"],"env":{"KIREO_API_KEY":"ki_sk_xxx"}}'

2. The default scope is local, and a stale local entry hides everything else

claude mcp add --help spells it out: -s, --scope <scope> is (default: "local"). Install without a scope and claude mcp get kireo reports Scope: Local config (private to you in this project). The tools then vanish the moment you open a different repository — which reads as "the memory server broke", not "the memory server was only ever installed here".

The second half of this is nastier. Claude Code resolves same-named servers by precedence — local, then project, then user — and "the entire server entry from that source is used; fields are not merged across scopes" (code.claude.com/docs/en/mcp, checked 2026-08-03). So one forgotten local kireo entry from an earlier experiment completely shadows the correct user-scope entry you added later, including its key. If claude mcp list shows a command line you do not recognise, clear the local one with claude mcp remove kireo before you debug anything else.

3. Project scope needs approval, and ${VAR} fails quietly

--scope project writes .mcp.json at the repository root — the right choice for a team, because everyone gets the same tools from source control. It does not connect straight away, though: claude mcp list shows ⏸ Pending approval (run `claude` to approve), because Claude Code asks each person before it will run a server defined by a checked-in file (code.claude.com/docs/en/mcp, checked 2026-08-03; claude mcp reset-project-choices resets those answers). This is the file Claude Code writes, note the explicit "type": "stdio":

.mcp.json
json
{ "mcpServers": { "kireo": { "type": "stdio", "command": "npx", "args": ["-y", "--package=@kireo/mcp-server", "kireo-mcp"], "env": { "KIREO_API_KEY": "${KIREO_API_KEY}" } } } }
Never commit the raw key. Claude Code expands ${VAR} and ${VAR:-default} inside .mcp.json — but if the variable is unset and has no default, the config still loads, a missing-variable warning appears in claude mcp list, and the literal text ${KIREO_API_KEY} is passed through as-is. That literal fails the server's ^ki_sk_[A-Za-z0-9_-]+$ check, so the process exits at startup and the failure presents as a broken install rather than a missing environment variable.

When it says "Failed to connect"

Configuration errors kill the process before the MCP handshake, and they are written to stderr only — which is why the host shows a bare connection error and nothing useful. The two you will actually hit:

  • No key at all → [kireo-mcp] fatal: Error: Invalid Kireo configuration: KIREO_API_KEY (apiKey): Required, exit 1.
  • Wrong shape (an OpenAI-style sk-… key, say) → KIREO_API_KEY must look like ki_sk_xxx.

For anything past that, read the rolling log files. They live in ~/.kireo/logs/ on all three platforms, but the writer appends a numeric suffix, so the real filenames are mcp-server.log.1, .log.2 and .log.3 — there is no plain mcp-server.log to cat. Raising KIREO_LOG_LEVEL=debug is safe over stdio: logs go to those files plus a warn-level stderr stream, and stdout is reserved for JSON-RPC, so debug logging cannot corrupt the session.

One capacity limit specific to this host: Claude Code warns when MCP tool output exceeds 10,000 tokens and caps it at 25,000 by default, raisable via MAX_MCP_OUTPUT_TOKENS (code.claude.com/docs/en/mcp, checked 2026-08-03). Since memory_search and memory_recall both allow limit: 50, pulling fifty long memories in one call can run into that ceiling. Ask for fewer, more specific results instead of raising the cap.

Index the repo you already have open

Claude Code runs in the terminal, so the code indexer runs in the same place — no editor UI, no extension. Point it at the repository you are already in and it uploads extracted symbols into a code-<repo> namespace, which memory_search can then reach from any MCP client, not just this one. This one runs in a plain shell rather than through claude mcp add, so the short -p from gotcha 1 is fine here:

terminal
bash
export KIREO_API_KEY=ki_sk_xxx npx -y -p @kireo/mcp-server kireo index ./ --repo my-app

Re-running is safe and incremental — a local .kireo/index-state.json holds a SHA-256 per file and only changed files are re-extracted, while the server deduplicates on (namespace, content_hash).

For a sense of scale, here is this site's own monorepo, measured on 2026-08-03. This covers the local half only — directory walk plus tree-sitter extraction, with no upload and no embedding: 336 source files totalling 1,371,357 bytes, from which 213 symbols were extracted (184 functions, 4 classes, 25 methods), in 486–653 ms across three runs. The derived namespace was code-lancedb_sass. Upload and embedding time is not included in that figure and was not measured.

The gap between 336 files and 213 symbols is worth understanding before you conclude something is missing. In this version the TypeScript rules match function_declaration, class_declaration and method_definition only, so export const foo = () => {} is not extracted; .vue single-file components are not indexed at all (38 of them at the time this was measured — this page is itself one, so the count has grown since; none appear in the output); and the walker skips node_modules, dist, .venv, .git and .kireo on top of your .gitignore, along with symlinks, binaries and any file over 1,000,000 bytes.

FAQ

Why does `claude mcp add … -- npx -y -p @kireo/mcp-server kireo-mcp` fail with "error: unknown option '--env'"?

The bare -p after the -- separator is swallowed by Claude Code's own option parser, so the CLI rejects the flags you actually passed — the same command reports unknown option '-e' or '--scope' depending on which one you used. Use npx's long form instead: npx -y --package=@kireo/mcp-server kireo-mcp. Verified against claude 2.1.220 on 2026-08-03; claude mcp add-json bypasses the parser entirely if you prefer.

Do I still need CLAUDE.md once the Kireo memory MCP is installed?

Yes. CLAUDE.md is where you hand-write the rules you want applied in every session, and Anthropic's own documentation suggests keeping each file under 200 lines because longer files consume more context and reduce adherence (code.claude.com/docs/en/memory, checked 2026-08-03). The Kireo memory MCP is for facts you look up on demand and need in another repository, on another machine, or in another editor. Rules stay in the file; recall goes through the tools.

Which scope should I use — local, user or project?

Use --scope user for a personal key: one entry, available in every project on that machine. --scope project writes .mcp.json into the repository, which is the right choice for a team, but Claude Code asks each person for approval before it will connect, and you must not commit a raw key there. local is the default and is private to a single project directory, which is usually not what people expect when they install a memory server once.

Claude Code says the kireo server failed to connect. Where do I look?

Start with claude mcp list and claude mcp get kireo. Startup failures are written to stderr only, so the host usually shows nothing but a connection error: a missing key prints "[kireo-mcp] fatal: Error: Invalid Kireo configuration: KIREO_API_KEY (apiKey): Required" and exits 1, and a key in the wrong shape prints "KIREO_API_KEY must look like ki_sk_xxx". Fuller logs land in ~/.kireo/logs/, where the rolling files are named mcp-server.log.1, .log.2 and .log.3 — there is no plain mcp-server.log.

Does memory_delete erase a memory permanently?

No. The Kireo memory MCP performs soft deletes only. memory_delete accepts a hard flag for compatibility, but the API ignores it and the tool says so in its reply; the record stays recoverable for 30 days and is then purged automatically. If a memory is merely out of date, memory_update is the better tool — it patches only the fields you pass.

Last verified 2026-08-03 against claude 2.1.220 and @kireo/mcp-server 0.2.1. Tool names and limits were read from source; CLI behaviour was reproduced on macOS 26.2 arm64.