I build AI agents for a living, and the single most useful thing I’ve done for them is stop giving them big interfaces. No SDKs. No sprawling APIs. No MCP servers with twenty tools registered at boot.
Instead I give them what Unix gave us forty years ago: small, sharp, isolated CLI tools they can compose. One job each. Text in, text out. That’s the whole methodology.
This post is the short version of why.
The context window is the budget
Every tool you register with an agent costs tokens. Schemas, descriptions, parameters, examples — they all sit in the system prompt and eat the window before the agent has done any work.
MCP servers tend to register everything. Twenty tools, full schemas, all loaded whether the agent needs them or not. That’s fine for a chatbot. It’s expensive for a long-running agent doing real work.
A CLI is invisible until the agent calls it. --help is on-demand documentation. The model loads only what it needs, when it needs it. That alone is worth the switch.
Small tools, sharp boundaries
The Linux philosophy translates almost one-to-one to agent design:
- Do one thing well.
skyfetches weather. That’s it. - Compose with pipes.
powerctl prices --json | jq '...'works because both sides speak text. - Fail loudly. Non-zero exit, stderr, the agent reads it like any other error.
- Isolate aggressively. One binary, one config file, one set of permissions. Drop it in a container if you want a hard wall.
Each tool is a sandbox. The agent gets a verb, not an SDK. I get to control exactly what it can touch.
My CLI-first stack
I write everything in Go right now. Single static binary, fast cold start, zero runtime dependencies, ships anywhere. The toolchain matches the goal: small, isolated, boring.
Three I lean on most:
- sky-cli — weather via met.no. Installable via homebrew-sky.
- powerctl-cli — Tibber and Tibber Pulse energy data. Installable via homebrew-powerctl.
- cfluence-cli — Confluence pages in and out. Tap scaffolded at homebrew-cfluence, live after the first release.
Each one wraps an API I’d otherwise have to explain to the agent every session. Now it’s just a verb.
CLI vs MCP — the short table
| Concern | CLI tool | MCP server |
|---|---|---|
| Context window cost | Near-zero until invoked | Schemas registered upfront |
| State | Stateless per call | Long-lived session |
| Streaming | No | Yes |
| Permissions | OS-level: env, files, containers | Client-level allowlists |
| Secrets | Stay in config/env on disk | Forwarded through the server |
| Distribution | brew, apt, Docker, single binary | Client-specific config + runtime |
| Composability | Pipes, jq, shell | Limited to what the client exposes |
| Cross-client | Anything with a shell | Any MCP-compatible client |
| Cold start | Fresh process per call | Already running |
| Best for | Agents in a shell, one-shot data, scripting | Sessions, streaming, rich resources |
Pros and cons in one breath
CLI pros
- Cheap on context — the agent loads only what it uses.
- OS-native isolation: containers, users, env vars, file modes.
- Secrets stay local.
- Distribution is solved (
brew install, Docker image, done). - Composes with everything Unix already knows.
CLI cons
- Cold start every call.
- No streaming.
- The agent has to read
--help(usually fine, sometimes friction). - Bad UX in the binary = bad behaviour from the agent. You own the flags.
MCP wins when: you need held-open state, streaming, rich return types, or one integration that has to work across many different clients.
How an agent actually uses these
The pattern is boring on purpose:
# Agent reads the contract
powerctl --help
# Agent does the work
powerctl prices --today --json | jq '.[] | select(.price > 1.5)'
In Claude Code’s settings, the tool is allowed with one line:
{ "permissions": { "allow": ["Bash(powerctl:*)"] } }
That’s the entire integration. No registration, no schema, no manifest. The agent inherits whatever the shell can do, scoped to the binaries I let it run.
Rule of thumb
If the operation fits in one shell command, ship a CLI. If it needs a session, ship an MCP server.
For agent work specifically, I’d push it harder: default to CLI. Reach for MCP only when the table above clearly tips the other way. Your context window will thank you.
One last thing
I built a tiny CLI you can run right now, in your browser, no install required.
Press ` (backtick) anywhere on this site to open the terminal, then type:
mcp
It’s a CLI pretending to be an MCP server, pretending to be a CLI. The joke is the whole post compressed into one command.
If you’d rather run something real, brew install kristofferrisa/sky/sky and ask it for the weather. That’s the stack.