og.rsts.dev
← writing

Give the Agent a Language, Not a Menu

Listen 0:00 / 0:00

Cloudflare's Code Mode turns MCP tools into a TypeScript API and lets the model write the program instead of calling tools one at a time. The pattern underneath matters more than the product: the model writes, the runtime holds the keys, and the script it leaves behind is the most honest audit record an agent has ever produced.

Give the Agent a Language, Not a Menu
Contents

“It turns out we’ve all been using MCP wrong.”

That is the opening line of Cloudflare’s Code Mode post, and it is half right. The protocol was never the mistake. The menu was.

For two years the default way to hand an agent a capability has been to list it. Here are forty tools, each with a name, a description, and a JSON schema. Pick one. Wait for the result. Pick the next one. It is the interaction model of a restaurant: the model reads the menu, orders a dish, and the kitchen sends it out one plate at a time.

Kenton Varda and Sunil Pai proposed something different. Convert the MCP tools into a TypeScript API, give the model a single tool that runs code, and let it write the program. Their summary is blunt: “LLMs are better at writing code to call MCP, than at calling MCP directly.”

I think the product is interesting. I think the pattern underneath it is more important, because it changes where the control lives in an agent architecture, and that is the question I spend most of my time on.

The menu is getting long

The stakes are easy to miss when an agent has five tools. They are hard to miss at fifty.

Every tool definition sits in the context window whether the model uses it or not. Every intermediate result travels back through the model so it can be copied into the next call. Anthropic’s engineering team, writing up the same idea from their side, put a number on one workflow: moving data from a document store into a CRM dropped “from 150,000 tokens to 2,000 tokens,” a saving of 98.7%.

Cloudflare then pushed the idea to its limit. Their MCP server for the entire Cloudflare API covers over 2,500 endpoints. Exposed as one tool per endpoint, they estimate it would cost 1.17 million tokens of context. With Code Mode it costs around 1,000, through exactly two tools: search() and execute().

Those are vendor numbers on vendor workloads, and I will come back to that. But the direction is not subtle. In an enterprise, the tool surface does not stay at five. I argued in The API Catalog Is the Tool Catalog that the tool layer agents act through is already sitting in the API catalog you govern. A mid-sized institution has hundreds of APIs in that catalog. Nobody is putting hundreds of tool definitions in a prompt and expecting good judgment out the other side.

There is a third cost besides tokens and latency, and it is the one that should bother a bank architect most. Every intermediate result that passes through the model is data in the context window. A customer list, a balance, a case note. It did not need to be there. It was only there because the menu model has no other way to move it from one tool to the next.

What the menu actually costs

Models read code better than menus

The first argument is about training data, and it is the most intuitive. Tool calling relies on special tokens and formats the models learned largely from synthetic examples. Code is something they have seen in enormous, messy, real-world volume. Asking a model to write a loop is asking it to do something it has done millions of times. Asking it to emit the fourteenth structured tool call in a sequence is asking it to stay disciplined in a format it learned in a lab.

Here is the shape of the difference. A banker asks the agent: which customers had an overdraft this week and also have an open service case, and what is the gist of each case?

The menu version is a transcript:

→ call accounts.listOverdraftEvents({ since: "2026-10-14" })
← 212 events (full payload returned into context)
→ call cases.search({ customerId: "C-10432", status: "open" })
← 1 case (full payload into context)
→ call cases.search({ customerId: "C-10587", status: "open" })
← 0 cases
→ call cases.search({ customerId: "C-11920", status: "open" })
← 2 cases
... 209 more calls, each result routed through the model ...
→ the model summarizes from everything it has accumulated

The language version is a program:

const events = await accounts.listOverdraftEvents({ since: "2026-10-14" });
const customerIds = [...new Set(events.map((e) => e.customerId))];

const withCases = [];
for (const id of customerIds) {
  const open = await cases.search({ customerId: id, status: "open" });
  if (open.length > 0) withCases.push({ id, subjects: open.map((c) => c.subject) });
}

console.log(JSON.stringify(withCases));

The model writes eleven lines once. The loop runs in the sandbox. Only the final, filtered list comes back. The 212 raw overdraft events never enter the context window at all.

The round trip is the bill

That second point is the real economic story. In the menu model, the model is the integration layer. It is the thing copying the output of step one into the input of step two, and you are paying inference prices for the world’s most expensive clipboard.

Anyone who has built integration platforms recognizes this. The industry spent a decade teaching enterprises not to route every hop through a central bottleneck, and then rebuilt exactly that bottleneck with a language model in the middle. Code puts the orchestration back in a runtime, where orchestration belongs, and keeps the model on the part that needs judgment: deciding what program to write.

Small menus make weak tools

The third cost is quieter. Because models struggle with many complex tools, MCP server designers simplify. They collapse ten operations into one generic tool with a loose schema, or they leave capabilities out. The menu gets shorter so the model can cope, and the agent gets less capable in the process.

A typed SDK does not have that ceiling. You can expose the real surface, with real types and real doc comments, and let the model navigate it the way a developer navigates an SDK. Cloudflare’s search() tool is exactly that move: the model looks up the part of the API it needs instead of holding all of it.

The pattern, without the vendor

Strip away Workers, isolates, and the Agents SDK, and the pattern has four parts. All four port to whatever runtime you already operate.

Keep the contract, change the interface

Code Mode does not throw MCP away. It keeps MCP for what it is good at: a uniform way to connect, authorize, and document a capability. What changes is the interface the model sees. The schema compiles into a type system, and the model programs against the types.

That distinction matters for anyone who has invested in API governance. Your OpenAPI specs, your gateway policies, your catalog entries: none of that becomes obsolete. It becomes the source the SDK is generated from. The better your contracts, the better the code the model writes against them.

The runtime holds the keys

This is the part I would underline twice. In Cloudflare’s design the generated code runs in a fresh V8 isolate with no general network access. The global fetch() and connect() throw. The only way the code can reach the outside world is through bindings, live objects that represent the connected MCP servers.

And the bindings are already authorized. Calls go back to a supervisor that holds the access tokens and attaches them before forwarding to the MCP server. In the authors’ words: “the AI cannot possibly write code that leaks any keys.” The model writes the program. It never touches a credential.

The follow-up server makes the same point at the identity layer: the OAuth token is downscoped to the permissions the user approved when connecting, and “the agent only gets the capabilities the user explicitly granted.”

Two lanes for an agent's work: a Menu lane where every tool result passes back through the model before the next call, and a Language lane where the model writes one script that runs in a sandbox with no network access, reaching MCP servers only through bindings that a supervisor authorizes with credentials the code never sees, returning only the final result

I have argued that agents need least agency, not just least privilege: the control has to bound the behavior, not only the credential. This architecture is least agency enforced by construction. The code cannot call what it has not been given a binding for. It cannot exfiltrate to a domain, because there is no network to exfiltrate over. You are not filtering traffic after the fact. You are deciding what exists inside the room.

Anthropic adds the data angle. Intermediate results “stay in the execution environment by default,” and sensitive fields can be tokenized so that real names and contact details flow between systems “but never through the model.” For anyone who has to answer the question “what customer data did the model see,” that is a different and much better answer than “everything the tools returned.”

The script is the audit artifact

Here is the payoff I have not seen discussed enough. A menu-driven agent leaves a transcript: a sequence of calls interleaved with model reasoning, hard to replay and harder to review. A code-driven agent leaves a script.

A script is something every engineering organization already knows how to govern. You can log it in full before it runs. You can run static checks against it: does it call a write operation, does it touch a binding outside this agent’s scope, does it loop over more records than policy allows. You can replay it against a test environment. You can diff it against yesterday’s script for the same request. You can keep it as evidence.

That is the line from The Model Decides, the Code Controls made literal. The model decides what to do. The thing that actually executes is code, and code is the most inspectable artifact software has.

Where the two sides meet

The case for the experience side is strong, and I do not want to undersell it. A banker who asks a cross-system question gets an answer in one pass instead of watching an agent grind through two hundred calls. The agent can do more because the tool surface is not artificially trimmed. It is faster and cheaper, and both of those determine whether people actually use it.

The case for the control side is equally strong. No credentials in generated code. No network egress. Customer data kept out of the context window. A complete, reviewable record of exactly what ran.

What strikes me is that these are not traded off against each other here. They are the same design decision. The sandbox and the bindings are what make it safe to let the model write programs, and letting it write programs is what makes the agent fast and capable. The control plane is what lets you say yes.

What I am not sure about yet

Now the honest part.

The original Code Mode post publishes no benchmarks. The token numbers in the later posts are real but they come from vendors measuring their own workloads, and a 98.7% saving on one document-to-CRM flow is not a general rate. Your mileage will depend on how chatty your tools are and how much intermediate data they move.

The original design also loads the full generated API into context, which the authors flagged as something to improve with dynamic browsing. The search() tool is that improvement, and it introduces its own question: the agent is now discovering capabilities at runtime, and discovery needs governance too.

The architecture leans on specific runtime features. Isolates that start in milliseconds and bindings that carry authorization are Cloudflare primitives. You can build the equivalent elsewhere, with containers, gVisor, or a locked-down Deno process and a credential-injecting proxy, but it is real engineering, and Anthropic says so plainly: running agent-generated code “requires a secure execution environment with appropriate sandboxing,” with “operational overhead and security considerations that direct tool calls avoid.”

And the script, for all its auditability, is new code every time. The same question asked twice can produce two different programs. Pre-execution checks become a gate you have to build and own, the way you once built gates for human-written code. That is a solvable problem. It is not a solved one.

The catalog becomes an SDK

The menu was a reasonable first answer to “how do we let a model use a capability.” It just does not scale to the number of capabilities an enterprise actually has, and it puts the model in the middle of work that a runtime should be doing.

The language model of agent tooling is where this is heading. Your governed API catalog compiles into a typed SDK. The agent writes a short program against it. A sandbox with no network runs that program, reaching your systems only through bindings a supervisor authorizes. The script is logged, checked, and kept.

If you govern APIs today, you are already building the most important part of that stack. The next step is deciding where the sandbox lives and who holds the keys.

If this was useful, read The API Catalog Is the Tool Catalog for why the tool layer is already in your API governance, and Least Privilege Was for Humans. Agents Need Least Agency. for the control model this architecture enforces.

Find me on X @orestesgarcia or LinkedIn.