Most MCP token waste begins before an agent starts reasoning. It is already present in the tool catalog, the input schemas, the API response, and every intermediate result that gets carried into the next turn.

The Model Context Protocol makes it straightforward to connect AI applications to APIs, databases, repositories, monitoring systems, and internal tools. But exposing an existing REST API through MCP one endpoint at a time is usually the wrong abstraction. A frontend can receive thousands of records and discard most of them cheaply. An agent has to read those records token by token.

That difference changes the design goal. A good MCP server is not a transparent proxy. It is an agent-oriented interface that returns exactly enough information for the next decision.

Where an MCP integration spends context
01Tool catalogNames, descriptions, schemas
02ArgumentsParameters and enum values
03ResultsRecords, fields, logs, metadata
04RepetitionFollow-ups and duplicate calls
05ReasoningWhat remains for the actual task

The interface competes with the task for the same context window.

Optimize the total cost of completing the task correctly, not the size of one isolated response.

Shape the interface around intent.

01Design tools around intent

Start with the outcome the agent needs, not the endpoint your backend happens to expose. A resource-oriented customer API might become five MCP tools:

list_customers
get_customer
list_customer_orders
list_customer_tickets
list_customer_transactions

The agent now has to discover those tools, call several of them, carry every response through context, and assemble the answer itself. An intent-oriented surface is smaller and more useful:

search_customers
get_customer_context
summarize_customer_activity

One tool can still call several internal APIs. The difference is that the server does the mechanical work and returns a compact decision-ready result:

{
  "customer_name": "Example GmbH",
  "account_status": "active",
  "open_issues": 2,
  "last_order_date": "2026-07-21",
  "risk_summary": "Two support tickets are overdue"
}

The same pattern works for deployment tooling. A summarize_pipeline_failure tool can retrieve the failed run, locate the failed step, isolate the relevant log lines, remove repetition, and return the probable cause plus the next action. The agent should not need thousands of log lines to explain one failed deployment.

02Expose fewer tools at once

Tool definitions consume context even when no tool is called. Names, descriptions, input schemas, parameter descriptions, enums, and output schemas all have a cost. A server with hundreds of tools can spend a meaningful part of the context window describing capabilities the agent will never use.

Keep small servers focused. For a large platform, use progressive discovery:

User request
    ↓
search_tools("find failed GitHub deployments")
    ↓
Load two relevant tool definitions
    ↓
Execute the selected tool

A search result needs only a stable tool name and a short summary. Load the detailed schema after the agent selects a candidate. This is especially effective when one MCP server fronts a broad SaaS or internal platform.

Constrain what reaches the model.

03Filter data before it reaches the agent

Never make the model retrieve a large collection just to discard most of it. Replace list_all_incidents() with a search tool that can express the actual question:

search_incidents(
  status="open",
  severity=["critical", "high"],
  service="payments",
  created_after="2026-07-27T00:00:00Z",
  limit=20
)

Filtering, sorting, deduplication, joining, aggregation, and counting are deterministic operations. Run them in the database, upstream API, or MCP server. Code is faster, cheaper, and more reliable at this work than a language model.

04Support field selection and detail levels

Not every task requires the full object. Let the caller choose fields, related data, or a clear detail level:

{
  "fields": ["id", "status", "owner"],
  "detail": "summary",
  "include": ["recent_failures"],
  "limit": 10
}
DetailUse it forTypical fields
minimalIdentification and routingID, title, status
summaryThe next decisionOwner, health, review state
fullExplicit deep inspectionComplete upstream object

Default to minimal or summary. Make full responses an explicit choice.

05Paginate every potentially large collection

Any result that can grow needs a boundary. Cursor pagination gives the agent enough information to continue without encouraging it to fetch everything:

{
  "items": [],
  "returned": 20,
  "has_more": true,
  "next_cursor": "eyJvZmZzZXQiOjIwfQ=="
}

Conservative defaults matter. Ten to twenty-five records is often enough for an agent to choose the next step. Set a hard maximum and never offer an unlimited mode by accident.

06Return summaries first and details on demand

Large documents, logs, email threads, source files, and incident histories benefit from two-stage retrieval. Search returns identifiers, concise summaries, and relevance. A second tool retrieves one section or a small set of selected records.

{
  "summary": "27,418 events found; 14 contain critical error patterns.",
  "result_handle": "result_8fc19",
  "expires_at": "2026-07-28T14:00:00Z"
}

Store the full intermediate result outside the conversation. Follow-up calls such as get_result_page(handle, cursor) and get_result_details(handle, item_ids) let the agent drill down without carrying the entire dataset.

The preferred retrieval pipeline
Raw backend data Filter at source Aggregate in code Return a compact result Reason with signal

Compute before returning.

07Move calculations into the MCP server

If the user asks for totals, growth, ranking, or a grouped summary, return that result rather than the raw records required to calculate it.

summarize_sales(
  period="2026-Q2",
  group_by="region",
  metrics=["revenue", "orders", "growth"]
)
{
  "period": "2026-Q2",
  "regions": [{
    "region": "DACH",
    "revenue": 1250000,
    "orders": 418,
    "growth_percent": 7.2
  }]
}

Databases and application code should handle sums, averages, percentages, grouping, date arithmetic, statistics, joins, and deduplication. The model’s job is to interpret the answer.

08Support bounded batch operations

Three repeated calls add latency and three separate results to the conversation. Prefer get_issues(ids=["A-1", "A-2", "A-3"]) over calling get_issue three times. Use the same approach for updates:

{
  "updates": [
    { "id": "A-1", "status": "closed" },
    { "id": "A-2", "priority": "high" }
  ]
}

Batch calls still need a ceiling. A limit such as 50 records prevents many small calls from becoming one unreasonably large response.

Make every call predictable.

09Use stable, structured responses

A result should not switch between an array, a string, and null depending on what happened. Keep one predictable envelope:

{
  "items": [],
  "returned": 0,
  "has_more": false,
  "next_cursor": null
}

Define an outputSchema and return structuredContent where the client supports it. The MCP tools specification explains how output schemas make results easier to validate and consume.

10Keep tool schemas concise and clear

The smallest schema is not necessarily the cheapest schema. If a vague name causes an incorrect call, the retry costs far more than the tokens saved.

Too much This tool provides the capability to retrieve and search through all customer records available…
Enough Search customers by name, email, status, or organization. Returns up to 20 summary records.

Prefer familiar parameter names such as query, status, fields, limit, cursor, and created_after. Avoid both sentence-length names and ambiguous abbreviations. The right target is the smallest schema an agent can use correctly.

11Remove irrelevant metadata

Backend responses often contain database shard IDs, serialization versions, framework metadata, request traces, debug output, permission trees, duplicate timestamps, and internal identifiers. None of that belongs in the model context unless it supports the next decision.

Most agent-facing results need a human-readable name, a stable follow-up ID, the current status, the owner, relevant dates, a concise summary, a useful URL, and a possible next action. Design that response deliberately instead of forwarding the upstream object.

12Return actionable errors

A stack trace explains the implementation failure but rarely helps the agent recover. Return a stable error code, a human-readable message, whether the error is recoverable, and the next valid action:

{
  "error_code": "CUSTOMER_NOT_FOUND",
  "message": "No customer exists with ID C-1842.",
  "recoverable": true,
  "suggested_action": "Use search_customers to locate the correct ID."
}

Keep full upstream errors behind a dedicated debugging path. Normal tools should provide the information required to repair the call, not an application stack trace.

Optimize the operating model.

13Cache stable information

Tool definitions, product catalogs, country lists, static documentation, lookup tables, and repeated session searches are good cache candidates. Return freshness information when it matters:

{
  "data": {},
  "cached": true,
  "generated_at": "2026-07-28T10:30:00Z",
  "ttl_seconds": 300
}

Stable ordering can also improve prompt-cache reuse. Do not hide staleness: rapidly changing values such as deployment health should always show when they were generated.

14Use code execution for complex processing

Some workflows are too flexible for dozens of specialized tools. A controlled code environment can load tools on demand, process intermediate records, and return only the final answer:

orders = await sales.search_orders({
  status: "pending",
  created_after: "2026-07-01"
})

const highValue = orders.filter(order => order.amount > 10_000)

return {
  count: highValue.length,
  total_value: highValue.reduce((sum, order) => sum + order.amount, 0)
}

The main model sees the count and total, not every order. Anthropic’s code execution with MCP article describes the same benefits: progressive disclosure, local filtering, and fewer intermediate results in context.

Code execution is an architectural boundary, not a shortcut around security. Isolate the filesystem, network, secrets, dependencies, CPU, memory, and execution time, and retain an audit trail. Reviewing generated source code is not a substitute for a secure sandbox.

15Use AI summarization as a fallback

Passing every oversized response through a cheaper model can help, but it is not the first optimization. Apply deterministic reductions first:

  1. Filter at the source.
  2. Select only the required fields.
  3. Aggregate on the server.
  4. Paginate the result.
  5. Return summaries and result handles.
  6. Use an AI summarizer only when necessary.

Summarization can remove the exact detail the next step needs. Retain the original result outside model context and return a handle for targeted retrieval.

A reusable search-tool shape.

The following pattern combines server-side filters, explicit fields, conservative defaults, pagination, stable output, and a result handle:

{
  "name": "incidents.search",
  "description": "Search incidents. Returns compact summaries.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": { "type": "string" },
      "status": {
        "type": "array",
        "items": { "enum": ["open", "resolved"] }
      },
      "severity": {
        "type": "array",
        "items": { "enum": ["critical", "high", "medium", "low"] }
      },
      "created_after": { "type": "string", "format": "date-time" },
      "fields": {
        "type": "array",
        "items": {
          "enum": ["id", "title", "status", "severity", "service", "owner"]
        },
        "default": ["id", "title", "status", "severity"]
      },
      "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 },
      "cursor": { "type": "string" }
    },
    "additionalProperties": false
  }
}
{
  "total_estimate": 143,
  "returned": 10,
  "items": [{
    "id": "INC-1932",
    "title": "Payment API elevated errors",
    "status": "open",
    "severity": "critical"
  }],
  "has_more": true,
  "next_cursor": "cursor_10",
  "result_handle": "results_9da21"
}

Optimize in the order that pays.

When improving an existing MCP server, start with the changes that remove the most unnecessary context with the least ambiguity:

  1. Reduce returned records. Add filters, limits, and pagination.
  2. Reduce returned fields. Keep only what supports the next decision.
  3. Reduce exposed schemas. Specialize servers or discover tools dynamically.
  4. Move processing server-side. Aggregate, calculate, join, and deduplicate in code.
  5. Add summaries and handles. Retrieve detail only when requested.
  6. Batch repeated work. Combine calls without creating unlimited payloads.
  7. Cache stable data. Avoid rebuilding information that has not changed.
  8. Add AI summarization carefully. Use it after deterministic reductions.

A pre-publish review checklist.

Tool design

  • Does the tool represent a real user intent?
  • Is it too closely coupled to one backend endpoint?
  • Can common calls become one agent-oriented operation?
  • Is its name clear and specific?

Input design

  • Can the server filter before returning data?
  • Are pagination and a conservative default limit present?
  • Can the caller choose fields or detail level?
  • Are parameter names understandable without an essay?

Output design

  • Does the agent need every field and record?
  • Is the response structured and predictable?
  • Are full logs and documents opt-in?
  • Would a summary, reference, or handle be enough?

Operations

  • Can repeated calls be batched?
  • Can stable results be cached safely?
  • Do errors explain how to recover?
  • Is large intermediate data kept outside context?

Measure task efficiency, not JSON size.

A small response is not automatically better. If it causes three extra calls, total token use and latency can increase. Measure the complete task:

Use evaluations grounded in real tasks. Anthropic’s guide to writing effective tools for agents is a useful companion: tool ergonomics and token efficiency should be tested together, because a tiny interface that the model repeatedly misuses is not efficient.

Audit your MCP API with a coding agent.

The prompt below is ready to paste into an agent coding IDE. It asks the agent to review the current repository against all fifteen rules, apply only small and safe improvements, and leave architectural changes as explicit recommendations.

MCP API audit prompt / Markdown

Does the agent need every field and every record in this response to decide what to do next?

When the answer is no, the response is probably too large. The best MCP layer behaves less like plumbing and more like a carefully designed boundary between deterministic systems and an agent that needs high-signal context.