The third MCP server was reasonable. The rest were not
MCP was the right answer for giving an agent a project's documentation. As the count grew, the servers themselves became the thing needing maintenance.
Standing up a separate MCP server for every project looked reasonable for the first three. Then the servers started living apart: which one indexed which directory, which had last synced, which carried its own chunking code.
This is the story of how four requirements turned into four design decisions while I was opening the documentation of dozens of projects to my agents. The short answer: the right unit for giving an agent context is not one server per project but one endpoint per project. What built those four is Contextator.
The problem was not the token bill
My first diagnosis was wrong. I read the agent rediscovering the project every session as a line item: it walks the files, reads them, derives the same structure over and over, and that costs money.
The bill is real, but it is not the point. Where discovery ends, the model does not stop. When it cannot find what it is looking for, it keeps producing an answer and fills the gap with an assumption. That happens quietly: there is no line in the output saying “I could not find this file”, there is a plausible answer with nothing under it. You see the token bill at the end of the month; you see the assumption only if somebody reads that code.
The right answer is giving the model the place it should be searching — which is MCP. The protocol is not the hard part.
The hard part is doing the same thing dozens of times
Writing an MCP server for one project is a few hours the first time. The second is copy and paste. The third is still reasonable.
By the fifth you notice what it actually is: every new project means writing the same chunker, the same embedding loop and the same incremental indexer once more — and maintaining all of them separately afterwards. One supports a new file type, another does not. One drops deleted files from its index, another leaves them. Seeing which one synced when means looking at each in turn.
There is a second problem on top: a project’s documentation does not live in one place. Part of it is in the repository, part in the handbook in Notion, part in an Obsidian vault, part in an archive on a shared drive. A tool that indexes one folder waits for a human to consolidate first — which never happens.
Contextator collapses that repetition into one installation. A project is the
sum of its sources: a mounted folder, a git repository (or a single subdirectory
of it), an uploaded archive, an Obsidian vault, a Notion workspace. They meet at
one endpoint, and every source is mounted under its own name, so a document
reads as handbook/install.md.
First decision: isolation is not a setting, it is the address
Each project gets its own URL: /mcp/<project>. A client connected to
/mcp/billing never sees /mcp/mobile.
The alternative was one endpoint with a project argument. In that design
isolation would depend on the agent passing the right one — and confidentiality
that rests on a model’s choice of argument is not confidentiality.
The cost is plain and accepted up front: there is no cross-project search. You cannot ask “which project did I use this pattern in?” in a single question. That is not an omission but the decision itself: if you could, every search would silently span every project.
Second decision: not needing a key in order to work
Embeddings are generated on the machine’s own CPU by default. The reason comes before cost: when a tool built to be queried constantly asks for an API key in order to work at all, it breaks the “runs on your own server” promise at the outset. A few environment variables switch it to OpenAI embeddings, but the default path never leaves the host.
The cost is paid at first start: the model is downloaded and the container takes a few minutes to come up. Later starts take seconds. To keep installation to one command, PostgreSQL and the application sit in the same image — a deliberate departure from one-container-one-process orthodoxy, and that is its price.
Third decision: a search that asks two indexes
A sentence model has no representation of HALYARD_DISPATCH_TIMEOUT. It has a
representation of the words around it. And a good share of what people actually
search documentation for is exactly that: a constant name, an error code, a flag.
So every chunk sits in a full-text index as well as a vector one, and a search takes both lists and fuses their rankings. Ranks rather than scores — because any weighting of scores would have to be re-learnt every time the embedding model changed, while ranks survive a model swap untouched.
The visible cost: the similarity score on a result no longer explains why that result is where it is.
Fourth decision: saying when nothing was found
When nothing clears the relevance floor, the search does not return its least bad hit; it says there is no good match and points to the tool that lists the documents instead.
The same discipline runs through indexing: a scanned PDF with no text layer is refused rather than indexed. Because the failure nobody notices is the one that produces a document: the empty string gets indexed, and the project now holds a document that exists, is listed, matches nothing, and reads as blank when opened.
A limit that was measured and then left alone
To be honest about it, one thing does not work: cross-lingual search. A question asked in Turkish whose answer lives on an English page mostly goes unanswered.
That is not a to-do but a closed decision. Two remedies were tried. Hybrid search first: it recovered identifiers and not one natural-language question. Then a multilingual reranking model: it raised cross-lingual recall but took a search from milliseconds to seconds and cost a number of ordinary questions their rank-one answer. Both were measured, and the measurements are in the repository.
The real fix would be a second translation-trained encoder: four times the download, a second vector column, and a full re-index on every installation. For a documentation server that price was not paid. So the finding sits in the documentation rather than on a roadmap — and if your corpus is genuinely bilingual and that kind of search is essential, it is a reason to pick a different tool.
What this costs in total
What I gained: opening a new project’s documentation to an agent is no longer writing a server, it is adding a source in a dashboard. When sources change, only the changed file is re-embedded and a deleted file’s documents are dropped.
What I pay is just as plain: no cross-project search, a first start that waits on a model download, one container that is not one process, and no searching across two languages. All four came out of a decision, and none of them is reversible — knowing what a tool does not do is as much part of the installation decision as knowing what it does.
The route I took and the reasoning behind the decisions are in the Labs entry; the product itself is in the portfolio. The tool is open source and runs on your own server.
Experiments on this topic
Serving the documentation of dozens of projects from one server
A multi-tenant documentation server that gives every project its own MCP endpoint and keeps the documents on your own machine.
What it does today
Makes a project's folder, git repository, Obsidian vault or Notion workspace searchable behind a single MCP endpoint, so an agent reaches the documentation without the data going to a third party. Anyone who can run Docker can host it and manage it from the dashboard.
Reaching production data without handing out the password
A self-hosted portal that puts ad-hoc production SQL behind approval, masking and an immutable trail; it became the QueryProxy product.
What it does today
Runs a developer's SQL against production through an approval step rather than directly; results are masked as they are written to disk and every request lands in an immutable record. Teams where production access sits with one person can run it today.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.