TL;DR — We replaced most MCP servers with direct CLI commands. The result: every agent action is readable in shell history, auditable without source-diving, and runs without persistent background processes. CLI-first trades protocol abstraction for transparency — and for most tools, it’s the better deal.
“Every command should be readable in shell history.”
Why CLI-First?
We used to run MCP servers. Lots of them. Chrome DevTools, Firecrawl, Supabase, custom servers for Gmail and Langfuse. They worked, but they added layers we couldn’t easily inspect.
Then we asked ourselves: Can we read every command our agents execute?
With MCP, the answer was no. Tool calls went through protocol handlers, spawned background processes, and produced results we had to trust. With CLI tools, the answer is yes. Every action is a shell command you can read, replay, and audit.
The Problem with Abstraction Layers
MCP servers solve a real problem—they give Claude access to external systems. But they introduce new ones:
Opaque execution. When Claude calls mcp__supabase__query, what actually happens? You’d need to read the server source code to know. With supabase db execute "SELECT * FROM users", it’s right there.
Persistent processes. MCP servers run continuously. They hold connections, consume memory, and can fail silently. CLI tools run on demand—invoke, execute, done.
Protocol overhead. Every MCP call involves JSON-RPC serialization, tool discovery, and response parsing. CLI tools are direct—stdin, stdout, exit codes.
Trust requirements. You’re trusting the MCP server to do what it says. With CLI, you can verify the exact command before it runs.
Key Takeaway — MCP’s core problem isn’t capability — it’s opacity. When Claude calls
mcp__supabase__query, you need to read source code to know what happens. Withsupabase db execute "SELECT ...", the action is the command itself.
Our CLI-First Principles
The shift starts with direct commands over protocol wrappers. Instead of mcp__firecrawl__scrape({ url: "..." }), we use firecrawl scrape https://example.com --format markdown. The command is readable. You can run it yourself. You can pipe it, redirect it, combine it with other tools.
This makes data flow transparent. Every CLI command shows you what it’s about to do (the command itself), where data comes from (arguments, env vars), and where data goes (stdout, files). No black boxes. No hidden state.
Shell history becomes your audit log for free:
history | grep supabase
# See every database query your agents ran
Try doing that with MCP tool calls. And because CLI tools just work — no protocol negotiation, no capability exchanges — you save context tokens that MCP would waste on handshakes every session.
The Tools We Use Now
| Task | MCP Server (Before) | CLI Tool (Now) |
|---|---|---|
| Database queries | mcp__supabase__* | supabase db execute |
| Web scraping | mcp__firecrawl__* | firecrawl CLI or curl |
| Browser automation | mcp__chrome-devtools__* | playwright CLI |
| API calls | Custom MCP servers | curl with proper headers |
| File operations | Various MCP tools | Native shell commands |
Practical Examples
Database Access
Before (MCP):
mcp__supabase__query({
query: "SELECT * FROM metrics WHERE date > '2026-01-01'"
})
After (CLI):
supabase db execute \
--project-ref "$SUPABASE_PROJECT" \
"SELECT * FROM metrics WHERE date > '2026-01-01'" \
--output json
Same result. But now you can:
- See the exact query in shell history
- Run it manually to verify
- Pipe output to
jqfor processing - Use standard Unix tools
Web Scraping
Before (MCP):
mcp__firecrawl__scrape({ url: "https://competitor.com/pricing" })
After (CLI):
curl -s https://competitor.com/pricing | \
pandoc -f html -t markdown | \
head -100
Or with Firecrawl CLI:
firecrawl scrape https://competitor.com/pricing --format markdown
Both are transparent. Both are auditable.
Browser Screenshots
Before (MCP):
mcp__chrome-devtools__screenshot({ url: "https://example.com" })
After (CLI):
playwright screenshot https://example.com --output screenshot.png
One command. Clear intent. Verifiable result.
When MCP Still Makes Sense
We’re not dogmatic. MCP has legitimate use cases:
- Complex OAuth flows that need persistent token state
- Real-time subscriptions via WebSockets
- Interactive protocols that require bidirectional communication
For these, use MCP—but audit the server code and understand what it does.
Building CLI Wrappers
When a CLI tool doesn’t exist, we write simple shell scripts:
#!/bin/bash
# tools/langfuse-query.sh
# Direct Langfuse API query - transparent, auditable
set -e
LANGFUSE_HOST="${LANGFUSE_HOST:-https://cloud.langfuse.com}"
curl -sS -X GET "${LANGFUSE_HOST}/api/public/traces" \
-H "Authorization: Bearer ${LANGFUSE_SECRET_KEY}" \
-H "Content-Type: application/json" \
--data-raw "$1"
Usage:
./tools/langfuse-query.sh '{"limit": 10}'
Every bit of this is readable. No hidden logic.
The Security Argument
CLI-first isn’t just about transparency—it’s about security.
Principle of least privilege: CLI tools run with the permissions of the invoking user. MCP servers often run with broader access.
Audit trails: Shell history is automatic. MCP logging requires deliberate implementation.
Code review: A bash script is reviewable by anyone. An MCP server is a full application.
Supply chain: CLI tools from package managers have established trust chains. MCP servers from npm can do anything.
Important — CLI-first isn’t just about developer convenience. It’s a security posture: least privilege by default, automatic audit trails via shell history, and reviewable bash scripts instead of full MCP server applications.
Migration Guide
Moving from MCP to CLI:
- List your MCP servers: What does each one do?
- Find CLI equivalents: Most have them (supabase CLI, gh CLI, aws CLI)
- Write wrapper scripts: For APIs without CLIs, wrap
curl - Update agent prompts: Teach agents to use shell commands
- Monitor and iterate: Some tools work better than others
The Result
Our agents now execute commands you can read:
$ history | tail -20
supabase db execute "SELECT COUNT(*) FROM executions"
gh issue list --repo agents-squads/hq --state open
curl -s https://api.langfuse.com/traces | jq '.data[0]'
playwright screenshot https://agents-squads.com --output og.png
Every action visible. Every command auditable. Every operation transparent.
That’s CLI-first.
This is part of the Engineering AI Agents series—practical patterns for building autonomous AI systems.