Code execution with MCP
When an agent has hundreds of MCP tools or large tool results, let it write code that calls and filters them outside the context window. The result can be dramatically cheaper, but only with a real sandbox.
What you'll learn
- Why tool schemas and intermediate results make ordinary MCP loops expensive
- How code execution keeps fetching, joining, and filtering outside the model context
- How input tokens, output tokens, and context occupancy differ in a concrete 50,000-row example
- Why sandbox isolation, scoped credentials, output limits, and pagination are part of the pattern
Before you start
At 3 a.m., a finance agent receives a simple question:
Which open orders are more than 30 days overdue, and what is their total value?
The company has 300 MCP tools spread across its database, CRM, billing, and warehouse servers. The answer needs one database query and a little arithmetic.
Yet the ordinary agent loop may first send all 300 tool descriptions to the model. The query then returns 50,000 rows. Those rows go back through the model so it can keep three of them and add a column.
The model is being used as a very expensive for loop.
This is the problem that code execution with MCP addresses. The agent writes a short program. That program calls MCP tools, filters and joins their results in a restricted environment, and returns only the useful answer to the model.
The large dataset still exists. It simply does not enter the model’s context.
The problem: everything flows through the model
A token is a small unit of text used by a language model. A context window is the text the model can read for one turn: instructions, conversation history, tool descriptions, and tool results all count.
Keep three measures separate:
- Input tokens are sent to the model in a particular request. Tool definitions and tool results count when the host places them in that request.
- Output tokens are generated by the model. Generated code is output on the turn where the model writes it.
- Context occupancy is the text loaded into the model’s context. If the host includes generated code or earlier results in a later request, they occupy that context and are input tokens for that request.
A tool result is not model output merely because it came from a tool. It becomes model input when the host sends it to a later model turn.
Traditional tool use puts two kinds of unnecessary material into that window. First are tool definitions: names, descriptions, and input schemas. If 300 definitions average 350 tokens:
300 × 350 = 105,000 tokens
Those are input tokens and context occupancy whenever included in a request. Real schemas vary, so measure yours.
Then come intermediate results. For a deliberately chosen toy calculation, assume the database’s exact serialized result is 45,000 tokens. Measure the actual JSON or CSV payload with your model’s tokenizer. When the host sends that result to the next model turn, all 45,000 tokens become input and occupy attention, even if the model keeps only three rows.
Long multi-turn histories also make the model slower to reason over and more likely to miss relevant details. This degradation is commonly called context rot. MCP is not itself the cause; the expensive design is placing every schema and raw result directly in model messages.
The mental model: a model-directed program
The ordinary loop is:
- The host gives the model tool definitions.
- The model chooses a tool.
- The host calls the MCP server and appends the result.
- The model reads the result and chooses what to do next.
Code execution changes who receives the result. The model gets documentation for an MCP API and writes a program that calls tools, filters or joins results in the execution environment, and returns a small structured object. The sandbox—not the model—holds the raw rows and local variables.
The MCP server still receives a tool call. In traditional mode, the model receives its result; in code mode, the sandbox receives it and the model receives only the program’s bounded return value. The data is not free: the sandbox still uses memory, CPU, network bandwidth, and database capacity.
A concrete code-mode task
The exact bridge differs by host. MCP defines how tools and resources are exchanged; it does not mandate a Python object named mcp.orders. Treat this as conceptual host code, not a universal MCP API.
This example uses USD, rejects mixed currencies, and rounds once at the output boundary.
import json
from datetime import date
from decimal import Decimal, ROUND_HALF_UP
# The host supplies this restricted MCP bridge inside the sandbox.
today = date(2026, 8, 28)
currency = "USD"
# In production, select only needed fields and consume pages or a stream.
orders = mcp.orders.search(
status="open",
fields=["order_id", "due", "amount", "currency"],
)
overdue = []
for order in orders:
if order["currency"] != currency:
raise ValueError("mixed or unexpected currency in order result")
due_date = date.fromisoformat(order["due"])
if (today - due_date).days > 30:
overdue.append(order)
total = sum(
(Decimal(str(order["amount"])) for order in overdue),
Decimal("0"),
)
total = total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
result = {
"overdue_count": len(overdue),
"overdue_total": str(total),
"currency": currency,
}
# The host parses this bounded stdout as one JSON document.
print(json.dumps(result, separators=(",", ":")))
If exactly three matching USD orders contain amounts of 1,200.00, 950.50, and 310.25, the program emits:
{"overdue_count":3,"overdue_total":"2460.75","currency":"USD"}
The total is a JSON string to preserve decimal money exactly. The host should parse bounded stdout as JSON, validate the fields and types, and keep logs on a separate channel. The model does not see the other 49,997 rows.
The causal chain is:
- Code performs the repetitive work.
- The work produces no model-visible tokens unless the program prints it.
- Only the printed result returns to the model.
- A smaller result means fewer input tokens and less irrelevant context.
Do not print rows inside the loop. A debug statement can turn a successful reduction into a 50,000-line invoice. Also let the database do what it is good at: filter by status and date, select needed columns, and aggregate where possible. Handle pagination explicitly; processing only page one can produce a neat but wrong answer.
The arithmetic behind the reported saving
For a deliberately simplified single-request accounting, assume the traditional request contains:
- 105,000 input tokens of tool definitions;
- 45,000 input tokens in the serialized order result;
- about 2,000 input tokens for the question and instructions.
That is:
105,000 + 45,000 + 2,000 = 152,000 input tokens
This describes one request under stated assumptions. It excludes model output, later requests, growing history, and repeated schemas.
A real trace separates the categories:
- Traditional mode: the model receives schemas, emits a tool call, then receives the result as input on a later turn and emits another action.
- Code mode: the model emits generated code as output; the sandbox receives and processes tool results; a later model turn receives only the bounded JSON summary.
If the host echoes generated code into that later request, the code occupies context and is input on that request. It is not output on both turns.
Anthropic’s published illustrative scenario compares roughly 150,000 tokens for traditional tool use with roughly 2,000 for code execution:
(150,000 - 2,000) ÷ 150,000 = 0.9867
That is approximately 98.7 percent less illustrative context payload—not a universal input-token, total-token, cost, or latency result.
Savings depend on schema size, result size, model turns, history, and sandbox overhead. Small jobs may save nothing: code generation and sandbox startup can cost more than a direct call. Model pricing may distinguish input and output tokens, and a code-mode task may retry after an execution error. Measure input tokens, output tokens, context occupancy, tool time, sandbox time, and total turns.
The production pattern
A safe deployment should give each layer a narrow job:
- Model: writes a program using documented namespaces, argument shapes, and a bounded output contract.
- Host validator: rejects obvious forbidden imports, filesystem access, subprocesses, unapproved destinations, and write-capable tools. These checks help, but the sandbox is the security boundary.
- Capability broker: authorizes tenant-scoped, short-lived abilities such as “read orders for tenant 482.” Enforce tool permissions, row limits, and destination allowlists; never expose a long-lived database password.
- Sandbox: enforces runtime, memory, disk, output-byte, and tool-call limits. Use a read-only filesystem and disable network access except for required MCP endpoints.
- Runtime: returns validated structured data such as
count,total,currency,warnings, andsource_pages. Report truncation instead of silently returning incomplete results. - Side-effect controls: separate reads from writes. Use dry runs, idempotency keys, per-operation authorization, quotas, and approval before consequential actions.
Record the generated program, sandbox identity, redacted tool calls, pages processed, resource usage, and final output. Raw tool output also remains less likely to steer the model when it stays in the sandbox, but this is not a complete prompt-injection defense: tool metadata and fetched data are still untrusted.
Where the pattern breaks
Fake code mode still sends all schemas to the model or feeds row-by-row logs back into context. Inspect traces, expose a compact execution API, suppress raw stdout, and return an explicit bounded result.
An over-restricted sandbox produces denied connections, missing dependencies, or timeouts. Fix the capability contract—allow the required endpoint, use preinstalled dependencies, or move filtering into the data service—instead of granting production credentials and open internet access.
Incomplete pagination or silent truncation produces plausible totals that disagree with the source. Require cursor loops, track pages_read and rows_read, and return warnings when more data exists.
Uncontrolled side effects can cause duplicate tickets or refunds. Make write tools unavailable to read-only jobs; for authorized writes, require approval, quotas, idempotency keys, and mutation logs.
Finally, code execution can be slower than ordinary calls for small jobs. Use it for many tools, large intermediate results, repeated transformations, or joins across sources. A direct tool call is simpler for “What is the weather in Nairobi?”
In one breath
- Ordinary MCP loops put schemas and raw intermediate results in model context.
- Code execution lets a program call MCP tools and process results in a sandbox, returning only a bounded result.
- Roughly 150,000 versus 2,000 tokens is an illustrative 98.7 percent context-payload reduction, not a production guarantee.
- Input tokens, model output, and later context occupancy are different accounting categories.
- Isolation, scoped capabilities, egress controls, resource limits, pagination checks, and side-effect approval are essential.
Quick check
Quick check
Next
For the wider picture, read about context engineering, then pair this pattern with cost and latency control and reliability for agent side effects.
Practice this in an interview
All questionsTool poisoning is malicious instruction content in a tool description or result; cross-server shadowing uses one server’s names or content to influence or misroute another server’s capability; a rug pull changes a previously reviewed capability later. Defenses combine origin-aware namespaces, isolated trust contexts, capability snapshots and change review, exact-argument authorization, sandboxing, and runtime monitoring.
MCP is an open protocol originally introduced by Anthropic that standardizes how an AI host discovers and uses tools, resources, and prompt templates exposed by separate servers. It solves bespoke integration sprawl by giving each server one common interface, while leaving model orchestration, permissions, and user approval to the host.
Tool use lets an LLM emit a structured request for an external function, which the application validates, authorizes, executes, and returns to the model. Reliable tools have clear descriptions, narrow scope, strict typed inputs, least-privilege access, idempotency, and useful structured errors.
Tool calling extends the LLM's output space to include structured function invocations. The model emits a JSON object naming a tool and its arguments; the runtime executes the tool and feeds the result back as a new message. An agent is a loop that repeats this cycle — observe, think, act — until the task is complete or a stopping condition is met.