When MCP results overload the context.
The MCP specification paginates the tool catalog and says nothing about the size of a tool call's result, so when a server hands your agent 100,000 tokens, the host you run and the settings you left at default decide what happens. You do not control those servers, and they change under you. The ceiling, the overflow behaviour and the eviction policy therefore have to be yours: set per tool, tested against your largest customer rather than your demo tenant, and measured in production. And every remedy on offer (spill to file, code execution, clearing, compaction) moves the cost somewhere else rather than removing it.
Step 1Measure the result before anything shapes it, in tokens and in characters.
Most teams first meet their result sizes as a symptom (a 429, a stalled turn, an agent that "forgets" the task), and by then the log shows only what the host truncated or spilled. Instrument the point where the raw MCP response arrives, before any host or middleware touches it, and record one row per call:
- Server, tool, and the arguments that drive size: page size, field selection, date range.
- Characters and estimated tokens, both. Hosts do not agree on the unit. Claude Code saves text results over 50,000 characters to a file whatever its token setting says. The reporter of claude-code issue #97622 measured a 50,000-character result at about 34k tokens of Latin-script text and about 48k tokens of Greek. A threshold tuned on English fires at a different point for everyone else.
- What the host did with it: passed through, truncated, saved to a file, or errored.
Then rank tools by p99 size, not by the mean: overload is a tail problem, and the tail grows with the account. GitHub's server returned 508KB for a list of 30 pull requests (its full record is in the page was counted in records). Figma's own client-troubleshooting page quotes a get_design_context response of 351,378 tokens against a 25,000-token cap. The reference Memory server's read_graph is reported in issue #3953 to return 50k–80k+ tokens after six weeks of use. The tool never changed; the store grew.
That is why fixtures matter more than thresholds. A test tenant with 11 projects passes every check; the customer with 1,400 is the one who pages you. Build fixtures from the largest account you are allowed to copy, or synthesise one at that scale, before you set a single number.
Step 2Set ceilings per tool, and know which knobs belong to you.
A single global cap is always wrong for somebody: high enough for log search lets the list tools flood the window, low enough for the list tools makes log search useless. The per-tool mechanisms differ in who owns them and what unit they count. As of October 2026:
- Claude Code. It warns at 10,000 tokens, a fixed threshold. The default cap is 25,000 tokens, which you can raise with
MAX_MCP_OUTPUT_TOKENS. That variable only governs tools that have not declared their own limit. A server can declare_meta["anthropic/maxResultSizeChars"]in itstools/listentry and raise one tool's threshold up to a hard ceiling of 500,000 characters. That is the server author's knob, not yours, so audit which connected tools declare it: they have opted out of the ceiling you think you set. - Codex.
tool_output_token_limitsets the budget for tool outputs stored in history. The per-toolmcp_servers.<id>.tools.<tool>.output_token_limit(merged in PR #41421 on 2026-08-28) overrides it for one MCP tool, before a standard 20% serialization allowance. When plugin and user policies overlap, the most restrictive limit wins. The default comes from the model catalog and is not documented, so set it explicitly. - Gemini CLI.
tools.truncateToolOutputThresholdtruncates at 40,000 characters by default; 0 or a negative value disables truncation. - claude.ai and Claude Desktop. Connectors have a maximum tool result of about 150,000 characters. It is not configurable, and the docs do not say what happens above it.
- Cursor. No documented ceiling. Long MCP output is written to a file the agent can read.
- VS Code Copilot. No documented ceiling. Over-long results are truncated with a hint to read the rest that fails (the case is in Step 3).
- OpenAI Agents SDK. No documented ceiling. Results are forwarded as they are, and resource blocks become their JSON serialization as text.
Rate limits are a ceiling too, and bite earlier than the window: the first overflow report against GitHub's server was a 429 on the follow-up call, not a context overflow (the page was counted in records has the numbers). A per-task token budget like the one in cost control in the loop should count tool results on the way in, not only model output on the way out. Keep each ceiling next to its measured p99 in the server allow-list; a ceiling with no measurement beside it is a guess.
Step 3Choose the overflow behaviour, then test the round trip, not the happy path.
When a result crosses the ceiling, the host does one of three things, and each fails in its own way.
- Spill to a file. Claude Code saves an over-cap successful, non-image result to the session's
tool-resultsdirectory and replaces it with a message naming the path. Cursor writes long MCP output to a file the agent can read. Nothing is lost, but the model now works through a pointer. - Truncate. Gemini CLI cuts at its character threshold. Claude Code truncates error results: an
isErrortext over about 11,000 characters keeps only its first 5,000 and last 5,000 characters. Fine for a stack trace; misleading if the cause sits in the middle. - Error. The call fails and the agent has to retry with narrower arguments. The most honest option, if the error text names the remedy.
Spill-to-file's failure cases are already public. Use them as your test suite:
- Can the agent read part of it? In notion-mcp-server issue #206, a 41,207-token page came back as JSON packed onto about five lines. The spill file could not be read partially, because the read tool pages by line offset, and a plain read failed with "exceeds maximum allowed tokens (25000)". A spill only helps if the file is line-structured and greppable.
- Can it edit and write back? In atlassian-mcp-server issue #106, a Confluence page body of 50–70KB was saved to disk, so the model never saw it and could not rebuild it to call the update tool. It would have had to emit about 15K tokens inline. Any read-modify-write workflow breaks the moment the read spills.
- Does the remedy hint actually work? In vscode issue #311068, VS Code truncated a 50KB–999KB file result and pointed the agent at a
vscode-chat-response-resource://URI to read the rest. Reading it failed as "outside the workspace", which left the agent stuck. - Does the host tell the model the right thing? Version 2.1.105 added format-specific recipes (jq for JSON). Version 2.1.287 fixed Claude being told to page large JSON results with Read's offset and limit. The overflow text is part of your system, and a host upgrade changes it.
For each heavy tool, write one test that crosses the ceiling and then asks the agent to do the next real thing: find a field, change a value, write the object back. Pass means the task completes, not that the call returned. The server-side half is in large results & pagination in MCP; how GitHub's server learned it is in the page was counted in records.
Step 4Keep bulk data out of the loop, and price where it goes instead.
The cheapest token never enters the main context. There are two ways to arrange that, and each has its own bill.
- A subagent for big reads. Anthropic's context-engineering guidance describes subagents that "might explore extensively, using tens of thousands of tokens or more" and return "only a condensed, distilled summary". The cost moves to the subagent's tokens and the summary's fidelity: the orchestrator cannot know what the summary left out. Log the subagent's raw inputs; when the answer is wrong, that is where the evidence lives.
- Programmatic tool calling, or code execution. The model writes a script, the script calls the tools in a sandbox and filters the data there, and only the result reaches the model. Anthropic's example of the problem: a two-hour meeting transcript copied from one tool to another passes through the model twice, roughly 50,000 extra tokens. The widely quoted 150,000 → 2,000 tokens (98.7%) is Anthropic's illustrative worked example, not a benchmark. Its measured figure for programmatic tool calling on complex research tasks is an average drop from 43,588 to 27,297 tokens, or −37%. Plan on the measured number.
Code execution is a real change of architecture, not a setting. On the Claude API it requires the code execution tool (code_execution_20260120 or later), it is not available for Claude Haiku 4.5, and it is not eligible for zero data retention. The docs warn that allowed_callers is not a security boundary. Anthropic lists the cost as sandboxing, resource limits and monitoring, "operational overhead and security considerations that direct tool calls avoid". You also lose the cheap audit trail: the transcript was a list of tool calls, and now it is a program whose effects you must reconstruct. Code as action covers what the action log has to capture to keep that reviewable.
Do not let a definition-side number justify a result-side project. Tool search's "over 85 percent" reduction, Cloudflare's 1.17 million → about 1,000 tokens and Cursor's −46.9% all measure tool definitions loaded up front. They do nothing for a single call that returns 100K tokens. A deck that blends the two will fund the wrong fix.
Step 5Evict what is stale, and know what eviction cannot do.
In a long session, most of the window is old tool results the agent has already acted on, and clearing them is, in Anthropic's words, "one of the safest lightest touch forms of compaction". As of October 2026:
- Anthropic context editing (beta, header
context-management-2025-06-27). Theclear_tool_uses_20250919strategy removes the oldest tool results first once input crosses a trigger, 100,000 input tokens by default. By default it keeps the last 3 tool uses.clear_at_leastskips the edit entirely unless it can free at least that many tokens, so a trivial saving does not cost you a rewritten prompt.exclude_toolsnames the tools whose results must never be cleared. - Anthropic compaction (beta, header
compact-2026-01-12, edit typecompact_20260112). Summarises history at a trigger: default 150,000 input tokens, minimum 50,000. - OpenAI Responses:
context_management: [{ type: "compaction", compact_threshold: … }]runs server-side compaction once the rendered token count crosses your threshold. The result is an encrypted item that is "opaque and not intended to be human-interpretable". Separately,truncation: "auto"drops items from the beginning of the conversation when input exceeds the window. It is blunt oldest-first dropping; the default,disabled, fails the request with a 400.
# POST /v1/messages, with the header anthropic-beta: context-management-2025-06-27 { "model": "<your-model>", "max_tokens": 4096, "tools": [ /* ...MCP-backed tools... */ ], "messages": [ /* ... */ ], "context_management": { "edits": [{ "type": "clear_tool_uses_20250919", "trigger": { "type": "input_tokens", "value": 60000 }, "keep": { "type": "tool_uses", "value": 3 }, "clear_at_least": { "type": "input_tokens", "value": 10000 }, "exclude_tools": ["getConfluencePage"] // the read a later write depends on }] } }
The trigger sits below the default on purpose: set it from your Step 1 data, where quality starts to fall, not where the window ends. exclude_tools applies Step 3's round-trip lesson: never clear the read a pending write depends on.
Two limits keep this honest. First, eviction is not free quality. arXiv 2606.00408 finds the gains from masking stale observations follow an inverted U across 4B–284B backbones, and masking can hurt when it removes evidence the model would have used. Tune keep and the trigger against your eval, not the token graph. Second, neither clearing nor compaction does anything for one oversized result. Both act on history, and a single 100K-token payload does its damage on the turn it arrives. That is Step 2's job. The summarisation trade-offs are in context compaction.
Step 6Alarm on drift and regression-test the overflow path.
Your ceilings were set against today's servers, and the servers will change. GitHub's server shows the pattern (the page was counted in records): one endpoint family after another hit the problem and got a minimal_* response type. A release like that changes a tool's p99 overnight, and the next field a vendor adds moves it back. This is third-party tool drift, and result size is one of its most measurable symptoms.
- Alert on per-tool p99 size drift, keyed by server version where the server reports one. A step in either direction means the contract moved; shrinkage can mean fields you relied on are now opt-in.
- Alert on overflow events as a rate: spills, truncations and size errors per thousand calls, per tool. A rising rate on one tool means a customer's data has crossed a threshold your fixtures never reached.
- Assert that the truncation marker reaches the model. Well-built servers say so in-band, with a truncation flag next to the remedy (the pattern in Step 4 of large results & pagination in MCP). Middleware, a host upgrade or your own summarisation can strip it. Test that the model's input still contains it; an agent that does not know its data is partial reports a confident wrong count.
- Pin and re-test on host upgrades. Overflow behaviour is host code and changes between releases. Run the Step 3 round-trip tests on every host version bump, as you would for a model change.
- Keep a canary large-account eval. Run a small, fixed set of real tasks against the large-account fixture on a schedule, and score them on outcome. It is the one test that catches data growth, server drift and host changes together.
If none of this is in place, do two things this week. First, log raw result size and host action per tool call, so you have a p99 to argue from. Second, take your three heaviest tools, push each one past its ceiling against a large-account fixture, and ask the agent to complete the next step of a real task. Whatever fails there is your real ceiling and your real overflow behaviour, whatever the settings file says.