
An agentic RAG served as thirteen MCP tools
When you connect a model to your data, the first attempt is always the same: stuff the context into the prompt. A dump of the documentation, the latest table rows, the summary you generated last night. It works with a small corpus and stops working as soon as it grows, because context is finite and because the model doesn't know what it's missing.
The alternative is not to give it content, but the ability to go find it. That's an MCP server: a process that exposes tools — named functions with typed arguments and a description — so the agent decides which one to call and with what. The protocol standardizes it; the interesting part is not the protocol, but what you decide to expose.
A tool is not an endpoint
The difference looks like a nuance and it isn't. An endpoint is called by a program that already knows what it wants; a tool is chosen by a model based on its description. That changes the design: the name matters, the description matters, and the arguments have to be obvious to someone who hasn't read your code.
It also changes what you do NOT expose. Giving direct access to the database seems like the most flexible option, and it's precisely what works worst.
A well-defined tool is a decision made in advance: this is the order in which things are searched, this is the limit, this is how sources are cited. The model doesn't have to reinvent it on every call.
The portal's thirteen
The Zetesis Portal MCP server exposes thirteen tools over the indexed content. They are not thirteen ways of doing the same thing: they cover three distinct moments of a query.
| Moment | Tools |
|---|---|
| Getting oriented | list_retrieval_profiles, get_taxonomy_tree, get_filter_criteria, get_collection_stats |
| Searching | search_collections, get_post_summaries, get_book_toc |
| Reading | get_chunks_by_ids, get_chunks_by_parent |
| Synthesizing | summarize_document, extract_claims, compare_perspectives, synthesize_comparison |
The getting-oriented group is the most underestimated one. An agent that starts by asking what taxonomies exist hits the mark far more often than one that fires off a blind search, because it can filter by author instead of putting the author's name in the query — which never appears in a corpus of verbatim quotes, because nobody cites themselves by their last name.
- list_retrieval_profileswhat I can read
- get_taxonomy_treehow filtering works
- search_collectionsconcept, not words
- get_chunks_by_idsthe full text
- extract_claimswith its source
What search_collections does under the hood
Of the thirteen, the most used one is search, and it's worth knowing what it does because it has three modes and they fail in different places.
The lexical one searches for the words you wrote. It's exact and explainable: if a result shows up, you know why. Its limit is that it joins terms with AND — a four-word query requires all four in the same fragment — and it knows nothing about synonyms.
The semantic one searches by meaning through embeddings. It finds what you meant even if not a single word matches, and in return it doesn't distinguish well between similar and correct.
| Mode | Gets it right when | Fails when |
|---|---|---|
| Lexical | You know the exact term | The query is long or there are synonyms |
| Semantic | You describe the idea | You have to tell similar things apart |
| Hybrid | You don't know which of the two cases it is | You have to explain why something came up |
The result count lies in vector mode
It's the trap that has bitten me most often. In the vector modes, recall is not set by the page you request: the engine retrieves a fixed number of neighbors and paginates over them. The total it returns is bounded by that number, not by what's in the corpus.
So asking the agent “how much content do I have on this?” in semantic mode returns something that looks like data and isn't. To actually count, you have to repeat the query in lexical mode and look at that total.
This is also what separates a tool from an endpoint: the description of search_collections has to teach the model to query by concept and filter by taxonomy, because an agent that puts the author's name in the query text finds nothing — in a corpus of quotes nobody names themselves by their last name.
The weight between the lexical and semantic parts in hybrid mode I set by intuition and manual testing, not by measurement. I know it works better than either one alone; I don't know what the optimum is or whether it changes with the corpus.
Why the semantic mode fails silently
There is a concrete reason behind vector mode leaving out things that are there, and it's not the corpus: it's geometric. In high-dimensional spaces, points appear that end up close to almost everything else. They're called hubs.
A generic entry ends up placed in the middle of the cloud and wins comparisons it shouldn't win, not because it fits better, but because it's close to everything. If the agent sorts by cosine and keeps the top results, the long tail never shows up.
The usual correction penalizes points that are neighbors of too many things. It reorders well, but it distorts the scale, so its number stops meaning anything on its own: to decide whether a result is reliable you have to look at the raw similarity, not the corrected one.
And confidence is no use for that either. A softmax over the finalists measures how much the first one stands out over the rest, not whether the first one is good. Five bad, similar candidates give low confidence; five bad ones where one stands out give high confidence. Neither tells you whether you got it right.
Chunking without losing the header
A long document doesn't fit in a single embedding call, and the way you reassemble it changes what the tool returns. Averaging the chunks equally treats the first page the same as the signature list at the end.
With decreasing weight — the first chunk weighs more than the second, and so on — the resulting vector looks more like what the document says it is. The header of a set of minutes tells you far more about its nature than its last page.
Profiles: what each agent can read
A tool server without scope control is a security problem with good documentation. On the portal, each agent is assigned retrieval profiles that define the hard scope of what it can read: which collections, which authors, which folders.
The detail that matters is the direction. The filters the agent passes on each call can only narrow within that scope, never widen it. If it asks for an author its profile doesn't cover, the request doesn't fail silently or return extra results: the filter is discarded and the response carries a notice explaining why. The agent can correct; what it can't do is step outside.
Same server, different scope
On top of that, search tokens are issued per user and carry their own profiles. Two people from different organizations point at the same server and see different corpora, without deploying anything per client.
That opens a possibility I hadn't foreseen at the start: any external MCP client — Claude Desktop, Cursor — can query the portal with those credentials. Search stops living only inside the website and moves to where people already work.
The same pattern, another domain
In Konect, the MCP server doesn't expose documents but search, facets, aggregations, and charts over analyzed conversations. An embedded chat uses them to answer questions about the data. The entire domain changes and the shape stays: tools with a defined scope instead of a dump into the prompt.
And in agentic development consulting they are custom-built servers against Loki, Grafana, or GitHub. The agent lives inside the system, with the same tools I would have.
What's still open
Thirteen tools are already enough for the model to pick the wrong one. I have no measure of how often it grabs the wrong one, and that's the next thing I want to instrument: not the quality of the answer, but the quality of the choice.
I'm also not sure where the ceiling is. I suspect the limit is not how many tools you expose but how much they overlap, and I don't know how to measure that yet.
Payload plugins and agent runtime, published as @zetesis/ packages. The MCP server lives here.
TypeScriptKeep reading

Zetesis Portal and PayloadAgents: how we built our SaaS

Huygens: a SurrealDB graph as memory for agents

Agentic development consulting

One deployment per customer or one space per customer

Search and agents over your own documentation
