Controlling the token cost of MCP: measurement and design
MCP tool definitions consume context on every request. How to measure per-server overhead, design token-efficient tools, trim responses, and add observability hooks.
Every MCP server you connect has a cost you pay before the model does anything: its tool definitions — names, descriptions, input schemas — are injected into the context window, and in the common pattern they ride along on every request in the session. Connect generously and you are paying rent on tools that are never called, in both dollars and in the model’s attention.
This is not a niche complaint. The best-known cautionary tale is a 106-tool server whose definitions consumed roughly 54,000 tokens at initialization — a quarter of a smaller model’s context gone before the first user message. And research reports have measured input-token inflation of up to 236× for MCP-based workflows versus direct API calls in unfavourable cases. Treat that second number with care — it is a reported research finding about worst-case configurations, not a typical overhead — but the direction is real and the mechanism is simple: definitions plus verbose tool results, resent turn after turn.
The good news is that this cost is almost entirely a design problem, and design problems have fixes.
Where the tokens actually go
Three sinks, in rough order of typical size:
- Tool definitions. Paid on every request that includes the tool catalog. Cost scales with tool count × schema verbosity. A server with 40 tools at 300–500 tokens each is 12,000–20,000 tokens of standing overhead.
- Tool results. A tool that returns a 30,000-token JSON dump puts those tokens into context — and they stay there, resent on every subsequent turn of the conversation. One careless “list everything” call early in a session taxes every turn after it.
- Intermediate chatter. Multi-step tool workflows (search → fetch → fetch → summarize) accumulate all intermediate results even when only the final answer matters.
Note what is absent: the protocol itself is nearly free. JSON-RPC framing is trivial. The cost is your catalog and your payloads.
Measuring per-server overhead
Do not guess — measure. Three practical methods:
Method 1 — differential measurement. Send the same short prompt with and without a server attached, and diff the input-token counts your provider reports (every major LLM API returns input/output token usage per request). The difference is that server’s standing overhead. Repeat per server; you now have a league table.
Method 2 — count the definitions directly. List the tools and run the JSON through a tokenizer:
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list > tools.json
Then count tokens with your model vendor’s tokenizer or token-counting endpoint. This measures the catalog exactly as the client will serialize it, before you pay for a single conversation.
Method 3 — in-client visibility. Some clients expose this directly — in Claude Code, /context shows what is occupying the window, including MCP tool definitions, and /mcp lists connected servers. Make checking this a habit when a session feels sluggish or expensive.
Set a budget once you can measure: for example, “standing MCP overhead ≤ 10% of the context window in default team configurations”. A number turns arguments into decisions.
Design fix 1 — fewer, task-shaped tools
The biggest lever. Servers accrue tools by mirroring an API surface: one tool per endpoint, forty endpoints, forty tools. But the model does not need your API’s shape — it needs task shapes.
Compare:
- API-shaped:
list_projects,get_project,list_issues,get_issue,list_comments,get_user, … (12 tools, and the model must plan a 5-call chain, carrying each result in context). - Task-shaped:
find_issues(query, project?)andget_issue_details(id)— two tools whose implementations make the five upstream calls server-side and return only the composed answer.
Task-shaped tools cut definition count and eliminate intermediate results from context — the composition happens in your server process, where tokens are free. As a review heuristic: if a tool’s description needs another tool’s output to be useful, the pair probably wants to be one tool.
This is also a governance win — fewer tools means less to review at intake and a smaller attack surface for the tool-poisoning class in the threat model. Trim schemas too: concise descriptions, no essay-length parameter docs, no six-level nested optional objects. The description should be exactly long enough for the model to choose correctly, and structured output (outputSchema and structuredContent, in the spec since 2025-06-18) lets a tight schema replace prose explaining the return format.
Design fix 2 — trim, paginate, and link instead of dumping
Rules for the response side:
- Return what the question needs, not what the database has. Default responses to the minimal useful projection; add an
include_detailsparameter for the rare full view. - Paginate by default. Cap list responses (20–50 items) with a cursor. An agent that needs page two will ask; most never do. Cap free-text fields per item too — a “summary” field carrying full document bodies defeats the pagination.
- Return resource links, not payloads. MCP lets a tool result reference a resource by URI instead of embedding content. For large artifacts — files, logs, reports — return the link and a short summary; the client fetches content only if actually needed. This turns “30K tokens now, resent every turn” into “50 tokens now, 30K only on demand”.
- Cap and truncate defensively. Enforce a hard byte limit on any tool response, truncating with an explicit marker (
…truncated; refine your query or request the resource URI). An unbounded response path will eventually be found by an unbounded query.
When to split (or filter) a server
A server wants splitting when its tool catalog serves multiple distinct jobs and no single session uses more than a fraction of it. Signals:
- Definitions exceed a few thousand tokens while typical sessions call 2–3 tools.
- Tools cluster by audience — admin/write tools alongside everyday read tools (also a security smell: split lets you allowlist the read-only server broadly and gate the write server tightly).
- You keep lengthening descriptions to help the model choose among near-duplicate tools — choice overload is a token cost and an accuracy cost.
You do not always need to split the codebase: a gateway that filters tools/list per team gives you virtual splitting — one server, several trimmed views — without forking anything. Split the actual server when the jobs have different owners, credentials, or deployment cadences.
Looking slightly ahead: the 2026-07-28 spec revision’s extensions framework and redesigned Tasks are aimed partly at this problem space, and client-side techniques for loading tool definitions on demand are an active area across the ecosystem. The design principles above — fewer task-shaped tools, small responses — pay off under every variant.
Observability hooks: log tokens per tool call
You cannot control a cost you do not attribute. Wherever your tool calls are logged — natively or at a gateway — add token accounting:
- Per call: tool name, server, an estimated token count of the serialized result (a tokenizer estimate is fine; consistency matters more than precision), and duration.
- Per session: standing definition overhead at session start, cumulative result tokens, and the provider-reported input/output totals to reconcile against.
- Per server, weekly: total tokens attributable to each server (definitions × request count + results), so cost reviews name servers, not vibes.
A minimal pattern inside a Python tool handler:
import json, logging
logger = logging.getLogger("mcp.metrics")
def log_result_size(tool_name: str, result) -> None:
payload = json.dumps(result, default=str)
logger.info(
"tool=%s bytes=%d est_tokens=%d",
tool_name, len(payload), len(payload) // 4, # ~4 chars/token estimate
)
Crude, but a bytes-per-tool chart from this alone will usually identify your one pathological tool within a day. Route the same numbers into whatever metrics stack you already run and alert on step changes — a tool whose average response size triples after a dependency upgrade is a bug report writing itself.
The one-paragraph summary
Measure standing overhead per server (diff method or Inspector + tokenizer), set a context budget, and enforce it through design: fewer task-shaped tools with tight schemas, paginated and truncated responses, resource links instead of payload dumps, and split or gateway-filtered servers when catalogs outgrow their sessions. Then instrument tokens per tool call so the next regression is a graph, not a surprise on the invoice. Token discipline and security discipline point the same direction here — small catalogs and small responses are cheaper and safer, as the threat model makes clear from the other side.
Was this guide useful?
Thanks — noted. It shapes what gets written next.
newsletter
One practical MCP guide in your inbox. No news, no hype.
Tutorials and decision frameworks as they ship. Unsubscribe anytime.