Writing · 2026-08-30
Designing an MCP surface under the attention ceiling
My MCP server sat at twice the tool count where agent selection accuracy starts to fall apart, so I rebuilt the surface as thin routers and paid for it with structured errors.
The agent had eight ways to read the same thing, and it kept choosing the fourth-best one.
That was the symptom I watched for weeks. The cause turned out to be a number. Published work on agent tool use shows selection accuracy degrading past roughly 30 tools. My MCP server had grown well past it. More than twice the ceiling, and every tool definition rode along in every turn, so the list was also a standing tax on the context window before the agent had done anything at all.
I took it back under the ceiling, with room to spare.
The method was not deletion. Cutting capability to hit a number gives you a surface that looks disciplined and cannot do the job, and the work comes back as five bespoke scripts nobody reviews. What I looked for instead was clusters of tools with the same shape: same kind of input, same kind of output, same intent, differing only in which slice of the graph they touched. Eight read operations over an ontology graph are eight names for one act. I collapsed each cluster into a thin-surface router with a typed op value. Nine routers. Thirteen tools stayed as singletons because they had no honest sibling to merge with. That is a surface cut to a fraction of its size, under the ceiling with room to grow.
The op values underneath still number in the hundreds, and that is fine, because the agent never faces an op list until it has already chosen a router. A hundred-plus typed operations priced inside one tool's schema is a lookup. The same hundred priced as separate tools would sit in every turn's payload and compete with each other for attention. The ceiling is about attention, not about how much the server can do.
The real gain was not the count. It was the reshaping. The agent's mental model went from "which of these eight read tools?" to a small set of intent layers: ask the graph, search inside a scope, read an artifact, write an artifact, run a gate. Choosing among five intents is a different cognitive task from choosing among sixty-three names, and it is the task models are good at.
Now the part that is usually left out of these stories. Consolidation costs something real.
A named tool is self-documenting in a way an op string is not. find_service_dependencies announces itself in its name and its schema, and the model gets that for free at selection time. query_intelligence with op: "service_dependencies" does not, or at least not as strongly. Worse, the failure changes character. A call that used to be impossible, because the tool did not exist, is now a runtime error, because the op is wrong. I moved a class of mistakes from selection time to execution time. On a naive server that trade is a downgrade: the agent gets back "invalid op", improvises a new guess, and burns three turns wandering.
The error envelope is what pays that back, and it is not a bonus feature. It is the thing that carries the weight the per-tool schema used to carry. Every failure returns the same four fields:
{
"error_code": "UNKNOWN_OP",
"retryable": true,
"suggested_action": "call query_intelligence with op=\"list_ops\" to get valid ops for this router",
"cited_rule": "mcp.router.ops-are-closed-set"
}
Each field removes a decision the model would otherwise improvise. retryable says whether trying again can possibly help, which kills the retry loop that eats a budget on a permanently bad call. suggested_action is the next call, written out, so recovery is a lookup rather than a guess. cited_rule names the rule that was broken, so the correction is anchored to something stable the agent can point at and a human can go read. error_code is machine-stable, so my own hooks and evals can key off it without parsing prose.
The result is that a wrong op self-corrects deterministically, usually in one turn. That is what makes the routers survivable. Take the envelope away and the consolidation is a bad idea.
Every new capability now runs the same decision tree. If an existing router already owns the shape, it becomes an op on that router. If the shape is new but likely to have siblings, it starts a router with a single op. If it is genuinely a one-off, it ships as a singleton, and I write down why it has no siblings, because that note is what a future me will argue with. And if adding it pushes the count back toward 30, something else has to leave first.
The last rule is the one with teeth, and it is the reason I think of the tool list as an attention budget rather than an API surface. An API grows when someone needs something. A budget means a new tool is priced in the accuracy of every other tool, on every turn, forever. That reframing changed more of my design decisions than the refactor itself did.
Now the caveats. I did not measure selection accuracy before and after, and I want that on the record. I moved under a published ceiling on the strength of that research plus my own session transcripts, which is judgment, not evidence. This runs in one organization: my engineering team at Genea, a physical-security company, uses it daily, and no outside team has stress-tested the shape. I can show you the surface and the reasoning. I cannot show you a controlled comparison.
The open problem: I am under someone else's published ceiling, and I still have no idea where my own sits.