Home / Articles

Designing Token-Efficient Tool Surfaces for Agentic MCP Servers

Learn how to collapse dozens of MCP tool definitions into a handful of domain-focused, action-routed tools without losing any underlying functionality.

Publisher: REACTAPP.TOP19 min read4404 words

This article is published in English.

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

The typical struggle with modern agentic systems isn't a shortage of tools.

It's having far too many.

As an agent's capabilities expand, it's common practice to wire it up to Jira, GitLab, Confluence, Sentry, Elasticsearch, Jaeger, various databases, monitoring dashboards, internal APIs, deployment pipelines, and countless other systems.

Each of these integrations tends to expose a long list of operations:

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

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

Confluence
├── searchPages
├── getPage
├── createPage
├── updatePage
└── ...

It doesn't take long before you're maintaining 30, 50, or even hundreds of tool definitions.

At a glance, this looks like an issue of raw capability.

In reality, it's a tool-surface issue.

The agent doesn't need fewer things it can do.

What it needs is fewer top-level tools to choose from.

This piece walks through an approach for shrinking bloated MCP or tool surfaces down to a handful of domain-focused tools, without losing any of the underlying functionality.

The central idea is straightforward:

Shrink the tool surface, not the set of capabilities.

Rather than exposing something like:

30 MCP tools

you can expose:

3–5 domain tools

and rely on an action field to route each call internally.

So a set like:

jira_search
jira_get_issue
jira_create_issue
jira_update_issue
jira_add_comment
jira_transition_issue
...

collapses into something like:

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

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

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

The backend retains every original capability.

From the model's perspective, though, the tool surface is dramatically smaller.

1. The Problem With Large Tool Surfaces

An MCP server isn't purely an execution mechanism.

It also becomes part of what the model has to hold in context.

Whenever an agent connects to an MCP server, the model typically has to process:

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

Picture an agent wired to 40 different tools.

Even when each individual schema is compact, the combined footprint can add up fast.

Beyond that, the model now faces a bigger decision surface:

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

It has to tell apart many operations that are conceptually adjacent.

Take this example:

jira_search_issues
jira_search_projects
jira_get_issue
jira_get_issue_comments
jira_get_issue_transitions
jira_get_issue_worklogs

These all belong to the same conceptual domain.

Treating each one as a fully independent top-level capability adds little value.

2. The Core Idea: Tool Multiplexing

The trick is to add a middle layer, a discriminator field:

{
  "action": "search",
  "query": "authentication bug"
}

Instead of defining:

jira_search

you expose just:

jira

The tool itself becomes a lightweight router.

Structurally, it looks like:

jira
                      │
                 action field
                      │
       ┌──────────────┼──────────────┐
       ▼              ▼              ▼
    search         getIssue       createIssue
       │              │              │
       ▼              ▼              ▼
 searchHandler   issueHandler   createHandler

The key insight is that the tool is not the same thing as the capability.

The tool is simply the outward-facing interface.

The action field is what actually carries the capability.

The result:

30 capabilities
       ↓
3 domain-oriented tools

with no functionality lost along the way.

3. Use Domain Boundaries, Not Arbitrary Grouping

A common pitfall is reasoning like:

"I've got 30 tools, so I'll bundle 10 operations into each one."

That's not the point.

Grouping needs to be driven by semantic domains, not arbitrary bucket sizes.

For instance:

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

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

observability
├── search
├── trace
├── log
└── error

The right grouping will vary by integration.

For a database, something like:

db_tables
db_query
db_advanced

could work well.

For Jira, you might prefer:

jira_issues
jira_projects
jira_search

For GitLab, a layout such as:

gitlab_repository
gitlab_issues
gitlab_mergeRequests

might fit better.

There's no fixed number that works everywhere.

The objective is finding the smallest tool surface that still respects clear, meaningful semantic boundaries.

4. A Database Example

A database connector makes a good illustration, since database APIs tend to expose a long list of individual operations.

Rather than defining separate entries such as:

listTables
getTableSchema
getSampleData
getTableSize
executeQuery
getDatabaseInfo
listRelationships
getIndexes
profileColumn
searchSchema
listProcedures
getTriggers
compareSchemas

you can collapse them into:

db_tables
db_query
db_advanced

The first of these tools can rely on a schema like:

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,
  }),
]);

From the model's point of view, there's just one tool:

db_tables

backed by a clearly defined set of possible actions:

list
schema
sampleData
size

Underneath, the runtime still dispatches to four distinct 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
    );
}

This separation matters:

Merging happens at the level of the MCP interface, not within the underlying business logic.

Each internal handler stays independently testable and easy to maintain.

5. Zod Discriminated Unions Are a Natural Fit

In TypeScript codebases, z.discriminatedUnion() offers a clean way to model 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"),
  }),
]);

From there, the type can be derived directly:

type QueryInput = z.infer<typeof querySchema>;

This approach gives you three practical benefits:

  1. A single MCP tool.
  2. Explicit routing based on the action field.
  3. Strict validation enforced at runtime.

The overall shape of the architecture looks like this:

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

This is far easier for a model to navigate than dozens of unrelated top-level tools.

6. Tool Count Is Not Capability Count

This is arguably the most important idea in this approach.

Say you end up with:

3 tools
13 actions

That doesn't mean ten capabilities disappeared. What you actually have is:

3 external interfaces
13 internal capabilities

In short:

Tool count ≠ capability count

This distinction only grows in importance as agent systems scale up. A large organization's toolset might look like:

Jira             20 operations
GitLab            25 operations
Confluence        15 operations
Sentry             8 operations
ELK               10 operations
Jaeger             6 operations
Database           20 operations

which can easily add up to well over 100 individual operations. Exposing every one of them directly to the model produces an unnecessarily bloated tool surface. Instead, you can consolidate into something like:

jira
gitlab
confluence
sentry
observability
database

while leaving the total number of underlying operations untouched.

7. Why This Can Reduce Token Usage

The main gain here comes from shrinking the volume of tool metadata the model has to parse. Rather than listing:

Tool 1
Tool 2
Tool 3
Tool 4
...
Tool 30

you present:

Tool A
Tool B
Tool C

This can cut down on:

  • tool descriptions
  • repeated parameter metadata
  • duplicated semantic explanations
  • the complexity of choosing among top-level tools
  • context space taken up by tool definitions

That said, this shouldn't be framed as a guaranteed proportional savings. Going from:

30 tools → 3 tools

doesn't automatically translate into:

90% lower token cost

because the merged schemas can themselves grow larger. A more accurate way to frame it:

Bundling several operations into one tool tends to shrink the metadata footprint an agent has to read, though how much you actually save in tokens or response time hinges on things like the size of the combined schema, how verbose the field descriptions are, quirks in the model provider's handling of tool definitions, and the specific way your agent framework manages tool calls.

That nuance is worth keeping in mind.

8. Bundling Everything Together Isn't Automatically Better

There's a trade-off worth calling out here.

Imagine a single tool exposing 40 different actions:

enterprise({
  action: ...
})

with actions like:

createCustomer
deleteCustomer
searchInvoice
rotateCredentials
deployService
createRepository
getTrace
searchLogs
...

This is technically achievable.

It also makes for poor design.

The model is now stuck parsing one massive schema.

You haven't solved the original problem, you've just relocated it, from:

30 tools

to:

1 giant tool

The better path is consolidation along domain lines.

For instance:

customer
billing
repository
observability
deployment

Five tools might be the right target, not one.

Which leads to a guiding principle:

Shrink the tool surface, but don't shrink it carelessly.

9. The Schema Doubles as Part of the Agent's Interface

Once a single tool covers several capabilities, the action parameter carries a lot of weight.

A weak version:

action: z.string()

An improvement:

action: z.enum([
  "search",
  "get",
  "create",
  "update"
])

The strongest option, when each action needs distinct parameters:

z.discriminatedUnion("action", [
  searchSchema,
  getSchema,
  createSchema,
  updateSchema,
])

Here, the chosen action and its expected arguments are tied together with real type safety.

For example:

action = "search"
→ query required
action = "get"
→ issueId requiredaction = "create"
→ title + description required

This is far more precise than a single loosely-typed schema stuffed with optional fields for every possible action.

10. JSON Schema Complications Under MCP

There's an added wrinkle in practice.

MCP tool schemas eventually get compiled down to JSON Schema.

Depending on which converter you use, a Zod discriminated union might generate:

{
  "anyOf": [...]
}

or:

{
  "oneOf": [...]
}

Some parts of the MCP schema pipeline assume the root of the schema is a plain object.

That assumption doesn't always hold.

In at least one real implementation, the Zod compatibility layer inside the MCP SDK was built around object shapes and didn't gracefully translate a discriminated union.

The fix is to insert a normalization step for the generated schema.

Conceptually, it looks like this:

Zod discriminated union
          │
          ▼
     JSON Schema
          │
          ▼
   MCP normalization
          │
          ▼
MCP-compatible object schema

The architectural takeaway is:

Reshape the schema you show the model without touching the strictness of your actual validator.

Your original Zod schema should stay the single source of truth for validation.

11. Two Different Schemas: One for the Model, One for Runtime

This points to a broader distinction worth making explicit.

There are really two separate jobs here:

LLM-facing representation
        │
        ▼
Tool selection and argument generation
Runtime representation
        │
        ▼
Validation and execution

The schema shown to the LLM should be:

  • easy to read
  • compact
  • unambiguous
  • simple to pick from

The schema used at runtime should be:

  • strict
  • safe
  • predictable
  • final

These two representations don't need to match exactly. A flattened, simplified object shape might help a model pick the right action more easily, while the underlying discriminated union still enforces validation behind the scenes.

This generalizes into a broader rule for agent systems:

Tune schemas for how models read them, but never rely on the model-facing schema as your only line of defense.

12. Descriptions Carry More Weight Once Tools Are Merged

With 30 separate tools, each one can afford a narrow, specific description.

With 3 tools, the description has to communicate the entire space of possible actions.

For instance:

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.

This isn't just documentation for humans.

It functions as part of how the model routes its calls.

A helpful way to think about it:

Tool name
    +
Description
    +
Action enum
    +
Parameter schema
        ↓
Agent routing signal

So consolidating tools should push you toward writing more careful descriptions, not fewer of them.

13. This Pattern Isn't Limited to Databases

The same approach pays off even more once you apply it across a broader set of engineering systems.

Jira

Rather than exposing:

searchIssues
getIssue
createIssue
updateIssue
addComment
transitionIssue
assignIssue
getTransitions

expose:

jira_issues

with an action set like:

search
get
create
update
comment
transition
assign

GitLab

Rather than surfacing separate tools for repositories, issues, merge requests and pipelines:

gitlab_repository
gitlab_issues
gitlab_mergeRequests
gitlab_pipelines

you can fold them into a single entry point, each with its own focused action set.

Confluence / Wiki

Instead of scattering operations across multiple tools:

searchPages
getPage
createPage
updatePage
deletePage
getChildren

collapse them into one:

wiki

driven by an action list such as:

search
get
create
update
children

Sentry

A similar shape works here too:

sentry

backed by:

searchIssues
getIssue
events
releases
projects

ELK

Same idea applies to log search:

logs

with:

search
aggregate
fields
indices

Jaeger

And for distributed tracing:

tracing

with:

search
getTrace
services
operations

None of these examples depend on the specific naming chosen. What matters is the underlying architectural approach, not the exact labels used for each action.

14. You Can Wrap Existing MCP Servers Instead of Rebuilding Them

One of the more practical benefits of this design is that you rarely need to reimplement an integration from scratch. Ready-made MCP servers already exist for many common platforms. The catch is that their default tool surface is frequently not tuned for a given agent's needs.

Rather than connecting an agent directly to a sprawling set of tools:

Agent
  │
  ├── 20 Jira tools
  ├── 20 GitLab tools
  └── 15 Confluence tools

you can insert a thin, purpose-built layer in between:

Agent
                   │
             Custom MCP Layer
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼
      Jira       GitLab    Confluence
        │          │          │
     existing   existing   existing
       APIs       MCP        MCP

That custom layer acts as an agent-facing facade. It's especially valuable when the underlying integration is functionally sound but simply exposes far more granular operations than the agent should have to reason about directly.

15. Treating MCP as an Adapter Layer

This leads to a more general architectural idea. A conventional setup looks like:

Agent → MCP → Service

A customized setup instead looks like:

Agent
  ↓
Agent-oriented MCP facade
  ↓
Service-specific MCP/API
  ↓
Service

The facade in between can take on responsibilities such as:

  • consolidating tools
  • enforcing permission checks
  • normalizing inputs
  • normalizing outputs
  • logging
  • tracing
  • caching
  • rate limiting
  • applying organization-specific policy rules

Under this view, the MCP layer stops being just a transport mechanism and becomes an actual interface layer for the agent.

16. Access Control Still Belongs at the Action Level

There's a security consequence worth calling out. If you merge:

jira_create
jira_update
jira_delete

into a single call shape like:

jira({
  action: ...
})

you cannot simply hand out a blanket permission such as:

permission: jira

and assume every underlying operation carries equal risk. Permission checks need to keep operating at the level of the individual action, for instance:

jira.search       → allowed
jira.get          → allowed
jira.create       → allowed
jira.update       → approval required
jira.delete       → denied

The consolidated tool is purely an interface-level simplification — it should never become a shortcut that silently grants broader access than intended.

17. Logs Need to Capture the Action, Not Just the Tool

Once tools are merged, recording only the tool name in your logs stops being useful. A weak log entry looks like:

tool = jira

A better one includes the action:

{
  "tool": "jira",
  "action": "search",
  "duration_ms": 142
}

And an even richer one adds further execution detail:

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

The real unit of execution is now the pair:

tool + action

rather than the tool alone:

tool

This distinction matters a lot when you're trying to debug what an agent actually did.

18. Keep the Underlying Handlers Separate

Consolidation should happen at the boundary exposed to the agent, not inside your implementation. Avoid collapsing everything into one massive handler:

async function jira(args) {
  // everything
}

Instead, dispatch to distinct functions:

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);
  }
}

Each handler stays independently testable:

jira()
  │
  ├── searchIssues()
  ├── getIssue()
  ├── createIssue()
  └── updateIssue()

This keeps your codebase maintainable even as the external tool count shrinks.

19. A Practical Rule for Deciding What to Merge

A good heuristic for whether two operations belong in the same tool is:

Would an engineer naturally group these operations under one capability?

For example, these clearly belong together:

Jira search
Jira get issue
Jira update issue

But these don't:

Jira update issue
Elasticsearch search logs
Create AWS infrastructure

A second useful check:

Would deciding between these operations typically require the same contextual information?

If the answer is yes, merging them usually pays off. If not, it's better to keep them as separate tools.

20. When Merging Tools Is a Bad Idea

Sometimes keeping tools separate is simply the right call.

Unrelated domains

database
payments
infrastructure
email

should not be flattened into something like:

enterprise({
  action: ...
})

Mixing unrelated domains into a single dispatcher just because you can doesn't make the interface more coherent.

Action sets that are too large

If one consolidated tool ends up covering fifty distinct actions, the resulting schema can become bloated and hard for the model to reason about, defeating the purpose of consolidation.

Operations with elevated privileges

Actions like:

deleteProductionData
rotateCredentials
deployProduction

might warrant their own permission boundaries even when they technically live in the same domain as lower-risk operations.

Operations with different lifecycle guarantees

Read-only calls and destructive calls sometimes work better as separate interfaces:

jira_read
jira_write

rather than being folded into one oversized tool.

As before, there's no fixed formula — this depends on the system.

21. Fewer Tools Is Not the Actual Goal

The aim isn't to shrink everything down to:

30 → 1

The real target is:

30 → smallest useful semantic surface

In one system, the right number might look like:

30 → 3

In another:

30 → 7

And in another:

30 → 12

What you're actually optimizing for is:

minimize unnecessary tool metadata

without sacrificing:

semantic clarity
validation
security
maintainability

22. A Reusable Architecture Shape

A scalable setup tends to follow this shape:

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

The model interacts with a small, clean surface. Underneath, the integration layer still has access to the full range of operations.

23. Rethinking What MCP Is For

A common mental model for MCP is:

"Turn every API call into its own tool."

That's a fine place to start.

But for systems running in production, the more useful question becomes:

What should the model actually see?

These two framings don't always lead to the same design.

Consider a REST API exposing:

GET /issues
GET /issues/:id
POST /issues
PATCH /issues/:id
POST /issues/:id/comments
POST /issues/:id/transition

There's no requirement to mirror each HTTP route as its own model-facing tool. The MCP layer can instead act as a semantic wrapper around those endpoints — much like how you'd design an API around what a specific consumer needs, rather than just exposing the underlying service or database schema directly.

24. Treating Tool Design as an Agent UX Problem

Classic API design asks:

What endpoints exist?

Designing for an agent asks a different question:

What decision is the model actually trying to make?

A human engineer integrating against an API might reach for:

getIssue
getIssueComments
getIssueTransitions
getIssueWorklogs

since each call maps cleanly to one operation.

An agent, on the other hand, may work better with:

jira_issue({
  action: "get",
  ...
})

paired with:

jira_issue({
  action: "comments",
  ...
})

because it first picks the domain, then narrows down to the specific action — a two-step routing process:

Domain
  ↓
Action
  ↓
Arguments

instead of forcing the model to solve:

One large global tool-selection problem

25. A Minimal Pattern You Can Reuse

This approach translates cleanly into 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);
    }
  }
);

The code itself stays fairly straightforward. The real design work is in answering:

  1. Which operations logically cluster together?
  2. What naming convention should the actions follow?
  3. What belongs in the tool's description?
  4. Which parameters are mandatory per action?
  5. Which actions need extra permission checks?
  6. How should responses be shaped consistently?
  7. How should each action be logged and traced?

26. Track the Impact With Real Numbers

Consolidating tools should not be a leap of faith — you need to verify it with data.

Before the change, you might record something like:

Tools: 32
Tool schema tokens: X
Average tool-selection latency: Y
Tool-selection errors: Z

After introducing the multiplexed design, you capture the same metrics again:

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

Some of the most informative signals to track are:

  • how many tools are exposed
  • the total size of the schemas
  • how many tokens the prompt or context consumes
  • how accurately the agent picks the right tool
  • how often it issues invalid tool calls
  • how often argument validation fails
  • response latency
  • how often executions actually succeed
  • whether the agent completes the overall task

The goal isn't simply to chase:

fewer tools

but rather to optimize for:

better agent performance per unit of context

27. A Broader Pattern for Agent-Facing APIs

This idea extends well past MCP itself.

You'll find the same principle relevant to:

  • function calling
  • OpenAI-style tool definitions
  • SDKs
  • custom agent runtimes
  • workflow engines
  • plugin architectures
  • REST-to-agent adapters
  • GraphQL-to-agent bridges

At its core, the pattern looks like this:

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

This isn't a database-specific trick.

It's a general pattern for designing interfaces that agents consume.

28. The Underlying Principle

The mental model to hold onto is simple:

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

The meaningful shift is not:

30 capabilities → 3 capabilities

Instead, it is:

30 exposed tools → 3 exposed interfaces

Nothing about what the system can do changes.

What changes is how much of that surface the model has to reason about directly.

Wrapping Up

As agentic systems get wired into more of your infrastructure, the number of tools available to them will only keep expanding.

Systems like Jira, GitLab, Confluence, Sentry, ELK, Jaeger, various databases, cloud providers, and internal services can each contribute dozens or hundreds of individual operations.

Handing every single one of those operations to the model as a separate tool is rarely the right architecture.

A more effective strategy is to build an agent-facing tool layer on top of them:

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

The guiding rule is:

Cut down on how many tools the model needs to reason about — not on how many capabilities your system actually offers.

An integration with thirty underlying operations doesn't have to translate into thirty tools visible to the model.

It might only need three carefully designed interfaces, each with a well-scoped action space.

This becomes even more valuable once you stop treating existing MCP servers as fixed, untouchable contracts, and instead treat them as building blocks you can wrap, reshape, and tune to match what your agent actually needs.

Going forward, good MCP design may be less about surfacing every operation an API supports, and more about crafting the smallest, most usable interface an agent needs to get its job done.