Scoped memory for coding agents: picks the right project, answers in scope, or abstains.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
Scoped memory for coding agents. A router in front of two stores.
Knowledge graphs hold structure but no prose, so they cannot answer why did the
cookie get dropped. Verbatim-recall systems hold prose but no call graphs, so
they cannot answer what calls run_agent_turn. Both make you name the
namespace when you write.
loci decides the scope when you read. One question path serves "how does auth work here" and "have I solved this in any project" β only the size of the scope set changes.
A street of independent shops. Each shop is one project β its own stock, its own books, nothing pooled in a shared warehouse. Each keeps two ledgers: what calls what, and what happened and why. They are read together or not at all.
Shops wear overlapping badges β yours, this client's, that vendor's, this monorepo's. A badge can cover shops at opposite ends of the street, which is why they are labels rather than a hierarchy.
You never walk the street yourself. You ask the dispatcher, who knows only which uncommon words are stocked where. They send you into at most three shops, or say nobody here carries that and name who might.
Each shop also hangs a signboard naming what it is β repository, package, command β and keeps a list of what it reaches for. Reading one against the other is how the street answers which of my shops buys from another, which no amount of asking the dispatcher ever could: that question is about the road between two shops, and the dispatcher only knows what is stocked inside them.
One thing the street still lacks: aisles inside a shop, so a word found in a test fixture does not count like the same word in shipped code.
Put every project in one index and the largest one wins regardless of the question. Measured against a merged graph of ten real repositories:
| question | on-topic nodes returned |
|---|---|
| "why was the admin session cookie dropped on localhost?" | 18% β 19 of 31 came from the biggest project |
| "what happens when a user clicks Save as ZIM?" | 2% β 61 of 62 came from the biggest project |
| "how does the reserved landing step work?" | 98% β and the biggest project was the answer |
That third row is the trap. Merged retrieval looks excellent whenever the answer happens to live in the largest corpus and collapses when it does not. It is the same failure in both directions, visible in only one.
The distribution is loci-mem; the command, the import and the project are
all loci. PyPI's loci is an unrelated outlier-detection package abandoned in
2018 β the same split as python-dateutil installing as dateutil.
The base install pulls rank-bm25, scikit-learn, numpy and joblib. No
torch, no vector database, no model download unless you ask for one.
| extra | adds |
|---|---|
graphify | code-symbol extraction across ~18 languages |
embeddings | local bge-small semantic ranking |
rerank | cross-encoder reranking (opt-in per query) |
mcp | the MCP server |
setup asks only what it cannot decide for you: which directories hold your
projects, whether to register the repositories it found that are not yours, and
whether to spend a one-time model download on semantic search. It ends by
running doctor, so whatever it could not cover is the last thing you read
rather than something you discover from a bad answer a week later.
Every prompt takes its default when stdin is not a terminal, so it is safe to
run unattended in a container or under an agent. -y does the same from a
terminal, and --no-graphs / --no-embed / --no-calibrate decide individual
steps up front.
The same thing by hand. The order is a dependency chain, not a preference:
graphs are what the index is built from, the index writes the chunks embed
encodes, and calibrate fits its semantic floor from those vectors.
Each step writes what the next one reads, which is what makes the order fixed rather than preferred:
Running it out of order does not fail loudly. It produces an install that works
and quietly retrieves worse, which is the failure doctor exists to name.
scan registers one scope per git repository. It also reads who owns each
repository out of git, prints who owns what, and asks before registering the
ones that are not yours; that prompt takes its default like every other, which is to
register everything.
A monorepo can become one scope per package instead. --split on loci scan or
loci setup takes anything carrying package.json, pyproject.toml,
Cargo.toml or go.mod one level down; a repo-local .loci.json names the rest
and is honoured with or without the flag. --split is off by default, and
the reason is measured rather than cautious: a new scope's aliases include its
bare directory name, and an alias outranks your working directory (6.0 to 4.0).
On the development corpus, splitting a repository holding glasses/ sent eight
hand-written questions about a different project to Delroy/glasses β seven of
them had routed correctly before the split, and six of the eight reverted when
the alias boost was zeroed. A Jekyll _site/ build directory became a scope of
its own on the same run. A .loci.json you wrote does not have that problem: you
named those sub-projects deliberately, so you can see what they are called.
ask uses your working directory by default, and should. Questions that name no
project β "how is this deployed?", "how do I run the tests?" β route
correctly 100% of the time with cwd. Without it they are unanswerable, and
loci abstains on all of them rather than guessing. cwd is not a tiebreaker
signal here, it is the primary one.
Scope. One project, one namespace. A registered git repository, one package
of a monorepo (under --split, or named in .loci.json), or anything you add
explicitly. Scopes are never merged.
Group. An overlapping label on a scope: me and vendor:<org>, read from
git provenance; client:acme and anything else you assert by hand; and a
monorepo's own id, carried by every package inside it and by the monorepo
itself, so --group <monorepo> reaches the code no package claimed. A scope can
be in several, and the scope set stays flat: grouping never merges scopes or
nests one inside another.
What a group does to a question is its mode, and the mode answers two
different questions. Reached through your working directory, explicit does
nothing, soft (the default) multiplies every outside scope's evidence base by
0.5, and hard confines routing to members and abstains when the best answer is
outside. Named on the command line with --group X, all three modes confine
to X's members β the mode decides only what happens when the best answer is
outside them: hard abstains and says so, soft and explicit answer with the
best member anyway. Membership lives in the scope registry, mode in
groups.json, so a re-scan β which rewrites the registry wholesale β cannot
discard policy. Measured: hard
anchored on cwd fires on questions that name an outside project (12 of 12) and
not on questions carrying only its vocabulary (0 of 24), so in practice --group
and project names drive it rather than where you are standing.
Structure store. What calls what β symbols, files, references, traversals
with file:line citations. Supplied by graphify
through an adapter.
Episode store. What happened and why β README and docs, git commit bodies, docstrings and comment blocks, and any notes you point it at. Stored verbatim, chunked on heading boundaries, redacted before it is written.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/loci)<a href="https://allmcps.com/mcp/loci"><img src="https://allmcps.com/api/badge/loci?style=directory" alt="Loci on AllMCPs" /></a>