Companies should ship CLIs, not MCPs

9 min read Original article ↗

My previous post argued that bash is the right tool orchestration layer for AI agents, which is true as far as it goes, but it doesn’t go far enough. Framing bash as a practical alternative to MCP makes it sound like a tooling preference, the kind of thing reasonable engineers might disagree about over lunch. The real point is more damning than that: MCP is rebuilding an operating system on top of an operating system, and every problem it solves was already solved by POSIX in 1988.

If you strip away the branding, an AI agent needs exactly five things from its environment: a way to discover tools, a way to invoke them, a way to pass data between them, a way to control access, and a way to orchestrate multi-step workflows. MCP provides all five. So does POSIX. The difference is that POSIX has been stable for nearly four decades, runs on every server on earth, and has an ecosystem of millions of tools built against its interfaces, while MCP is a moving target with a few thousand integrations, most of which are wrappers around CLI tools that already exist.

Here’s how each capability maps across the two systems.

Discovery. MCP requires tool schemas to be loaded into the model’s context so it knows what’s available, and the original approach was to load everything upfront, which burned tens of thousands of tokens before the conversation even started. Anthropic’s own engineering team reported setups consuming 134,000 tokens in tool metadata alone, which at current Claude pricing works out to roughly $0.40 per conversation in input tokens before the user says a word. The fix was Tool Search Tool, which discovers tools on demand. POSIX solved this decades ago with $PATH and --help: binaries live in known directories, you run one when you need it, and you don’t preload anything into memory.

Invocation. MCP calls tools through JSON-RPC over stdio or HTTP, where the model emits a structured tool call, the server executes it, and the result comes back as structured data. POSIX uses fork and exec: the model emits a shell command, the kernel runs it, and the result comes back on stdout. The capability is identical; the difference is that the POSIX version has been hardened by forty years of production use across billions of machines.

Data flow. This is where the cost difference gets painful. MCP passes every tool result back through the model’s context window, which means the model pays tokens to read it and the context window becomes the bottleneck. POSIX pipes data between processes directly: curl | jq | grep moves data through three tools without any of it entering a context window, because there is no context window. A five-step pipeline that costs fractions of a cent in bash can easily cost several dollars when each step requires a full model inference pass, because every intermediate result has to be serialised into the conversation and reasoned about before the next step can begin.

Access control. MCP is building its own permission model from scratch. POSIX has users, groups, and file modes, a system that is well-understood, battle-tested, and supported by every piece of infrastructure ever built.

Orchestration. MCP orchestrates by routing each step through the model in a loop of tool call, inference, tool call, inference. Anthropic’s own blog post explained why this was “both slow and error-prone,” and their fix, Programmatic Tool Calling, lets the model write code that calls multiple tools in a sandbox. POSIX orchestrates with shell scripts: write the commands, chain them with pipes, run the script. No inference between steps, no tokens burned, deterministic and replayable.

Five capabilities, five problems already solved. MCP is not extending the operating system. It is reimplementing it at the application layer, with worse performance, less maturity, and a fraction of the ecosystem.

If MCP is a reimplementation of POSIX, then MCP servers are reimplementations of CLI tools, and most of them quite literally are. The typical MCP server wraps an HTTP API or an existing CLI in MCP’s JSON-RPC format, translating between the protocol and the underlying service. It is a translation layer that adds latency, complexity, and a dependency on a protocol that is still shipping breaking changes.

The companies that agents most need to interact with already have CLIs. stripe payments list, gh pr create, aws s3 cp: these work with pipes, scripts, CI/CD, cron, Docker, and any agent that has shell access. They already exist, they are already maintained, and they compose with every other CLI tool on the machine for free.

Shipping an MCP server on top of this means shipping a second interface to the same functionality, except the second interface only works inside MCP-compatible hosts, can’t be piped or scripted, requires the model to burn an inference pass for every invocation, and depends on a protocol whose auth spec only landed last year. The argument for MCP servers has always been accessibility: they show up in a tool picker, they handle auth transparently, they work in chat UIs. This is real, but it confuses the presentation layer with the execution layer. The tool picker is a feature of the host application, and auth brokering is a feature of the runtime. Neither requires a protocol at the tool level.

The last serious argument for MCP is authentication. CLI tools traditionally require the user to run something like gh auth login or gcloud auth login manually, and that’s genuine friction for non-technical users in a chat interface. MCP handles this with built-in OAuth: the server declares what scopes it needs, the host opens a browser, the user authorises, and the token flows back transparently. That is a real UX improvement, and it would be dishonest to pretend otherwise.

The improvement is misattributed, though. It comes from the runtime, not the protocol.

When you run gh auth login, the CLI opens a browser, you click authorise, and a token gets stored in your environment. The only reason this doesn’t work seamlessly in a chat UI today is that nobody has built the bridge yet. The agent runtime could intercept the auth request, present the OAuth flow in the UI exactly the way MCP does, set the resulting token as an environment variable, and let the CLI proceed as normal. The user gets the same “click, authorise, done” experience; the tool stays a standard CLI underneath.

This is a feature of the agent host, not a property of the tool. The runtime already mediates everything between the user and the model. Brokering OAuth for CLI tools that need credentials is a straightforward extension of that role, and it does not require the tool itself to speak a custom protocol.

There is an honest objection to all of this that deserves a serious answer: even if POSIX handles invocation, data flow, and orchestration, don’t we still need something new for discovery and auth?

Yes. The gap is real. It is also much, much thinner than MCP.

On discovery: --help works surprisingly well for LLMs because they can read natural language, but “works” and “works reliably at scale across thousands of heterogeneous CLIs” are different claims. A structured, machine-readable manifest that describes a tool’s commands, argument types, and required versus optional inputs would be more reliable than hoping the model correctly parses every CLI’s idiosyncratic help output. This is a manifest format, though, not a protocol. Think package.json or pyproject.toml: a static file that sits alongside or inside the binary and describes what it can do. You do not need JSON-RPC and a running server process to tell an agent what arguments stripe payments list accepts.

On auth: if the runtime is going to broker authentication on behalf of CLI tools, those tools need a standardised way to declare their requirements. Something like “I need a GitHub token with repo scope from this OAuth provider, and I expect it in the GITHUB_TOKEN environment variable.” Right now every CLI does this differently, and the runtime cannot generalise without a common format. This is an auth declaration spec, not a protocol. A static file that specifies the OAuth provider, the scopes, and the environment variable to populate.

Two static file formats. A tool manifest and an auth declaration. That is the entire gap between POSIX and what AI agents need from their tool layer. Everything else, invocation, data flow, orchestration, access control, is already handled by the operating system.

The problem with MCP is not that it addresses non-problems. The thin layer of discovery metadata and auth configuration is genuinely useful. The problem is that MCP bundles this thin layer with a thick layer of unnecessary infrastructure: its own invocation mechanism, its own data passing format, its own orchestration model, all of which reimplement what POSIX already provides, and reimplement it worse. The right move is to unbundle them. Define the two thin standards and let POSIX handle the rest.

The stack is simpler than the industry is making it.

The model decides what to do: it reasons about the task, plans a sequence of actions, and emits shell commands or scripts. The operating system executes those commands, handling process management, file I/O, networking, pipes, and permissions, everything that POSIX defines. The agent runtime sits between the user and the model, handling the parts that neither the model nor the OS covers: presenting results in a UI, brokering authentication, enforcing safety policies, managing the conversation.

There is no room in this stack for a protocol that reimplements the OS and wraps every tool in a custom RPC format. There is room for two thin standards: a tool manifest that describes what a CLI can do, and an auth declaration that tells the runtime how to broker credentials.

Companies should ship CLIs with a manifest file and an auth declaration. Agent runtimes should get better at reading those manifests, brokering auth, and mediating access. The execution layer between them should be POSIX, which has been waiting for a user smart enough to wield it fluently, and that user turned out to be an LLM.

Discussion about this post

Ready for more?