DEV Community

Cover image for From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface
Serif COLAKEL
Serif COLAKEL

Posted on

From 30 Tools to 3: Designing a Token-Efficient MCP Tool Surface

Modern agentic applications rarely suffer from a lack of tools.

They suffer from too many of them.

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.

Each integration can expose many operations:

Jira
├── searchIssues
├── getIssue
├── createIssue
├── updateIssue
├── addComment
├── transitionIssue
├── getTransitions
├── assignIssue
└── ...

GitLab
├── listProjects
├── getProject
├── listIssues
├── createIssue
├── updateIssue
├── listMergeRequests
├── getMergeRequest
├── createComment
└── ...

Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...
Enter fullscreen mode Exit fullscreen mode

It is easy to end up with 30, 50, or even hundreds of tools.

At first, this looks like a capability problem.

It is actually a tool-surface problem.

The agent does not necessarily need fewer capabilities.

It needs fewer top-level tools.

This 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.

The core idea is simple:

Consolidate the tool surface, not the capabilities.

Instead of exposing:

30 MCP tools
Enter fullscreen mode Exit fullscreen mode

we can expose:

3–5 domain tools
Enter fullscreen mode Exit fullscreen mode

and use an action discriminator to route requests internally.

For example:

jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...
Enter fullscreen mode Exit fullscreen mode

can become:

jira({
  action: "search",
  ...
})

jira({
  action: "getIssue",
  ...
})

jira({
  action: "createIssue",
  ...
})
Enter fullscreen mode Exit fullscreen mode

The backend still has all the original capabilities.

The model simply sees a much smaller tool surface.


1. The Problem With Large Tool Surfaces

An MCP server is not only an execution interface.

It is also part of the model's context.

When an agent connects to an MCP server, the model generally needs to understand:

  • tool names
  • descriptions
  • input schemas
  • parameters
  • enums
  • constraints
  • sometimes additional metadata

Imagine an agent connected to 40 tools.

Even if each tool has a relatively small schema, the aggregate context can become significant.

More importantly, the model now has a larger decision space:

User request
     │
     ▼
Which tool?
     │
 ┌───┼────┬────┬────┬────┐
 ▼   ▼    ▼    ▼    ▼    ▼
T1   T2   T3   T4   T5   ...
Enter fullscreen mode Exit fullscreen mode

The model has to distinguish between many semantically related operations.

For example:

jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs
Enter fullscreen mode Exit fullscreen mode

are all part of the same conceptual domain.

There is little value in forcing the model to treat every operation as a completely independent top-level capability.


2. The Core Idea: Tool Multiplexing

The pattern is to introduce an intermediate discriminator:

{
  "action": "search",
  "query": "authentication bug"
}
Enter fullscreen mode Exit fullscreen mode

Instead of:

jira_search
Enter fullscreen mode Exit fullscreen mode

we expose:

jira
Enter fullscreen mode Exit fullscreen mode

The tool becomes a small router.

Conceptually:

                     jira
                      │
                 action field
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
    search         getIssue       createIssue
       │              │              │
       ▼              ▼              ▼
 searchHandler   issueHandler   createHandler
Enter fullscreen mode Exit fullscreen mode

The important point is that the tool is not the capability.

The tool is the external interface.

The action represents the capability.

This gives us:

30 capabilities
       ↓
3 domain-oriented tools
Enter fullscreen mode Exit fullscreen mode

without throwing away functionality.


3. Use Domain Boundaries, Not Arbitrary Grouping

The easiest mistake is to think:

"I have 30 tools, so I will put 10 operations into each tool."

That is not the goal.

Grouping should follow semantic domains.

For example:

jira
├── search
├── issue
├── comment
├── transition
└── project

gitlab
├── project
├── issue
├── mergeRequest
└── pipeline

observability
├── search
├── trace
├── log
└── error
Enter fullscreen mode Exit fullscreen mode

The exact grouping depends on the integration.

For a database:

db_tables
db_query
db_advanced
Enter fullscreen mode Exit fullscreen mode

might make sense.

For Jira:

jira_issues
jira_projects
jira_search
Enter fullscreen mode Exit fullscreen mode

may be better.

For GitLab:

gitlab_repository
gitlab_issues
gitlab_mergeRequests
Enter fullscreen mode Exit fullscreen mode

may be more natural.

There is no universal number.

The goal is to find the smallest tool surface that still preserves clear semantic boundaries.


4. A Database Example

A database integration is a useful example because database APIs can easily expose a large number of operations.

Instead of:

listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas
Enter fullscreen mode Exit fullscreen mode

we can expose:

db_tables
db_query
db_advanced
Enter fullscreen mode Exit fullscreen mode

The first tool can use:

const tablesSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list"),
    schema: schemaField,
  }),

  z.object({
    action: z.literal("schema"),
    tableName: z.string(),
    schema: schemaField,
  }),

  z.object({
    action: z.literal("sampleData"),
    tableName: z.string(),
    schema: schemaField,
    rowCount: z.number().optional().default(10),
  }),

  z.object({
    action: z.literal("size"),
    tableName: z.string(),
    schema: schemaField,
  }),
]);
Enter fullscreen mode Exit fullscreen mode

The model sees one tool:

db_tables
Enter fullscreen mode Exit fullscreen mode

with an explicit action space:

list
schema
sampleData
size
Enter fullscreen mode Exit fullscreen mode

The runtime still has four separate handlers.

switch (args.action) {
  case "list":
    return handleListTables(args.schema);

  case "schema":
    return handleGetTableSchema(
      args.tableName,
      args.schema
    );

  case "sampleData":
    return handleGetSampleData(
      args.tableName,
      args.schema,
      args.rowCount
    );

  case "size":
    return handleGetTableSize(
      args.tableName,
      args.schema
    );
}
Enter fullscreen mode Exit fullscreen mode

This distinction is important:

Consolidation happens at the MCP interface, not inside the business logic.

The internal handlers remain independently testable and maintainable.


5. Zod Discriminated Unions Are a Natural Fit

For TypeScript applications, z.discriminatedUnion() provides a clean way to express this pattern.

For example:

const querySchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("execute"),
    query: z.string(),
    params: z.record(z.string()).optional(),
    limit: z.number().optional().default(100),
  }),

  z.object({
    action: z.literal("info"),
  }),
]);
Enter fullscreen mode Exit fullscreen mode

The type can then be inferred directly:

type QueryInput = z.infer<typeof querySchema>;
Enter fullscreen mode Exit fullscreen mode

This gives us three useful properties:

  1. A single MCP tool.
  2. Explicit action routing.
  3. Strict runtime validation.

The resulting architecture becomes:

             MCP Tool
                │
                ▼
        Discriminated Union
                │
          ┌─────┴─────┐
          │  action   │
          └─────┬─────┘
                │
       ┌────────┼────────┐
       ▼        ▼        ▼
    Handler A Handler B Handler C
       │        │        │
       └────────┼────────┘
                ▼
             Backend
Enter fullscreen mode Exit fullscreen mode

This is much more predictable than asking the model to navigate dozens of unrelated top-level tools.


6. Tool Count Is Not Capability Count

This is probably the most important conceptual distinction.

Suppose we have:

3 tools
13 actions
Enter fullscreen mode Exit fullscreen mode

That does not mean we lost 10 capabilities.

We have:

3 external interfaces
13 internal capabilities
Enter fullscreen mode Exit fullscreen mode

Therefore:

Tool count ≠ capability count
Enter fullscreen mode Exit fullscreen mode

This distinction becomes increasingly important as agent systems grow.

A large organization may have:

Jira             20 operations
GitLab            25 operations
Confluence        15 operations
Sentry             8 operations
ELK               10 operations
Jaeger             6 operations
Database           20 operations
Enter fullscreen mode Exit fullscreen mode

That can easily become 100+ operations.

Exposing all of them directly to the model creates an unnecessarily large tool surface.

Instead, we can build:

jira
gitlab
confluence
sentry
observability
database
Enter fullscreen mode Exit fullscreen mode

and keep the underlying operation count unchanged.


7. Why This Can Reduce Token Usage

The primary optimization is reducing the amount of tool metadata the model needs to process.

Instead of presenting:

Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30
Enter fullscreen mode Exit fullscreen mode

we present:

Tool A
Tool B
Tool C
Enter fullscreen mode Exit fullscreen mode

This can reduce:

  • tool descriptions
  • repeated parameter metadata
  • duplicated semantic information
  • top-level tool-selection complexity
  • context consumed by tool definitions

However, this should not be described as a guaranteed linear cost reduction.

If we transform:

30 tools → 3 tools
Enter fullscreen mode Exit fullscreen mode

it does not necessarily mean:

90% lower token cost
Enter fullscreen mode Exit fullscreen mode

because the consolidated schemas themselves can become larger.

The correct statement is:

Tool 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.

This distinction matters.


8. Consolidation Is Not Always Better

There is an important trade-off.

Consider a tool with 40 actions:

enterprise({
  action: ...
})
Enter fullscreen mode Exit fullscreen mode

with actions such as:

createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...
Enter fullscreen mode Exit fullscreen mode

This is technically possible.

It is also terrible design.

The model now has one enormous schema.

The problem has simply moved from:

30 tools
Enter fullscreen mode Exit fullscreen mode

to:

1 giant tool
Enter fullscreen mode Exit fullscreen mode

The correct approach is domain-oriented consolidation.

For example:

customer
billing
repository
observability
deployment
Enter fullscreen mode Exit fullscreen mode

The ideal number might be 5 rather than 1.

Therefore:

Minimize the tool surface, but do not minimize it blindly.


9. The Schema Is Part of the Agent Interface

Once multiple capabilities share one tool, the action field becomes extremely important.

Bad:

action: z.string()
Enter fullscreen mode Exit fullscreen mode

Better:

action: z.enum([
  "search",
  "get",
  "create",
  "update"
])
Enter fullscreen mode Exit fullscreen mode

Best, when actions have different parameters:

z.discriminatedUnion("action", [
  searchSchema,
  getSchema,
  createSchema,
  updateSchema,
])
Enter fullscreen mode Exit fullscreen mode

Now the action and its parameters form a strongly typed relationship.

For example:

action = "search"
→ query required

action = "get"
→ issueId required

action = "create"
→ title + description required
Enter fullscreen mode Exit fullscreen mode

This is much more expressive than one generic schema with dozens of optional fields.


10. The MCP JSON Schema Problem

In practice, there is another layer of complexity.

MCP tool schemas ultimately need to be represented as JSON Schema.

A Zod discriminated union can produce a schema based on:

{
  "anyOf": [...]
}
Enter fullscreen mode Exit fullscreen mode

or:

{
  "oneOf": [...]
}
Enter fullscreen mode Exit fullscreen mode

depending on the converter.

Some MCP schema handling paths expect an object at the root.

That creates a compatibility problem.

In 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.

A compatibility layer can therefore normalize the generated schema.

Conceptually:

Zod discriminated union
          │
          ▼
     JSON Schema
          │
          ▼
   MCP normalization
          │
          ▼
MCP-compatible object schema
Enter fullscreen mode Exit fullscreen mode

The important architectural principle is:

Adapt the schema exposed to the model without weakening the runtime validator.

The original Zod schema should remain the source of truth for validation.


11. LLM-Facing Schema vs Runtime Schema

This leads to an important distinction.

There are effectively two concerns:

LLM-facing representation
        │
        ▼
Tool selection and argument generation

Runtime representation
        │
        ▼
Validation and execution
Enter fullscreen mode Exit fullscreen mode

The LLM-facing schema needs to be:

  • understandable
  • compact
  • explicit
  • easy to select from

The runtime schema needs to be:

  • strict
  • safe
  • deterministic
  • authoritative

These do not necessarily have to be identical.

For 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.

This is a useful general principle for agent infrastructure:

Optimize schemas for model consumption, but never use model-facing schemas as the only security boundary.


12. Descriptions Become More Important

When there are 30 tools, each tool can have a very specific description.

When there are 3 tools, the description needs to explain the action space.

For example:

Unified tool for advanced database analysis.

ACTIONS:

relationships
  List foreign key relationships.

indexes
  Get indexes for a table.

profileColumn
  Analyze nullability, cardinality and top values.

searchSchema
  Search tables and columns.

listProcedures
  List stored procedures.

triggers
  Get triggers.

compareSchemas
  Compare two table definitions.
Enter fullscreen mode Exit fullscreen mode

This is not documentation only.

It is part of the model's routing interface.

A useful mental model is:

Tool name
    +
Description
    +
Action enum
    +
Parameter schema
        ↓
Agent routing signal
Enter fullscreen mode Exit fullscreen mode

Therefore, when consolidating tools, descriptions should become more intentional, not less.


13. The Pattern Generalizes Beyond Databases

This approach becomes much more valuable when applied across an entire engineering environment.

Jira

Instead of:

searchIssues
getIssue
createIssue
updateIssue
addComment
transitionIssue
assignIssue
getTransitions
Enter fullscreen mode Exit fullscreen mode

use:

jira_issues
Enter fullscreen mode Exit fullscreen mode

with:

search
get
create
update
comment
transition
assign
Enter fullscreen mode Exit fullscreen mode

GitLab

Instead of exposing every repository, issue, merge request and pipeline operation:

gitlab_repository
gitlab_issues
gitlab_mergeRequests
gitlab_pipelines
Enter fullscreen mode Exit fullscreen mode

Each can expose a focused action set.

Confluence / Wiki

Instead of:

searchPages
getPage
createPage
updatePage
deletePage
getChildren
Enter fullscreen mode Exit fullscreen mode

use:

wiki
Enter fullscreen mode Exit fullscreen mode

with:

search
get
create
update
children
Enter fullscreen mode Exit fullscreen mode

Sentry

Potentially:

sentry
Enter fullscreen mode Exit fullscreen mode

with:

searchIssues
getIssue
events
releases
projects
Enter fullscreen mode Exit fullscreen mode

ELK

Potentially:

logs
Enter fullscreen mode Exit fullscreen mode

with:

search
aggregate
fields
indices
Enter fullscreen mode Exit fullscreen mode

Jaeger

Potentially:

tracing
Enter fullscreen mode Exit fullscreen mode

with:

search
getTrace
services
operations
Enter fullscreen mode Exit fullscreen mode

The important point is not the exact names.

It is the architectural pattern.


14. Existing MCP Servers Can Be Wrapped

One of the most practical aspects of this approach is that we do not necessarily need to rewrite every integration.

There are already MCP implementations for many popular systems.

The problem is that their exposed tool surface may not be optimal for a specific agent.

Instead of:

Agent
  │
  ├── 20 Jira tools
  ├── 20 GitLab tools
  └── 15 Confluence tools
Enter fullscreen mode Exit fullscreen mode

we can introduce a customized layer:

                 Agent
                   │
             Custom MCP Layer
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼
      Jira       GitLab    Confluence
        │          │          │
     existing   existing   existing
       APIs       MCP        MCP
Enter fullscreen mode Exit fullscreen mode

The custom layer becomes an agent-oriented facade.

This is particularly useful when the existing integration is technically correct but exposes too many low-level operations.


15. MCP as an Adapter Layer

This suggests a broader architectural pattern.

Traditional integration:

Agent → MCP → Service
Enter fullscreen mode Exit fullscreen mode

Customized integration:

Agent
  ↓
Agent-oriented MCP facade
  ↓
Service-specific MCP/API
  ↓
Service
Enter fullscreen mode Exit fullscreen mode

The facade can provide:

  • tool consolidation
  • permission checks
  • input normalization
  • output normalization
  • logging
  • tracing
  • caching
  • rate limiting
  • organization-specific policies

The MCP layer therefore becomes more than a transport.

It becomes an agent interface layer.


16. Permissions Should Follow Actions

There is an important security implication.

If we consolidate:

jira_create
jira_update
jira_delete
Enter fullscreen mode Exit fullscreen mode

into:

jira({
  action: ...
})
Enter fullscreen mode Exit fullscreen mode

we should not simply grant:

permission: jira
Enter fullscreen mode Exit fullscreen mode

and assume all operations are equivalent.

Permissions should still be evaluated at the action level.

For example:

jira.search       → allowed
jira.get          → allowed
jira.create       → allowed
jira.update       → approval required
jira.delete       → denied
Enter fullscreen mode Exit fullscreen mode

The unified tool is only an interface optimization.

It must not become a security boundary that accidentally grants excessive privileges.


17. Observability Should Include the Action

With consolidated tools, logging only the tool name is no longer sufficient.

Bad:

tool = jira
Enter fullscreen mode Exit fullscreen mode

Better:

{
  "tool": "jira",
  "action": "search",
  "duration_ms": 142
}
Enter fullscreen mode Exit fullscreen mode

Even better:

{
  "tool": "jira",
  "action": "search",
  "status": "success",
  "duration_ms": 142,
  "request_id": "...",
  "agent_id": "...",
  "user_id": "..."
}
Enter fullscreen mode Exit fullscreen mode

The fundamental execution unit becomes:

tool + action
Enter fullscreen mode Exit fullscreen mode

rather than just:

tool
Enter fullscreen mode Exit fullscreen mode

This is particularly important for debugging agent behavior.


18. Keep Internal Handlers Independent

Tool consolidation should happen at the boundary.

Do not create a 2,000-line function like:

async function jira(args) {
  // everything
}
Enter fullscreen mode Exit fullscreen mode

Instead:

async function jira(args: JiraInput) {
  switch (args.action) {
    case "search":
      return searchIssues(args);

    case "get":
      return getIssue(args);

    case "create":
      return createIssue(args);

    case "update":
      return updateIssue(args);
  }
}
Enter fullscreen mode Exit fullscreen mode

Each handler remains independently testable:

jira()
  │
  ├── searchIssues()
  ├── getIssue()
  ├── createIssue()
  └── updateIssue()
Enter fullscreen mode Exit fullscreen mode

This preserves maintainability while reducing the external tool surface.


19. A Useful Design Rule

A practical rule for deciding whether two operations should be consolidated is:

Would a human developer naturally describe these operations as belonging to the same capability?

For example:

Jira search
Jira get issue
Jira update issue
Enter fullscreen mode Exit fullscreen mode

Yes.

But:

Jira update issue
Elasticsearch search logs
Create AWS infrastructure
Enter fullscreen mode Exit fullscreen mode

No.

Another useful test:

Would the same context usually be required to decide between these operations?

If yes, consolidation is often beneficial.

If no, keep them separate.


20. What Should Not Be Consolidated?

There are cases where separate tools are better.

Completely different domains

database
payments
infrastructure
email
Enter fullscreen mode Exit fullscreen mode

should not become:

enterprise({
  action: ...
})
Enter fullscreen mode Exit fullscreen mode

Extremely large action sets

If a single tool has 50 actions, the schema itself may become expensive and confusing.

Highly privileged operations

Operations such as:

deleteProductionData
rotateCredentials
deployProduction
Enter fullscreen mode Exit fullscreen mode

may deserve separate permission boundaries even if they belong to the same domain.

Different lifecycle semantics

Read-only operations and destructive operations can sometimes benefit from separate interfaces:

jira_read
jira_write
Enter fullscreen mode Exit fullscreen mode

instead of one enormous tool.

Again, there is no universal rule.


21. The Sweet Spot Is Not "As Few Tools As Possible"

The goal is not:

30 → 1
Enter fullscreen mode Exit fullscreen mode

The goal is:

30 → smallest useful semantic surface
Enter fullscreen mode Exit fullscreen mode

For one system that might be:

30 → 3
Enter fullscreen mode Exit fullscreen mode

For another:

30 → 7
Enter fullscreen mode Exit fullscreen mode

For another:

30 → 12
Enter fullscreen mode Exit fullscreen mode

The optimization target is:

minimize unnecessary tool metadata
Enter fullscreen mode Exit fullscreen mode

while preserving:

semantic clarity
validation
security
maintainability
Enter fullscreen mode Exit fullscreen mode

22. A General Architecture

A scalable implementation can look like this:

                        Agent
                          │
                          ▼
                ┌──────────────────┐
                │  Agent MCP Layer │
                └────────┬─────────┘
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
        jira           gitlab          wiki
          │              │              │
     ┌────┼────┐    ┌────┼────┐    ┌────┼────┐
     ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼    ▼
  search get create issues MR pipeline search get update
          │              │              │
          └──────────────┼──────────────┘
                         ▼
                 Existing APIs/MCPs
Enter fullscreen mode Exit fullscreen mode

The agent sees a compact interface.

The integration layer retains the full capability set.


23. This Also Changes How We Think About MCP

MCP is often treated as:

"Expose every API operation as a tool."

That is a reasonable starting point.

For production agent systems, however, a better question is:

What interface should the model see?

Those are not necessarily the same thing.

A REST API might expose:

GET /issues
GET /issues/:id
POST /issues
PATCH /issues/:id
POST /issues/:id/comments
POST /issues/:id/transition
Enter fullscreen mode Exit fullscreen mode

We do not have to expose every HTTP endpoint as a separate model-facing tool.

The MCP layer can become a semantic abstraction over those endpoints.

This is similar to designing an API specifically for a consumer rather than simply mirroring an underlying database or service API.


24. Tool Design Becomes an Agent UX Problem

Traditional API design asks:

What endpoints does the system provide?

Agent interface design asks:

What decisions does the model need to make?

These are different questions.

A human developer may prefer:

getIssue
getIssueComments
getIssueTransitions
getIssueWorklogs
Enter fullscreen mode Exit fullscreen mode

because each API operation is explicit.

An agent may perform better with:

jira_issue({
  action: "get",
  ...
})
Enter fullscreen mode Exit fullscreen mode

and:

jira_issue({
  action: "comments",
  ...
})
Enter fullscreen mode Exit fullscreen mode

because the model first identifies the domain and then chooses the operation.

This creates a two-level routing model:

Domain
  ↓
Action
  ↓
Arguments
Enter fullscreen mode Exit fullscreen mode

rather than:

One large global tool-selection problem
Enter fullscreen mode Exit fullscreen mode

25. A Simple Implementation Pattern

The pattern can be generalized with TypeScript:

const toolSchema = z.discriminatedUnion("action", [
  searchSchema,
  getSchema,
  createSchema,
  updateSchema,
]);

server.registerTool(
  "jira",
  {
    description: "...",
    inputSchema: toolSchema,
  },
  async (args) => {
    switch (args.action) {
      case "search":
        return search(args);

      case "get":
        return get(args);

      case "create":
        return create(args);

      case "update":
        return update(args);
    }
  }
);
Enter fullscreen mode Exit fullscreen mode

The implementation remains simple.

The important engineering work is deciding:

  1. Which operations belong together?
  2. What should the action names be?
  3. What should the tool description contain?
  4. Which fields are required for each action?
  5. Which actions require special permissions?
  6. How should the output be normalized?
  7. How should actions be logged and traced?

26. Measure Before and After

Tool consolidation should be measurable.

Before:

Tools: 32
Tool schema tokens: X
Average tool-selection latency: Y
Tool-selection errors: Z
Enter fullscreen mode Exit fullscreen mode

After:

Tools: 5
Tool schema tokens: X'
Average tool-selection latency: Y'
Tool-selection errors: Z'
Enter fullscreen mode Exit fullscreen mode

The most useful metrics include:

  • number of exposed tools
  • total schema size
  • prompt/context token usage
  • tool-selection accuracy
  • invalid tool calls
  • argument validation failures
  • latency
  • execution success rate
  • agent task completion rate

The objective is not merely:

fewer tools
Enter fullscreen mode Exit fullscreen mode

but:

better agent performance per unit of context
Enter fullscreen mode Exit fullscreen mode

27. The Bigger Pattern: Agent-Oriented API Design

This approach can be generalized beyond MCP.

The same principle applies to:

  • function calling
  • OpenAI-style tools
  • SDKs
  • internal agent runtimes
  • workflow engines
  • plugin systems
  • REST-to-agent adapters
  • GraphQL-to-agent interfaces

The pattern is essentially:

Traditional API
       ↓
Many low-level operations
       ↓
Agent-oriented facade
       ↓
Small semantic tool surface
       ↓
Action routing
       ↓
Strict execution layer
Enter fullscreen mode Exit fullscreen mode

This is not really a database trick.

It is an agent interface design pattern.


28. The Principle

The final mental model is simple:

                 ┌──────────────────────┐
                 │      30 APIs         │
                 │    capabilities      │
                 └──────────┬───────────┘
                            │
                     Consolidation
                            │
                            ▼
                 ┌──────────────────────┐
                 │      3–5 tools       │
                 │  semantic domains    │
                 └──────────┬───────────┘
                            │
                     Action routing
                            │
                            ▼
                 ┌──────────────────────┐
                 │    30 handlers       │
                 │  original abilities │
                 └──────────────────────┘
Enter fullscreen mode Exit fullscreen mode

The important transformation is therefore not:

30 capabilities → 3 capabilities
Enter fullscreen mode Exit fullscreen mode

It is:

30 exposed tools → 3 exposed interfaces
Enter fullscreen mode Exit fullscreen mode

The capabilities remain.

The model-facing surface becomes smaller.


Conclusion

As agent systems become more integrated, the number of available tools will continue to grow.

Jira, GitLab, Confluence, Sentry, ELK, Jaeger, databases, cloud platforms and internal systems can easily produce dozens or hundreds of operations.

Exposing every operation directly to the model is not always the best architecture.

A better approach is to introduce an agent-oriented tool layer:

Many low-level operations
          ↓
Semantic domain grouping
          ↓
Discriminated action schemas
          ↓
Small MCP tool surface
          ↓
Strict runtime validation
          ↓
Original capabilities
Enter fullscreen mode Exit fullscreen mode

The central principle is:

Reduce the number of tools the model has to understand, not the number of capabilities your system provides.

A 30-tool integration does not necessarily need 30 model-facing tools.

It may need three well-designed interfaces with clear action spaces.

And 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.

The 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.

Top comments (0)