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. sky fetches 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:

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

ConcernCLI toolMCP server
Context window costNear-zero until invokedSchemas registered upfront
StateStateless per callLong-lived session
StreamingNoYes
PermissionsOS-level: env, files, containersClient-level allowlists
SecretsStay in config/env on diskForwarded through the server
Distributionbrew, apt, Docker, single binaryClient-specific config + runtime
ComposabilityPipes, jq, shellLimited to what the client exposes
Cross-clientAnything with a shellAny MCP-compatible client
Cold startFresh process per callAlready running
Best forAgents in a shell, one-shot data, scriptingSessions, 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.