{"slug": "from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface", "title": "From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface", "summary": "A developer describes a pattern for reducing large MCP tool surfaces into a few domain-oriented tools by using an 'action' discriminator, which preserves capabilities while shrinking the model's context and decision space. The approach consolidates tools like Jira, GitLab, and Confluence into 3-5 domain tools, improving token efficiency and agent performance.", "body_md": "Modern agentic applications rarely suffer from a lack of tools.\n\nThey suffer from **too many of them**.\n\nAs 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.\n\nEach integration can expose many operations:\n\n```\nJira\n├── searchIssues\n├── getIssue\n├── createIssue\n├── updateIssue\n├── addComment\n├── transitionIssue\n├── getTransitions\n├── assignIssue\n└── ...\n\nGitLab\n├── listProjects\n├── getProject\n├── listIssues\n├── createIssue\n├── updateIssue\n├── listMergeRequests\n├── getMergeRequest\n├── createComment\n└── ...\n\nConfluence\n├── searchPages\n├── getPage\n├── createPage\n├── updatePage\n└── ...\n```\n\nIt is easy to end up with 30, 50, or even hundreds of tools.\n\nAt first, this looks like a capability problem.\n\nIt is actually a **tool-surface problem**.\n\nThe agent does not necessarily need fewer capabilities.\n\nIt needs fewer **top-level tools**.\n\nThis article describes a pattern I have been using to reduce large MCP/tool surfaces into a small number of domain-oriented tools while preserving the underlying capabilities.\n\nThe core idea is simple:\n\nConsolidate the tool surface, not the capabilities.\n\nInstead of exposing:\n\n```\n30 MCP tools\n```\n\nwe can expose:\n\n```\n3–5 domain tools\n```\n\nand use an `action`\n\ndiscriminator to route requests internally.\n\nFor example:\n\n```\njira_search\njira_get_issue\njira_create_issue\njira_update_issue\njira_add_comment\njira_transition_issue\n...\n```\n\ncan become:\n\n```\njira({\n  action: \"search\",\n  ...\n})\n\njira({\n  action: \"getIssue\",\n  ...\n})\n\njira({\n  action: \"createIssue\",\n  ...\n})\n```\n\nThe backend still has all the original capabilities.\n\nThe model simply sees a much smaller tool surface.\n\nAn MCP server is not only an execution interface.\n\nIt is also part of the model's context.\n\nWhen an agent connects to an MCP server, the model generally needs to understand:\n\nImagine an agent connected to 40 tools.\n\nEven if each tool has a relatively small schema, the aggregate context can become significant.\n\nMore importantly, the model now has a larger decision space:\n\n```\nUser request\n     │\n     ▼\nWhich tool?\n     │\n ┌───┼────┬────┬────┬────┐\n ▼   ▼    ▼    ▼    ▼    ▼\nT1   T2   T3   T4   T5   ...\n```\n\nThe model has to distinguish between many semantically related operations.\n\nFor example:\n\n```\njira_search_issues\njira_search_projects\njira_get_issue\njira_get_issue_comments\njira_get_issue_transitions\njira_get_issue_worklogs\n```\n\nare all part of the same conceptual domain.\n\nThere is little value in forcing the model to treat every operation as a completely independent top-level capability.\n\nThe pattern is to introduce an intermediate discriminator:\n\n```\n{\n  \"action\": \"search\",\n  \"query\": \"authentication bug\"\n}\n```\n\nInstead of:\n\n```\njira_search\n```\n\nwe expose:\n\n```\njira\n```\n\nThe tool becomes a small router.\n\nConceptually:\n\n```\n                     jira\n                      │\n                 action field\n                      │\n       ┌──────────────┼──────────────┐\n       ▼              ▼              ▼\n    search         getIssue       createIssue\n       │              │              │\n       ▼              ▼              ▼\n searchHandler   issueHandler   createHandler\n```\n\nThe important point is that **the tool is not the capability**.\n\nThe tool is the external interface.\n\nThe action represents the capability.\n\nThis gives us:\n\n```\n30 capabilities\n       ↓\n3 domain-oriented tools\n```\n\nwithout throwing away functionality.\n\nThe easiest mistake is to think:\n\n\"I have 30 tools, so I will put 10 operations into each tool.\"\n\nThat is not the goal.\n\nGrouping should follow **semantic domains**.\n\nFor example:\n\n```\njira\n├── search\n├── issue\n├── comment\n├── transition\n└── project\n\ngitlab\n├── project\n├── issue\n├── mergeRequest\n└── pipeline\n\nobservability\n├── search\n├── trace\n├── log\n└── error\n```\n\nThe exact grouping depends on the integration.\n\nFor a database:\n\n```\ndb_tables\ndb_query\ndb_advanced\n```\n\nmight make sense.\n\nFor Jira:\n\n```\njira_issues\njira_projects\njira_search\n```\n\nmay be better.\n\nFor GitLab:\n\n```\ngitlab_repository\ngitlab_issues\ngitlab_mergeRequests\n```\n\nmay be more natural.\n\nThere is no universal number.\n\nThe goal is to find the smallest tool surface that still preserves clear semantic boundaries.\n\nA database integration is a useful example because database APIs can easily expose a large number of operations.\n\nInstead of:\n\n```\nlistTables\ngetTableSchema\ngetSampleData\ngetTableSize\nexecuteQuery\ngetDatabaseInfo\nlistRelationships\ngetIndexes\nprofileColumn\nsearchSchema\nlistProcedures\ngetTriggers\ncompareSchemas\n```\n\nwe can expose:\n\n```\ndb_tables\ndb_query\ndb_advanced\n```\n\nThe first tool can use:\n\n``` js\nconst tablesSchema = z.discriminatedUnion(\"action\", [\n  z.object({\n    action: z.literal(\"list\"),\n    schema: schemaField,\n  }),\n\n  z.object({\n    action: z.literal(\"schema\"),\n    tableName: z.string(),\n    schema: schemaField,\n  }),\n\n  z.object({\n    action: z.literal(\"sampleData\"),\n    tableName: z.string(),\n    schema: schemaField,\n    rowCount: z.number().optional().default(10),\n  }),\n\n  z.object({\n    action: z.literal(\"size\"),\n    tableName: z.string(),\n    schema: schemaField,\n  }),\n]);\n```\n\nThe model sees one tool:\n\n```\ndb_tables\n```\n\nwith an explicit action space:\n\n```\nlist\nschema\nsampleData\nsize\n```\n\nThe runtime still has four separate handlers.\n\n```\nswitch (args.action) {\n  case \"list\":\n    return handleListTables(args.schema);\n\n  case \"schema\":\n    return handleGetTableSchema(\n      args.tableName,\n      args.schema\n    );\n\n  case \"sampleData\":\n    return handleGetSampleData(\n      args.tableName,\n      args.schema,\n      args.rowCount\n    );\n\n  case \"size\":\n    return handleGetTableSize(\n      args.tableName,\n      args.schema\n    );\n}\n```\n\nThis distinction is important:\n\nConsolidation happens at the MCP interface, not inside the business logic.\n\nThe internal handlers remain independently testable and maintainable.\n\nFor TypeScript applications, `z.discriminatedUnion()`\n\nprovides a clean way to express this pattern.\n\nFor example:\n\n``` js\nconst querySchema = z.discriminatedUnion(\"action\", [\n  z.object({\n    action: z.literal(\"execute\"),\n    query: z.string(),\n    params: z.record(z.string()).optional(),\n    limit: z.number().optional().default(100),\n  }),\n\n  z.object({\n    action: z.literal(\"info\"),\n  }),\n]);\n```\n\nThe type can then be inferred directly:\n\n```\ntype QueryInput = z.infer<typeof querySchema>;\n```\n\nThis gives us three useful properties:\n\nThe resulting architecture becomes:\n\n```\n             MCP Tool\n                │\n                ▼\n        Discriminated Union\n                │\n          ┌─────┴─────┐\n          │  action   │\n          └─────┬─────┘\n                │\n       ┌────────┼────────┐\n       ▼        ▼        ▼\n    Handler A Handler B Handler C\n       │        │        │\n       └────────┼────────┘\n                ▼\n             Backend\n```\n\nThis is much more predictable than asking the model to navigate dozens of unrelated top-level tools.\n\nThis is probably the most important conceptual distinction.\n\nSuppose we have:\n\n```\n3 tools\n13 actions\n```\n\nThat does not mean we lost 10 capabilities.\n\nWe have:\n\n```\n3 external interfaces\n13 internal capabilities\n```\n\nTherefore:\n\n```\nTool count ≠ capability count\n```\n\nThis distinction becomes increasingly important as agent systems grow.\n\nA large organization may have:\n\n```\nJira             20 operations\nGitLab            25 operations\nConfluence        15 operations\nSentry             8 operations\nELK               10 operations\nJaeger             6 operations\nDatabase           20 operations\n```\n\nThat can easily become 100+ operations.\n\nExposing all of them directly to the model creates an unnecessarily large tool surface.\n\nInstead, we can build:\n\n```\njira\ngitlab\nconfluence\nsentry\nobservability\ndatabase\n```\n\nand keep the underlying operation count unchanged.\n\nThe primary optimization is reducing the amount of tool metadata the model needs to process.\n\nInstead of presenting:\n\n```\nTool 1\nTool 2\nTool 3\nTool 4\n...\nTool 30\n```\n\nwe present:\n\n```\nTool A\nTool B\nTool C\n```\n\nThis can reduce:\n\nHowever, this should not be described as a guaranteed linear cost reduction.\n\nIf we transform:\n\n```\n30 tools → 3 tools\n```\n\nit does not necessarily mean:\n\n```\n90% lower token cost\n```\n\nbecause the consolidated schemas themselves can become larger.\n\nThe correct statement is:\n\nTool consolidation can significantly reduce the tool metadata exposed to the model, but the actual token and latency savings depend on schema size, descriptions, provider behavior, and how the agent framework handles tools.\n\nThis distinction matters.\n\nThere is an important trade-off.\n\nConsider a tool with 40 actions:\n\n```\nenterprise({\n  action: ...\n})\n```\n\nwith actions such as:\n\n```\ncreateCustomer\ndeleteCustomer\nsearchInvoice\nrotateCredentials\ndeployService\ncreateRepository\ngetTrace\nsearchLogs\n...\n```\n\nThis is technically possible.\n\nIt is also terrible design.\n\nThe model now has one enormous schema.\n\nThe problem has simply moved from:\n\n```\n30 tools\n```\n\nto:\n\n```\n1 giant tool\n```\n\nThe correct approach is **domain-oriented consolidation**.\n\nFor example:\n\n```\ncustomer\nbilling\nrepository\nobservability\ndeployment\n```\n\nThe ideal number might be 5 rather than 1.\n\nTherefore:\n\nMinimize the tool surface, but do not minimize it blindly.\n\nOnce multiple capabilities share one tool, the `action`\n\nfield becomes extremely important.\n\nBad:\n\n```\naction: z.string()\n```\n\nBetter:\n\n```\naction: z.enum([\n  \"search\",\n  \"get\",\n  \"create\",\n  \"update\"\n])\n```\n\nBest, when actions have different parameters:\n\n```\nz.discriminatedUnion(\"action\", [\n  searchSchema,\n  getSchema,\n  createSchema,\n  updateSchema,\n])\n```\n\nNow the action and its parameters form a strongly typed relationship.\n\nFor example:\n\n```\naction = \"search\"\n→ query required\n\naction = \"get\"\n→ issueId required\n\naction = \"create\"\n→ title + description required\n```\n\nThis is much more expressive than one generic schema with dozens of optional fields.\n\nIn practice, there is another layer of complexity.\n\nMCP tool schemas ultimately need to be represented as JSON Schema.\n\nA Zod discriminated union can produce a schema based on:\n\n```\n{\n  \"anyOf\": [...]\n}\n```\n\nor:\n\n```\n{\n  \"oneOf\": [...]\n}\n```\n\ndepending on the converter.\n\nSome MCP schema handling paths expect an object at the root.\n\nThat creates a compatibility problem.\n\nIn one implementation, the MCP SDK's Zod compatibility layer expected an object shape and did not naturally handle the discriminated union in the same way.\n\nA compatibility layer can therefore normalize the generated schema.\n\nConceptually:\n\n```\nZod discriminated union\n          │\n          ▼\n     JSON Schema\n          │\n          ▼\n   MCP normalization\n          │\n          ▼\nMCP-compatible object schema\n```\n\nThe important architectural principle is:\n\nAdapt the schema exposed to the model without weakening the runtime validator.\n\nThe original Zod schema should remain the source of truth for validation.\n\nThis leads to an important distinction.\n\nThere are effectively two concerns:\n\n```\nLLM-facing representation\n        │\n        ▼\nTool selection and argument generation\n\nRuntime representation\n        │\n        ▼\nValidation and execution\n```\n\nThe LLM-facing schema needs to be:\n\nThe runtime schema needs to be:\n\nThese do not necessarily have to be identical.\n\nFor example, a flattened object representation may make action choices easier for a model to see, while the original discriminated union remains responsible for strict validation.\n\nThis is a useful general principle for agent infrastructure:\n\nOptimize schemas for model consumption, but never use model-facing schemas as the only security boundary.\n\nWhen there are 30 tools, each tool can have a very specific description.\n\nWhen there are 3 tools, the description needs to explain the action space.\n\nFor example:\n\n```\nUnified tool for advanced database analysis.\n\nACTIONS:\n\nrelationships\n  List foreign key relationships.\n\nindexes\n  Get indexes for a table.\n\nprofileColumn\n  Analyze nullability, cardinality and top values.\n\nsearchSchema\n  Search tables and columns.\n\nlistProcedures\n  List stored procedures.\n\ntriggers\n  Get triggers.\n\ncompareSchemas\n  Compare two table definitions.\n```\n\nThis is not documentation only.\n\nIt is part of the model's routing interface.\n\nA useful mental model is:\n\n```\nTool name\n    +\nDescription\n    +\nAction enum\n    +\nParameter schema\n        ↓\nAgent routing signal\n```\n\nTherefore, when consolidating tools, descriptions should become more intentional, not less.\n\nThis approach becomes much more valuable when applied across an entire engineering environment.\n\nInstead of:\n\n```\nsearchIssues\ngetIssue\ncreateIssue\nupdateIssue\naddComment\ntransitionIssue\nassignIssue\ngetTransitions\n```\n\nuse:\n\n```\njira_issues\n```\n\nwith:\n\n```\nsearch\nget\ncreate\nupdate\ncomment\ntransition\nassign\n```\n\nInstead of exposing every repository, issue, merge request and pipeline operation:\n\n```\ngitlab_repository\ngitlab_issues\ngitlab_mergeRequests\ngitlab_pipelines\n```\n\nEach can expose a focused action set.\n\nInstead of:\n\n```\nsearchPages\ngetPage\ncreatePage\nupdatePage\ndeletePage\ngetChildren\n```\n\nuse:\n\n```\nwiki\n```\n\nwith:\n\n```\nsearch\nget\ncreate\nupdate\nchildren\n```\n\nPotentially:\n\n```\nsentry\n```\n\nwith:\n\n```\nsearchIssues\ngetIssue\nevents\nreleases\nprojects\n```\n\nPotentially:\n\n```\nlogs\n```\n\nwith:\n\n```\nsearch\naggregate\nfields\nindices\n```\n\nPotentially:\n\n```\ntracing\n```\n\nwith:\n\n```\nsearch\ngetTrace\nservices\noperations\n```\n\nThe important point is not the exact names.\n\nIt is the architectural pattern.\n\nOne of the most practical aspects of this approach is that we do not necessarily need to rewrite every integration.\n\nThere are already MCP implementations for many popular systems.\n\nThe problem is that their exposed tool surface may not be optimal for a specific agent.\n\nInstead of:\n\n```\nAgent\n  │\n  ├── 20 Jira tools\n  ├── 20 GitLab tools\n  └── 15 Confluence tools\n```\n\nwe can introduce a customized layer:\n\n```\n                 Agent\n                   │\n             Custom MCP Layer\n                   │\n        ┌──────────┼──────────┐\n        ▼          ▼          ▼\n      Jira       GitLab    Confluence\n        │          │          │\n     existing   existing   existing\n       APIs       MCP        MCP\n```\n\nThe custom layer becomes an **agent-oriented facade**.\n\nThis is particularly useful when the existing integration is technically correct but exposes too many low-level operations.\n\nThis suggests a broader architectural pattern.\n\nTraditional integration:\n\n```\nAgent → MCP → Service\n```\n\nCustomized integration:\n\n```\nAgent\n  ↓\nAgent-oriented MCP facade\n  ↓\nService-specific MCP/API\n  ↓\nService\n```\n\nThe facade can provide:\n\nThe MCP layer therefore becomes more than a transport.\n\nIt becomes an **agent interface layer**.\n\nThere is an important security implication.\n\nIf we consolidate:\n\n```\njira_create\njira_update\njira_delete\n```\n\ninto:\n\n```\njira({\n  action: ...\n})\n```\n\nwe should not simply grant:\n\n```\npermission: jira\n```\n\nand assume all operations are equivalent.\n\nPermissions should still be evaluated at the action level.\n\nFor example:\n\n```\njira.search       → allowed\njira.get          → allowed\njira.create       → allowed\njira.update       → approval required\njira.delete       → denied\n```\n\nThe unified tool is only an interface optimization.\n\nIt must not become a security boundary that accidentally grants excessive privileges.\n\nWith consolidated tools, logging only the tool name is no longer sufficient.\n\nBad:\n\n```\ntool = jira\n```\n\nBetter:\n\n```\n{\n  \"tool\": \"jira\",\n  \"action\": \"search\",\n  \"duration_ms\": 142\n}\n```\n\nEven better:\n\n```\n{\n  \"tool\": \"jira\",\n  \"action\": \"search\",\n  \"status\": \"success\",\n  \"duration_ms\": 142,\n  \"request_id\": \"...\",\n  \"agent_id\": \"...\",\n  \"user_id\": \"...\"\n}\n```\n\nThe fundamental execution unit becomes:\n\n```\ntool + action\n```\n\nrather than just:\n\n```\ntool\n```\n\nThis is particularly important for debugging agent behavior.\n\nTool consolidation should happen at the boundary.\n\nDo not create a 2,000-line function like:\n\n```\nasync function jira(args) {\n  // everything\n}\n```\n\nInstead:\n\n```\nasync function jira(args: JiraInput) {\n  switch (args.action) {\n    case \"search\":\n      return searchIssues(args);\n\n    case \"get\":\n      return getIssue(args);\n\n    case \"create\":\n      return createIssue(args);\n\n    case \"update\":\n      return updateIssue(args);\n  }\n}\n```\n\nEach handler remains independently testable:\n\n```\njira()\n  │\n  ├── searchIssues()\n  ├── getIssue()\n  ├── createIssue()\n  └── updateIssue()\n```\n\nThis preserves maintainability while reducing the external tool surface.\n\nA practical rule for deciding whether two operations should be consolidated is:\n\nWould a human developer naturally describe these operations as belonging to the same capability?\n\nFor example:\n\n```\nJira search\nJira get issue\nJira update issue\n```\n\nYes.\n\nBut:\n\n```\nJira update issue\nElasticsearch search logs\nCreate AWS infrastructure\n```\n\nNo.\n\nAnother useful test:\n\nWould the same context usually be required to decide between these operations?\n\nIf yes, consolidation is often beneficial.\n\nIf no, keep them separate.\n\nThere are cases where separate tools are better.\n\n```\ndatabase\npayments\ninfrastructure\nemail\n```\n\nshould not become:\n\n```\nenterprise({\n  action: ...\n})\n```\n\nIf a single tool has 50 actions, the schema itself may become expensive and confusing.\n\nOperations such as:\n\n```\ndeleteProductionData\nrotateCredentials\ndeployProduction\n```\n\nmay deserve separate permission boundaries even if they belong to the same domain.\n\nRead-only operations and destructive operations can sometimes benefit from separate interfaces:\n\n```\njira_read\njira_write\n```\n\ninstead of one enormous tool.\n\nAgain, there is no universal rule.\n\nThe goal is not:\n\n```\n30 → 1\n```\n\nThe goal is:\n\n```\n30 → smallest useful semantic surface\n```\n\nFor one system that might be:\n\n```\n30 → 3\n```\n\nFor another:\n\n```\n30 → 7\n```\n\nFor another:\n\n```\n30 → 12\n```\n\nThe optimization target is:\n\n```\nminimize unnecessary tool metadata\n```\n\nwhile preserving:\n\n```\nsemantic clarity\nvalidation\nsecurity\nmaintainability\n```\n\nA scalable implementation can look like this:\n\n```\n                        Agent\n                          │\n                          ▼\n                ┌──────────────────┐\n                │  Agent MCP Layer │\n                └────────┬─────────┘\n                         │\n          ┌──────────────┼──────────────┐\n          ▼              ▼              ▼\n        jira           gitlab          wiki\n          │              │              │\n     ┌────┼────┐    ┌────┼────┐    ┌────┼────┐\n     ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼\n  search get create issues MR pipeline search get update\n          │              │              │\n          └──────────────┼──────────────┘\n                         ▼\n                 Existing APIs/MCPs\n```\n\nThe agent sees a compact interface.\n\nThe integration layer retains the full capability set.\n\nMCP is often treated as:\n\n\"Expose every API operation as a tool.\"\n\nThat is a reasonable starting point.\n\nFor production agent systems, however, a better question is:\n\nWhat interface should the model see?\n\nThose are not necessarily the same thing.\n\nA REST API might expose:\n\n```\nGET /issues\nGET /issues/:id\nPOST /issues\nPATCH /issues/:id\nPOST /issues/:id/comments\nPOST /issues/:id/transition\n```\n\nWe do not have to expose every HTTP endpoint as a separate model-facing tool.\n\nThe MCP layer can become a semantic abstraction over those endpoints.\n\nThis is similar to designing an API specifically for a consumer rather than simply mirroring an underlying database or service API.\n\nTraditional API design asks:\n\nWhat endpoints does the system provide?\n\nAgent interface design asks:\n\nWhat decisions does the model need to make?\n\nThese are different questions.\n\nA human developer may prefer:\n\n```\ngetIssue\ngetIssueComments\ngetIssueTransitions\ngetIssueWorklogs\n```\n\nbecause each API operation is explicit.\n\nAn agent may perform better with:\n\n```\njira_issue({\n  action: \"get\",\n  ...\n})\n```\n\nand:\n\n```\njira_issue({\n  action: \"comments\",\n  ...\n})\n```\n\nbecause the model first identifies the domain and then chooses the operation.\n\nThis creates a two-level routing model:\n\n```\nDomain\n  ↓\nAction\n  ↓\nArguments\n```\n\nrather than:\n\n```\nOne large global tool-selection problem\n```\n\nThe pattern can be generalized with TypeScript:\n\n``` js\nconst toolSchema = z.discriminatedUnion(\"action\", [\n  searchSchema,\n  getSchema,\n  createSchema,\n  updateSchema,\n]);\n\nserver.registerTool(\n  \"jira\",\n  {\n    description: \"...\",\n    inputSchema: toolSchema,\n  },\n  async (args) => {\n    switch (args.action) {\n      case \"search\":\n        return search(args);\n\n      case \"get\":\n        return get(args);\n\n      case \"create\":\n        return create(args);\n\n      case \"update\":\n        return update(args);\n    }\n  }\n);\n```\n\nThe implementation remains simple.\n\nThe important engineering work is deciding:\n\nTool consolidation should be measurable.\n\nBefore:\n\n```\nTools: 32\nTool schema tokens: X\nAverage tool-selection latency: Y\nTool-selection errors: Z\n```\n\nAfter:\n\n```\nTools: 5\nTool schema tokens: X'\nAverage tool-selection latency: Y'\nTool-selection errors: Z'\n```\n\nThe most useful metrics include:\n\nThe objective is not merely:\n\n```\nfewer tools\n```\n\nbut:\n\n```\nbetter agent performance per unit of context\n```\n\nThis approach can be generalized beyond MCP.\n\nThe same principle applies to:\n\nThe pattern is essentially:\n\n```\nTraditional API\n       ↓\nMany low-level operations\n       ↓\nAgent-oriented facade\n       ↓\nSmall semantic tool surface\n       ↓\nAction routing\n       ↓\nStrict execution layer\n```\n\nThis is not really a database trick.\n\nIt is an **agent interface design pattern**.\n\nThe final mental model is simple:\n\n```\n                 ┌──────────────────────┐\n                 │      30 APIs         │\n                 │    capabilities      │\n                 └──────────┬───────────┘\n                            │\n                     Consolidation\n                            │\n                            ▼\n                 ┌──────────────────────┐\n                 │      3–5 tools       │\n                 │  semantic domains    │\n                 └──────────┬───────────┘\n                            │\n                     Action routing\n                            │\n                            ▼\n                 ┌──────────────────────┐\n                 │    30 handlers       │\n                 │  original abilities │\n                 └──────────────────────┘\n```\n\nThe important transformation is therefore not:\n\n```\n30 capabilities → 3 capabilities\n```\n\nIt is:\n\n```\n30 exposed tools → 3 exposed interfaces\n```\n\nThe capabilities remain.\n\nThe model-facing surface becomes smaller.\n\nAs agent systems become more integrated, the number of available tools will continue to grow.\n\nJira, GitLab, Confluence, Sentry, ELK, Jaeger, databases, cloud platforms and internal systems can easily produce dozens or hundreds of operations.\n\nExposing every operation directly to the model is not always the best architecture.\n\nA better approach is to introduce an **agent-oriented tool layer**:\n\n```\nMany low-level operations\n          ↓\nSemantic domain grouping\n          ↓\nDiscriminated action schemas\n          ↓\nSmall MCP tool surface\n          ↓\nStrict runtime validation\n          ↓\nOriginal capabilities\n```\n\nThe central principle is:\n\nReduce the number of tools the model has to understand, not the number of capabilities your system provides.\n\nA 30-tool integration does not necessarily need 30 model-facing tools.\n\nIt may need three well-designed interfaces with clear action spaces.\n\nAnd this pattern becomes especially powerful when existing MCP servers are treated not as untouchable interfaces, but as underlying integrations that can be wrapped, adapted and optimized for the needs of the agent.\n\nThe future of MCP design may therefore be less about exposing **everything an API can do**, and more about designing the **smallest useful interface through which an agent can do what it needs to do**.", "url": "https://wpnews.pro/news/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface", "canonical_source": "https://dev.to/serifcolakel/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface-b40", "published_at": "2026-08-15 22:29:14+00:00", "updated_at": "2026-08-15 23:11:05.219238+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models"], "entities": ["Jira", "GitLab", "Confluence", "MCP"], "alternates": {"html": "https://wpnews.pro/news/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface", "markdown": "https://wpnews.pro/news/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface.md", "text": "https://wpnews.pro/news/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface.txt", "jsonld": "https://wpnews.pro/news/from-30-tools-to-3-designing-a-token-efficient-mcp-tool-surface.jsonld"}}