DEV Community

Christopher Hoeben
Christopher Hoeben

Posted on • Originally published at stickwithfiddle-sys.github.io

How to Reduce MCP Token Usage: Designing Context-Efficient Tool Schemas to Cut Agent Brittleness in Production

How to Reduce MCP Token Usage: Designing Context-Efficient Tool Schemas to Cut Agent Brittleness in Production

Practical schema design and server-side patterns to shrink tool descriptions, slim responses, and keep your agent's context window reserved for reasoning instead of bloat.

TL;DR: Design small, purpose-built MCP tools instead of wrapping entire APIs. Return only the fields the agent needs, use progressive disclosure to delay large payloads, and move data transformation into code-execution blocks. This keeps tool descriptions and responses compact, preserving context window for reasoning and reducing tool-selection errors.

Design Tools Around Workflows, Not API Endpoints

Design tools around the specific tasks your agent performs rather than exposing every underlying API endpoint. A compact catalog of workflow-oriented tools consumes far less context window space and reduces the chance of the LLM selecting the wrong operation.

The most tempting mistake in MCP server design is wrapping your entire API—generating 200 tools from 200 endpoints. When the LLM sees 200 tool descriptions, it burns context window space parsing them and still picks the wrong one because 200 options is not a menu, it is a phantom load. Instead, design tools around specific agent workflows. Group related operations into single, purpose-built tools with tight descriptions. A common approach is to limit the surface area to the small set of actions the agent actually performs, keeping the tool catalog compact enough to fit in the context window without crowding out reasoning space. Every extra description competes for the same limited window, so each redundant endpoint directly raises the risk of a mistaken tool call.

Rather than forcing the model to choose among granular CRUD endpoints, give it one intent-aligned operation. Exposing separate tools for user creation, email updates, and deletion wastes tokens; instead, consolidate them into one workflow-aligned tool:

{
  "name": "manage_user",
  "description": "Create, update, or delete a user record in a single call.",
  "parameters": {
    "type": "object",
    "properties": {
      "action": { "enum": ["create", "update", "delete"] },
      "user_id": { "type": "string" },
      "email": { "type": "string" }
    },
    "required": ["action", "user_id"]
  }
}
Enter fullscreen mode Exit fullscreen mode

This pattern collapses multiple CRUD operations into a single tight schema. It eliminates redundant descriptions and keeps the tool catalog small, preserving tokens for actual reasoning rather than navigation. Aim for a catalog small enough that the entire tool list fits in the context window alongside system instructions and conversation history.

Return Only the Fields the Agent Needs

Trim every tool response to the smallest payload the agent actually requires by letting callers request specific field projections and paginated slices, so a single call never wastes thousands of tokens on unused data. A bloated JSON response with 50 fields can consume thousands of tokens in one shot when the agent only needed three. Without this guardrail, a single over-fetching call can crowd out the context window needed for multi-step reasoning. Instead of dumping complete objects, default to the smallest viable payload and omit nested metadata and relations when the downstream consumer only needs identifiers and statuses. Empty arrays, null placeholders, and deeply nested relation objects still burn tokens even when they carry no actionable data, so design the schema to exclude them entirely rather than returning noise. Expose an explicit fields parameter in the tool schema so the agent can declare exactly what it needs before the server builds the response.

{
  "tool": "get_user",
  "params": {
    "user_id": "u_4821",
    "fields": ["id", "status", "last_seen"]
  }
}
Enter fullscreen mode Exit fullscreen mode

For list endpoints, return paginated slices rather than full collections. Returning an unbounded array of full records can monopolize the context window, whereas a paged response with a tight limit and only the requested surface fields preserves space for multi-step reasoning.

{
  "tool": "list_orders",
  "params": {
    "status": "pending",
    "limit": 10,
    "fields": ["order_id", "total"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Move Data Transformation into MCP Code Execution

Shift data transformation and aggregation into the MCP server so the agent receives only the final scalar or small result, eliminating the need to stream raw tables into the context window.

A tool call that returns a wide JSON blob can consume thousands of tokens in one shot when the agent only needs a single computed value. Pushing the computation into the server via a code execution block reverses this flow: the LLM sends a lightweight script or filter parameter, the server executes it against the data, and returns a minimal payload such as a single value or a few rows. This pattern replaces a large intermediate dataset with a compact answer, directly reducing the token surface area between steps and preventing context-window bloat.

A concrete implementation exposes a tool that accepts a filter expression and an aggregation metric:

{
  "name": "aggregate_sales",
  "description": "Run a server-side aggregation and return only the result.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "filter": { "type": "string" },
      "metric": { "type": "string", "enum": ["sum", "avg", "count"] }
    },
    "required": ["filter", "metric"]
  }
}
Enter fullscreen mode Exit fullscreen mode

The server-side handler executes the logic and returns a tiny payload:

def run_aggregation(table, filter_expr, metric):
    filtered = table.query(filter_expr)
    return {"result": float(filtered[metric].iloc[0])}
Enter fullscreen mode Exit fullscreen mode

By returning {"result": 1247.50} instead of a multi-row dataset, the agent’s next-step reasoning operates on a near-fixed token cost regardless of table size. The smaller response also leaves more room in the context window for subsequent tool calls.

Minimize Upfront Context with Progressive Disclosure

Register only the tools your agent needs for its current workflow phase, and defer detailed sub-schemas until intent is narrowed. This progressive disclosure pattern keeps the context window small and prevents the model from selecting specialized tools too early.

MCP servers expose every tool description and schema definition on every turn by default, so an unfiltered catalog can consume context window space before any useful work begins. Instead of registering a monolithic server with dozens of endpoints upfront, split capabilities into layers. Expose a high-level capability tool first, then dynamically attach the detailed implementation only after the agent branches into that path. A common approach is to start with a single dispatcher that advertises broad categories, then progressively reveal the specific operations relevant to the selected branch.

For example, instead of loading all CRM operations at once, register a single crm_dispatch tool that advertises only the intent categories:

# Phase 1: high-level tool registered
tools = [{"name": "crm_dispatch", "description": "Route to CRM modules: contacts, deals, or tickets"}]
Enter fullscreen mode Exit fullscreen mode

Once the model selects contacts, swap the registry to expose the specific sub-schema:

# Phase 2: narrow registry after intent is clear
tools = [{"name": "contact_search", "schema": {"properties": {"email": {"type": "string"}}}}]
Enter fullscreen mode Exit fullscreen mode

This staged registration minimizes upfront context and reduces the surface area for incorrect tool selection, which is especially important when multiple MCP servers are running in production workflows.

Enforce Response Budgets and Validate Payload Size

Cap response payloads at a per-tool token limit and strip every null or unpopulated field before the middleware passes the result to the agent. This prevents a single oversized tool return from monopolizing the context window and forcing downstream failures.

A single tool call that returns a JSON blob with 50 fields when only 3 are needed can consume thousands of tokens in one shot. In production, treat token capacity as a bounded resource by inserting middleware that estimates response token count and rejects or truncates any payload exceeding its per-tool budget. The exact threshold depends on your model's context window, but the principle is to fail fast on bloat instead of letting one call burn the entire budget. If a payload exceeds the limit, return a concise error or a truncated view rather than the full blob, preserving tokens for the next reasoning step. Layer strict schema constraints on top of that gate: omit null keys, drop empty arrays, and return only populated fields so structural noise never reaches the agent. Validating size before the agent sees the data keeps the context window reserved for reasoning, not parsing unused fields.

def enforce_budget(payload: dict, budget: int, tokenizer) -> dict:
    compact = {k: v for k, v in payload.items() 
               if v is not None and v != []}
    if tokenizer.count(json.dumps(compact)) > budget:
        raise ValueError("Tool response exceeds token budget")
    return compact
Enter fullscreen mode Exit fullscreen mode
# Pre-serialization filter: remove structural noise
clean = {k: v for k, v in raw.items() 
         if v is not None and v != {} and v != []}
Enter fullscreen mode Exit fullscreen mode

FAQ

Should I auto-generate MCP tools from my OpenAPI spec?

Auto-generating 200 tools from 200 endpoints is the most tempting mistake in MCP server design. The LLM sees hundreds of tool descriptions, burns context window space parsing them, and still picks the wrong one. A common approach is to curate a small set of workflow-specific tools manually.

How do I handle large database queries without hitting token limits?

Move the computation into the MCP server using code execution blocks so the agent receives only the final aggregated result instead of a raw dataset. A common approach is to expose a tool that accepts a filter or query and returns a scalar or small summary.

What is progressive disclosure in MCP design?

Progressive disclosure means minimizing upfront context by exposing only high-level tools initially and delaying detailed schemas until the agent narrows its intent. This prevents the model from burning tokens parsing irrelevant options on every turn.

Do tool descriptions really consume that many tokens?

Yes. Every tool description and schema definition sits in the context window, and a single bloated response—such as a JSON blob with 50 fields when only 3 are needed—can consume thousands of tokens in one shot. Keeping descriptions terse and responses sparse is essential.

Is there a safe number of tools to expose to an agent?

While no universal limit is specified in the sources, exposing hundreds of tools creates a phantom load that degrades performance. A common best practice is to limit the catalog to the specific actions the agent workflow requires, keeping descriptions terse so the model can reason about the full catalog without burning context.

References for further reading

Sources consulted while researching this guide, included so you can verify the details and go deeper. Listing them is not a claim that every line was independently fact-checked.


I packaged the setup above into a ready-to-use kit — **MCP Server Integration Pack: 12 Production-Ready Tool Schemas & Multi-Agent Configs* — for anyone who'd rather copy-paste than wire it from scratch: https://unfairhq.gumroad.com/l/fspoy.*

Top comments (0)