← Back to blog
#AI agents#MCP#CLI#OpenAPI#spec2cli#Claude Code

Give your AI agents a CLI, not an MCP server

GH
Gauthier Hubert-Viollet · Jun 25, 2026 · 9 min read

If you have ever connected several MCP servers to an agent, the feeling will be familiar. You wire up GitHub, then a database, then a ticketing tool, and before you have even asked a question, part of the context window is already gone. Not on useful work, on tool definitions the agent may never call.

That is the problem I want to walk through here, how the ecosystem is fixing it, and the small solution I ended up writing. Spoiler: it is a CLI, and it fits in a handful of Python files.

The MCP tax, and what is changing

The Model Context Protocol does one simple thing that was, for a long time, expensive. On connection, it loads the schema of every tool it exposes into context, whether you use them or not. Anthropic itself documented a five-server setup, 58 tools in total, that swallowed roughly 55,000 tokens before the first message. GitHub’s MCP server, with all its tools enabled, has consumed around 64,600 tokens according to GitHub. On a 200,000-token window, that is a third of the budget gone before the first question.

And it is not only a matter of price. Every wasted schema token is a token less for reasoning, and reliability suffers too. A Scalekit benchmark measured a trivial question costing 1,365 tokens over a CLI against 44,026 over MCP, with remote-server connection failures on top that the local CLI simply does not have. At the other end of the spectrum, Cloudflare calculated that exposing its 2,500 endpoints as that many MCP tools would require 1.17 million tokens, more than most models’ context windows.

Now let me play fair, because a tech-blog reader knows the subject. This tax is being fixed. Since January 2026, Claude Code defers MCP tools by default through a Tool Search feature: instead of preloading everything, it only loads a tool’s definition when it needs it. Concretely, when definitions exceed about 10% of the context, it switches to a lightweight search index, and Anthropic reports roughly an 85% reduction in overhead. GitHub’s server, for its part, no longer enables all its tools by default and offers dynamic toolset discovery. So no, in an up-to-date Claude Code, GitHub no longer swallows tens of thousands of tokens at once. That is worth saying.

Except these fixes live on the client side, they are not guarantees of the protocol. Justin makes the point himself: if you publish an MCP server, you cannot assume the client will manage context well. Outside Claude Code, below the trigger threshold, or with another client, the tax comes back. And above all, the deeper arguments in favor of the CLI do not depend on any fix. We will get there.

The idea I borrow from Justin Poehnelt

Before writing a line of code, I came across two articles by Justin Poehnelt, an engineer at Google, that shaped my thinking. The first is You Need to Rewrite Your CLI for AI Agents, the second The MCP Abstraction Tax.

His central distinction became my through-line: developer experience designed for humans optimizes for discoverability and forgiveness, while the one designed for agents optimizes for predictability and defense in depth. These are two different goals, and trying to retrofit a human-first CLI for agents is often a losing bet.

He also insists on a point I find underrated: input hardening. The idea is simple. A human who slips at the keyboard makes a typo, quickly spotted. An agent has no fingers: it generates text, and it can invent absurd values with complete confidence. The tool’s job, then, is to distrust what the agent sends it, exactly the way a web API distrusts whatever a user types.

Two concrete examples. An agent can produce a path like ../../.ssh, which tries to climb out of the intended folder to dig into sensitive files. Or it can glue a piece of a query inside an identifier, say file123?fields=name, and break the call because what should have been a plain id suddenly contains characters that have no business being there. A human almost never makes these mistakes. An agent does. Hence Justin’s line, which says it all: “Agents hallucinate. Build like it.” In other words, assume the agent will hallucinate, and build accordingly.

His second article adds the nuance that keeps me out of black-and-white thinking. Between the raw data and the agent there are successive layers: the database, the API that exposes it, then sometimes MCP on top. And each layer shows a little less than the one beneath it, a bit like a summary that inevitably leaves details out. Justin says it plainly: even the REST API is only an imperfect projection of the data model, because the internal representation is richer than what the API lets through. Fields, relationships, metadata stay behind the scenes, and MCP adds yet another layer on top. This is what he calls the abstraction tax. His conclusion, which I share: MCP and CLIs do not optimize for the same thing, and understanding what each one costs you is more useful than trying to crown a winner. I will keep that in mind for the end of the article.

Why a CLI flips the problem

A command line flips the cost logic. The agent pays nothing until it runs a command. It discovers what it needs at the moment it needs it, through --help or a small skill file, then issues exactly the call that matters. You give up some of MCP’s structured discoverability, but for a read-and-write workflow against an API that already ships an OpenAPI spec, the token savings are huge and the loss is small.

There is also a deeper reason, rarely mentioned. Models were trained on billions of terminal interactions: Stack Overflow answers, GitHub repositories, tutorials. A CLI therefore plays at home. An MCP schema gets no comparable training-data advantage. The model already knows the command subcommand --option value grammar.

The tool: spec2cli

That is where spec2cli came from, a small open source tool I put online. The design bet is this: instead of generating a frozen binary per API, it reads the OpenAPI spec at runtime and exposes each operation as a subcommand. Zero code generation, no line specific to any given API. It is deliberately simple, almost dumb, and that is the whole point.

One spec, two paths: from OpenAPI to an agent-first CLI or an MCP server

One spec, two paths. The CLI only loads an operation’s definition when it is called.

Four commands are enough to grasp the philosophy:

  • list discovers the operations and returns a flat JSON array, so it is cheap to load.
  • describe dumps the full schema of a single operation, $ref pointers resolved, so the agent never needs static docs preloaded.
  • call runs an operation, with a --dry-run that composes and prints the exact request without sending it.
  • skill generates a SKILL.md tailored to the target API, so Claude Code knows how to use it.

The best way to get it is to watch it run against a real API. Open-Meteo is perfect for that: free, no key, no sign-up, plain GET JSON. Here is the tool pointed straight at a community spec pulled from GitHub. That spec defines no operationId, so spec2cli generates stable ones from the method and path.

SPEC=https://raw.githubusercontent.com/cmer81/open-meteo-mcp/main/openapi.yml

# 1. Discover: 16 operations, a flat array
spec2cli list "$SPEC"

# 2. Inspect a single operation, on demand
spec2cli describe "$SPEC" getV1Elevation
#   -> latitude, longitude (required query params)

# 3. Call it: ground elevation at Nice, France
spec2cli call "$SPEC" getV1Elevation \
  --params '{"latitude":43.7102,"longitude":7.2620}'
#   -> {"ok": true, "status": 200, "data": {"elevation": [29.0]}}

At no point did the agent need all 16 operations in context. It listed, it looked at the one it cared about, it called. The token cost follows the task, not the size of the API.

The tool also handles the messy cases of the real world. Open-Meteo splits some services across sibling hosts while the spec declares only one, so a simple --base-url redirects the call:

spec2cli call "$SPEC" getV1Archive \
  --base-url https://archive-api.open-meteo.com \
  --params '{"latitude":43.7102,"longitude":7.2620,"start_date":"2024-07-01","end_date":"2024-07-01","daily":"temperature_2m_max,temperature_2m_min"}'
#   -> {"ok": true, "status": 200, "data": {"daily": {"temperature_2m_max": [24.5], "temperature_2m_min": [18.3]}}}

The guardrails that make it more than a curl wrapper

If I had stopped at “read the spec and send the request,” I would have written a disguised curl. Justin’s principles are exactly what turn it into an interface for agents. spec2cli ships them by default.

Output is JSON on stdout, for every command, so the agent parses it directly and pipes it through jq when needed. Input hardening rejects any value bound for a URL segment that contains ?, #, %, .., or a control character. These are exactly the mistakes agents make: a query string glued onto an identifier, a pre-encoded string that double-encodes, a hallucinated path traversal. The --dry-run shows the exact request with secrets redacted, to use before any write. Authentication comes only from environment variables, never from a flag. And every error path still returns parseable JSON with a non-zero exit code, so the agent understands what happened.

Nothing spectacular taken alone. But put end to end, these rules make the difference between a tool the agent uses without supervision and one that derails at the first hallucination.

So, CLI or MCP?

Honestly, there is no winner here, just a choice that depends on your situation. When you work locally and you control what runs, a CLI is almost always the right call: it burns few tokens, it slots in next to the rest of your command-line tools, and it does not drop you in the middle of a task. For day-to-day development, that is hard to beat.

MCP takes back the lead the moment you leave your own machine. An agent acting for your customers, access you need to manage, a record of who did what: that is where its explicit schemas and strict auth turn into an asset rather than a burden. And plenty of business tools simply have no CLI, so the question settles itself.

There is one thing I do not want to wave away: context windows keep growing and getting cheaper. Some of MCP’s cost will melt away over time. So the token argument has an expiry date, even if it is not for tomorrow.

My take, today, is simple. If you control execution and you want efficiency, give your agents a CLI. And if you already have an API with an OpenAPI spec, spec2cli turns it into a CLI in one line. It is small, it is open, and it solves a real problem.


Sources

GH
Gauthier Hubert-Viollet

Lead internal product & AI engineer at Flowdesk. I build with Claude Code daily and write about it here.