Build1 publisher3 min readPublished
Thirty MCP tools become three: the reliability bug is in your schema, not the model
A dev.to writeup argues large agent tool surfaces should collapse into a few domain routers with an action discriminator. The pattern is sound; the post ships no measurements.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- An article on dev.to titled "From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface", by serifcolakel, describes a pattern the author has been using to reduce large MCP/tool surfaces into a small number of domain-oriented tools while preserving the underlying capabilities, proposing 3-5 domain tools in place of 30 MCP tools.
- The author notes that as an AI agent grows it is common to connect it to Jira, GitLab, Confluence, Sentry, Elasticsearch, Jaeger, databases, monitoring systems, internal APIs, deployment platforms and dozens of other services, and that it is easy to end up with 30, 50 or even hundreds of tools.
- The author argues that a large tool count first looks like a capability problem but is actually a tool-surface problem: the agent does not necessarily need fewer capabilities, it needs fewer top-level tools. The core idea is stated as "Consolidate the tool surface, not the capabilities."
- The article states that an MCP server is not only an execution interface but also part of the model's context, and that when an agent connects to an MCP server the model generally needs to understand tool names, descriptions, input schemas, parameters, enums, constraints and sometimes additional metadata.
- The author writes that for an agent connected to 40 tools, even if each tool has a relatively small schema, the aggregate context can become significant.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A developer writeup on dev.to sets out a pattern for shrinking sprawling agent tool surfaces: keep every capability, but expose three to five domain-oriented tools that route internally on an `action` field instead of thirty separately named ones [1][8]. The author's framing is the useful part, because the symptom looks like missing or confused capability and is actually a surface problem: the agent does not need fewer capabilities, it needs fewer top-level tools [4].
The cost has two components. First, context: an MCP server is not only an execution interface, it is also part of the model's prompt, since the model has to see tool names, descriptions, input schemas, parameters, enums and constraints [5]. Connect forty tools and even modest per-tool schemas aggregate into something significant [6]. Second, selection: the author points out that operations like `jira_search_issues`, `jira_search_projects`, `jira_get_issue`, `jira_get_issue_comments`, `jira_get_issue_transitions` and `jira_get_issue_worklogs` all live in one conceptual domain, and forcing the model to treat each as an independent top-level capability widens the decision space for no gain [7].
The fix is an intermediate discriminator. Rather than `jira_search`, you expose `jira`, called as `jira({ action: "search", ... })`, and the tool becomes a small router to the same handlers [8]. The author's formulation is worth keeping: the tool is the external interface, the action is the capability [9]. Implementation is a tagged union, in this case Zod's `z.discriminatedUnion("action", [...])` [13]. The database example is the concrete one: thirteen listed operations, from `listTables` and `getTableSchema` through `executeQuery`, `profileColumn` and `compareSchemas`, collapse into `db_tables`, `db_query` and `db_advanced` [12], a 77 percent cut in top-level tool count [15]. Applied to the headline case, three to five domain tools in place of thirty leaves 10 to 17 percent of the original surface [16].
The author is explicit that this is not arithmetic. Splitting thirty operations into ten-per-tool buckets misses the point; grouping should follow semantic domains, such as `jira` covering search, issue, comment, transition and project, or an `observability` tool covering search, trace, log and error [11][10]. There is no universal number, only the smallest surface that still holds clear semantic boundaries [14].
Two things to hold onto before you refactor. The portion of the article available carries no token counts, no latency numbers and no before-and-after task accuracy comparison [17], so treat the savings as structural rather than demonstrated. And the discriminator does not delete the selection problem, it relocates it: the action enum still has to be described somewhere in the schema, so the win comes from eliminating per-tool boilerplate and giving the model one obvious door per domain, not from hiding the branch.
What to watch in your own stack. Per-tool telemetry collapses when every Jira call is logged as `jira`, so log the action field before you migrate, not after. Check whether your client's approval or allowlist mechanism operates at tool granularity; if it does, a router is a coarser permission boundary than what you have now, and a read-only agent that could previously be handed `jira_search` alone now gets `createIssue` in the same union. And watch where invalid calls surface: with a tagged union they fail at schema parse rather than at a missing tool name, which is a better error only if you pass the parse failure back to the model in a form it can act on.