MCP at 10,000 Tools: Why Code Mode Wins
At 10,000 tools, MCP catalogs eat your context before the task even starts. Here's where lazy loading stops helping, and how Code Mode runs the workflow in a sandbox instead.
At 10,000 tools, MCP catalogs eat your context before the task even starts. Here's where lazy loading stops helping, and how Code Mode runs the workflow in a sandbox instead.
"Just expose another tool."
For a small agent, that works.
A tool-calling agent receives a list of functions, each with a name, a description, and an input schema. The model reads the list, picks a function, waits for the result, then decides what to call next.
The loop looks like this:
Repeats from the first step.
The problem begins when we expose hundreds or even thousands of tools to the model. Every tool comes with a name, a description, and an input schema. If the client loads the full catalog up front, those definitions take up context before the task begins.
The context keeps growing after execution starts. Each result comes back to the model, which has to read it, carry the relevant values forward, and decide on the next call. A long workflow can spend more model turns moving IDs and records between tools than reasoning about the user's request.
Modern models are good at writing code, but conventional tool calling makes little use of that ability. A model can write a loop, handle a failed request, run calls concurrently, and filter a result. With direct tool calls, it has to express the same plan one operation at a time, with another inference between each step.
Code Mode lets the model write that program directly. In an MCP setup, one common design exposes an execute tool that accepts a short TypeScript or Python program. A sandbox runs the program against approved capabilities and returns a compact result. MCP still connects the agent to the underlying services, while the sandbox handles the orchestration and intermediate data.
One point causes most of the confusion around Code Mode, so it is worth stating early. The model does not write the tools. Developers still implement each capability, with its authentication and its API calls, exactly as they would for an ordinary tool. The model only writes the glue: the loop, the filter, and the calls to those pre-built functions. Code Mode changes who runs the orchestration, not who builds the tools.
A tool-capable model does not receive an API client. It receives text describing a callable function, including the arguments the host will accept.
{
"name": "list_pull_requests",
"description": "List pull requests by state and author",
"input_schema": {
"type": "object",
"properties": {
"state": { "type": "string" },
"author": { "type": "array", "items": { "type": "string" } }
},
"required": ["state"]
}
}The model reads the definition and emits a structured call. Application code validates the arguments, checks authorization, and sends the request. A GitHub integration may define separate tools for pull requests, issues, comments, reviews, checks, and releases. Connecting deployment or incident tooling adds another set of definitions.
MCP did not require clients to place the entire catalog in the prompt. That is a client design choice. The MCP architecture documentation defines how clients and servers exchange context and invoke tools, but it leaves context management to the host application.
The ordinary tool loop
Schemas enter before the first decision. Raw results return to the model before every subsequent call.
Anthropic documents one example in its tool search documentation. A setup with GitHub, Slack, Sentry, Grafana, and Splunk can spend about 55,000 input tokens on tool definitions before the model begins the task. Anthropic reports that loading three to five relevant tools on demand often reduces that cost by more than 85 percent.
Cloudflare provides a larger example. Its API has more than 2,500 endpoints. Cloudflare calculated that converting the complete API into a flat MCP tool catalog would require roughly 1.17 million tokens. Its alternative MCP interface starts with search and execute, which use roughly 1,000 tokens. The company explains the calculation in its implementation write-up.
The 1.17 million figure describes a hypothetical schema for Cloudflare's complete API. It is not the cost of one normal MCP call, and it is not representative of every MCP server.
Prompt caching can reduce the price of repeatedly sending stable schemas. It does not remove those schemas from the context window. It also does not help the model choose among thousands of similarly named operations.
Progressive discovery addresses the schema cost. The host keeps the complete catalog outside the prompt. The model searches the catalog and loads only the definitions needed for the current task.
Suppose the agent discovers the two operations it needs:
github.listMyPullRequests(...)
github.getChecks(...)A search_tools function can load these two schemas instead of the full catalog. The schema cost falls, but the workflow is unchanged. The agent still has to make the calls.
It lists open pull requests. The result returns to the model. For each pull request, it fetches the check results. Those records return to the conversation. The model tracks which checks belong to which pull request and drops the ones where every check is passing.
These steps are deterministic. They do not need another model inference after every network request.
Discovery tells the model which functions are available. It does not run a multi-step workflow. An execution layer can combine those calls and process their results before the model runs again.
Code Mode exposes a program runner to the model. It can execute TypeScript, Python, restricted Bash, or another scripting language inside a controlled environment. The runner receives approved functions, not raw credentials or unrestricted access to the host.
The model writes a short program with the control flow needed for the task. The sandbox runs it and sends only its return value to the next model turn.
The Code Mode loop
The model loads relevant types, writes one program, and receives a filtered result.
Code Mode over MCP can expose a small surface such as:
search(query)
getSchemas(names) // optional inspection step
execute(program)The exact interface varies. Cloudflare exposes search and execute. Other implementations add a schema inspection tool such as getSchemas. In both cases, the model is still using MCP. It sends a program through execute instead of making one low-level call per model turn. A typed SDK inside the sandbox provides the selected operations.
The agent host can provide the same interface without putting execute in an MCP server. Its sandbox may combine MCP tools with REST APIs, GraphQL, CLIs, or internal functions. Code Mode does not require a specific transport.
Consider this request:
Find my open pull requests whose CI is failing, and return each one with the checks that failed.
The agent loads type definitions for two capabilities. These are the function signatures only. The bodies, including authentication and the actual network calls, are already written and secured by developers. The model reads these types the way you read a library's documentation before calling it.
declare const github: {
listMyPullRequests(input: {
state: "open" | "closed"
}): Promise<Array<{
number: number
title: string
author: string
}>>
getChecks(input: {
prNumber: number
}): Promise<Array<{
name: string
status: "passed" | "failed" | "pending"
}>>
}The agent never sees an API token or a network client. It writes only the code that calls those functions and shapes their results:
const prs = await github.listMyPullRequests({ state: "open" })
const checked = await Promise.all(
prs.map(async (pr) => {
const checks = await github.getChecks({ prNumber: pr.number })
return { pr, checks }
}),
)
return checked
.filter(({ checks }) => checks.some((check) => check.status === "failed"))
.map(({ pr, checks }) => ({
number: pr.number,
title: pr.title,
failing: checks
.filter((check) => check.status === "failed")
.map((check) => check.name),
}))The program above is exactly what the model would write for this request: it is the kind of short, disposable script an agent generates and runs once, not a function a person checks in.
The program still makes the same backend requests. The full check lists, the passing checks, and the pull requests with green CI all stay inside the sandbox. The model receives only the failing pull requests and the names of the checks that failed.
One tool at a time
Model
Code Mode
Model
Sandbox
Model
Put numbers on it. Say you have 30 open pull requests. One tool at a time, that is 1 call to list them, then 1 check lookup per pull request, so 31 tool calls. Each one is a separate model turn: the model waits for the result, reads it into a context that keeps growing, and only then decides the next call. Code Mode runs the same 31 backend requests as a plain loop inside the sandbox. The model writes the program once and reads the result once, so it wakes up twice instead of 31 times.
Code Mode does not eliminate backend requests. The system still has to ask GitHub about every pull request. What changes is the model's involvement: fewer round trips, and none of the passing checks or green pull requests passing through its context on the way to the answer.
Anthropic calls this programmatic tool calling. In its published agentic-search evaluations, programmatic calls improved performance by an average of 11 percent and used 24 percent fewer input tokens. Those numbers come from specific benchmarks, but they show the effect of processing intermediate data before the next inference.
This division is worth restating, because it is the part most people get wrong. Developers provide github.getChecks(), its type definition, and its authorization rules. The model does not receive an API token or guess an internal endpoint. It writes a short program against the function the host provides, and that program is thrown away after the request. Nothing the model wrote becomes a permanent tool.
The host decides which capabilities the model can access and enforces policy on every call. The model chooses how to combine those capabilities for the request. The sandbox handles the resulting control flow and data processing.
The SDK should use the concepts the task requires, such as pull requests, checks, and reviews. The model should not need to understand internal table names or undocumented request sequences.
The SDK can also change with the task. A read-only CI review should not receive branch-merging or PR-closing functions. A later remediation step may add one narrow write capability after the user approves the plan.
Give an agent Bash and it can inspect --help, call CLIs, connect commands with pipes, keep temporary files, and filter output with tools such as jq. A shell is presented as one tool, but it opens access to every command installed in the environment.
This is not a hypothetical. Coding agents already default to it. Claude Code will reach for grep or sed over its own built-in search and edit tools often enough that people write hooks just to stop it. The habit comes from training data thick with shell one-liners, not from an instruction to behave this way. Code Mode does not teach a model a new trick. It puts a sandbox and a policy around something the model already wants to do on its own.
gh issue list --repo acme/checkout --state open --json number,labels,author \
| jq '[.[]
| select(.labels | any(.name == "checkout"))
| {number, author: .author.login}]'With a shell, the model can complete multi-step repository work without a separate tool definition for every command. It can inspect commands, chain them, and filter their output before returning a result.
The same pattern shows up with databases. Ask a coding agent like Claude, Cursor, or Codex to read something from a database, and it will not wait for a dedicated tool for that question. It will read the connection details straight out of the environment, such as DATABASE_URL or PGHOST, and use them without being told the connection string. If psql or sqlite3 is on the machine, it inspects the schema, writes the SQL the question needs, runs it, and reads the result. It does not need a separate tool such as count_flaky_tests or list_authors_with_broken_builds for every possible query.
select
author,
count(*) as failed_runs,
max(started_at) as latest_failure
from ci_runs
where status = 'failed'
and started_at >= current_date - interval '7 days'
group by author
having count(*) >= 3
order by failed_runs desc;This follows the same execution-layer pattern as Code Mode. The model generates a task-specific query, the database performs the filtering and aggregation, and the model receives the result. If the agent runs the query through a shell command that connects directly to the database, MCP is not involved. It is Code Mode-like execution, not Code Mode over MCP. It becomes Code Mode over MCP when the generated program reaches the database through MCP-backed functions inside the sandbox.
Running Bash on the host can expose files, processes, environment variables, credentials, and unrestricted networking. Shell quoting is also fragile, and structured objects often have to pass through JSON or text streams.
just-bash provides a narrower environment. It is a Bash interpreter written in TypeScript with a virtual filesystem and no network access by default. The host chooses which commands, files, and network routes are available, so the interpreter does not automatically inherit access from the host machine.
The just-bash project notes that its core interpreter does not provide VM isolation. Workloads that execute arbitrary binaries or need a stronger boundary still require a hardened sandbox.
| Concern | Bash | TypeScript Code Mode |
|---|---|---|
| Discovery | Command names and help text | Searchable functions and declarations |
| Values between calls | Text, JSON, files, exit codes | Objects and promises |
| Composition | Pipes, loops, redirection | Functions, loops, array methods |
| Concurrency | Shell jobs and process control | Promise.all and async functions |
| Capability boundary | Installed commands | Functions injected by the host |
| Strongest use case | Files, CLIs, builds, tests | APIs, data joins, product workflows |
Python can provide the same execution model as TypeScript. The language is an implementation choice. What matters is that the runtime processes intermediate values without sending each one back to the model.
The model produces code as untrusted text. Do not pass it to eval(), new Function(), or the main application process.
A prompt cannot prevent the program from reading files, making network calls, accessing secrets, or running forever when the runtime permits those actions.
A few things can act as the sandbox: an isolate, WebAssembly, a separate process, or a container. The faster ones isolate less. Pick based on how much you trust the code and how often it runs. Either way, give the program only the capabilities it needs for the task, nothing more.
The sandbox receives functions, not credentials
A generated function call crosses into trusted host code, where policy and authentication are enforced.
The sandbox receives a proxy for github.getChecks(). The credential stays in the trusted broker. Every call crosses a boundary where the host can validate arguments, check the user's permissions, apply a rate limit, and record an audit event.
At minimum, the runtime needs:
Isolation and authorization are separate controls. A sandbox may block access to /etc/passwd while still allowing the program to call github.mergePullRequest() 10,000 times. Isolation limits what the code can reach. Authorization determines whether each requested action is allowed.
Node.js makes this distinction explicit: node:vm is not a security mechanism. A separate JavaScript global object is not equivalent to a hardened boundary for untrusted code.
MCP connects a client to tools and context. Code Mode runs model-generated orchestration. A system can use them together or independently.
| Arrangement | What happens |
|---|---|
| Code Mode over MCP | The model sends a program through an MCP execute tool or calls MCP-backed functions inside a host sandbox. |
| Code Mode without MCP | The sandbox calls functions backed directly by APIs, SDKs, GraphQL, CLIs, or internal code. |
| MCP without Code Mode | The model invokes individual MCP tools through the ordinary tool loop. |
The host can choose the execution path for each request. Codex's MCP documentation describes direct server connections, tool allow lists, and per-tool approval policies. Its changelog also includes Code Mode work and shared MCP types. Reading GitHub issue 123 may need one direct get_issue call. Searching many issues and joining them with another system is a better fit for a program. Both paths can use the same MCP integration.
A general code interpreter lets the model write arbitrary code from scratch, including logic that talks to the outside world however it likes. Code Mode is narrower. The model still writes and runs code, but only against the typed functions the host injects. It composes pre-built capabilities; it does not implement new ones or reach past the sandbox. If you pictured the model authoring its own tools, that is the code-interpreter model, not Code Mode over MCP.
Call the tool directly
The task needs one clear operation.
Direct calls are compact, easy to approve, and easy to trace. A sandbox adds little value for a single lookup or mutation.
Use Code Mode
The task needs a small program whose steps do not depend on judgment in between.
Code Mode is a better fit for loops, dependent calls, concurrency, joins, calculations, or large intermediate results, as long as the control flow can be decided upfront.
That condition is easy to miss. Code Mode works when the whole plan can be written before anything runs. It struggles when the model needs to look at a result and decide what to do next by judgment, not by a fixed rule, such as reading one file before deciding which file to open next.
When that happens, you have two options, and neither is free. Stop the program and hand control back to the model: you lose the round trips Code Mode was supposed to save. Or give the model live capabilities inside the sandbox: now the same approval questions are back, just harder to audit.
Code Mode can replace direct MCP calls at the model-facing layer. It does not replace authentication, schemas, authorization, retries, or the connection to the underlying service. MCP can continue to provide that connection beneath the sandbox.
It also changes what a human can approve. A direct tool call can get a per-call approval prompt before it runs. A program that makes twenty calls inside a sandbox does not offer twenty checkpoints. Any approval has to happen before the program runs or after it finishes, not in between, so a consequential action buried in step twelve of thirty gets the same review as the harmless steps around it unless the host adds its own gate for that specific call.
The sandbox itself is not free. Starting an isolate, container, or subprocess per request adds latency and infrastructure that a single direct tool call does not need. For one clear operation, that cost buys little.
Moving 10,000 tool definitions into TypeScript does not solve the context problem. The host still needs progressive discovery so the model loads only the relevant capabilities. The runtime also needs to filter output before it returns to the conversation.
search and call help the model select tools, but they leave orchestration in the model loop. Code Mode lets the model load a small set of operations, write the task-specific control flow, and return only the data that requires another inference.
Bash, TypeScript, and Python can all provide this execution layer. MCP can supply the functions underneath it.
Direct tool calling is still simpler for one clear operation. Code Mode is useful when a task requires loops, dependent calls, concurrent requests, or large intermediate results. In those cases, the sandbox runs the workflow and the model handles the parts that require reasoning.
Large catalogs need progressive discovery
Keep the complete catalog outside the prompt and load only the types relevant to the current request.
Lazy loading is only half the design
An execution layer can chain calls and filter intermediate results without another model turn after every operation.
The model writes orchestration
Developers still implement authenticated, permission-aware capabilities. The model writes a temporary program against that surface.
Sandboxing does not replace policy
Generated code needs isolation, while every exposed capability still needs validation, authorization, limits, and approval rules.