Code and dense tables are folded away. Open any of them on demand.
What code mode is
With classic tool calling, the model emits one tool call, the client runs it, the result goes back into the context, and the model decides the next step. Each call is visible in the transcript.
Code mode changes this. The tools are presented to the model as a programming interface (for example a TypeScript API generated from MCP tool schemas), and the model writes a short program that calls them. A sandbox runs the program and returns only the final output. Cloudflare described this approach as "Code Mode" in 2025, and Anthropic's engineering post "Code execution with MCP" describes the same pattern. The motivations are practical:
- models are good at writing code, including loops, filters and error handling;
- intermediate results stay in the sandbox instead of filling the context window;
- many tool calls can happen in one step, which saves time and tokens.
The trade-off is visibility. A program that loops over 200 customer records and calls crm.update on each one produces 200 side effects from a single model turn. Nobody reviews them one by one.
The sandbox contains code, not consequences
A sandbox (a V8 isolate, a container, a microVM) is good at one thing: stopping the generated code from touching the host. It limits file system access, network access and resource use.
It does not make the tool calls safe. By design, the code is allowed to call tools, and those tools act on real systems with real credentials: they write to the database, send email, open pull requests. A perfectly isolated sandbox can still run db.execute("DELETE FROM invoices") if the tool proxy passes it through.
So the security question for code mode is not "can the code escape?" (the sandbox's job) but "should each tool call the code makes be allowed to happen?" (a policy question).
Put the guard in the tool proxy
In a well-built code-mode setup, generated code cannot reach tools directly. It calls stubs, and each stub sends the request through a tool proxy outside the sandbox, which holds the credentials and forwards the call. Every call passes that point, whatever the code looks like, however it was obfuscated.
That proxy is where the guard belongs:
Show technical detailsHide technical details· ts sample
A few design points:
- Check in the proxy, not in the generated code. Code the model writes can skip a check; the proxy cannot be skipped.
- Pass the user's request, not the code. The guard judges the action against the intent. The program text is rarely needed.
- Tell the proxy what the code has read. If the run fetched external content earlier, set
triggertotool_resultfor later calls, since those calls may be influenced by it. - Return blocks as errors. Generated code can catch a
ToolBlockederror and report back, which gives the model a chance to recover.
Keeping it fast: batches and budgets
Code mode is attractive because it is fast, so the guard must not become the bottleneck. In our measurements a full check takes about 15 ms on an RTX 4090 (p95 about 23 ms, batch 1), and one GPU sustains roughly 400 checks per second. Over the internet, add the network round trip (about 100 ms is typical from Europe).
Two techniques keep the overhead small:
- Batch where the code allows it. When generated code maps a tool over a list, the proxy can collect the calls and send them in one
POST /v1/guard/batchrequest (up to 64 checks), then execute only the allowed ones. One round trip instead of many. - Skip read-only tools deliberately. If a tool cannot change anything (a pure lookup on data the agent may already see), you may choose not to guard it. Make that an explicit allowlist in the proxy, not a default. Note that reads can still leak data if a later call sends it out; the
exfiltrationscore applies to the call that sends.
At $0.20 per 1,000 checks, checking every mutating call in a run costs little compared with the model call that produced the code.
Fail closed, and keep the other layers
If the guard cannot be reached (a timeout, a 503), the proxy has to decide. For tools that can change or send data, fail closed: treat the call as ask or block. The MCP Guard API returns 503 for retryable failures and charges nothing for them, so a retry with a short backoff is also reasonable.
And keep the controls that do not depend on any model:
- Scoped credentials in the proxy. The sandbox never sees them; the proxy holds the least privilege the task needs.
- Hard rules first. A denylist in the proxy for operations that should never run from generated code, checked before the guard.
- Rate and volume limits. A cap on how many mutating calls one run may make stops a runaway loop even when each call looks fine on its own.
- Audit logs on your side. Record which calls ran and their verdicts in your own systems; MCP Guard does not store request payloads.
The guard reduces the chance that a bad call gets through. These layers limit the damage when one does.
Frequently asked questions
Does the guard need to see the generated code?
Should I guard read-only tool calls?
What happens to a blocked call inside running code?
Sources
Last updated . MCP Guard reduces the risk of harmful agent actions; it does not guarantee safety. Keep deterministic controls in place alongside it. Third-party names are used only to describe their products; we are not affiliated with them.
Next articleBlast radius and reversibility: how much can this tool call break?How to reason about the impact of an AI agent tool call: read-only, reversible writes and production side effects, and why backups and dry runs change the verdict.