Engineering

CLI-First Agent Architecture

By Agents Squads · · 10 min

“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.

Our CLI-First Principles

1. Direct Commands Over Protocol Wrappers

Instead of:

mcp__firecrawl__scrape({ url: "https://example.com" })

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.

2. Transparent Data Flow

Every CLI command shows you:

No black boxes. No hidden state.

3. Auditable History

Shell history becomes your audit log:

history | grep supabase
# See every database query your agents ran

Try doing that with MCP tool calls.

4. Context Efficiency

MCP requires protocol negotiation and tool discovery on every session. CLI tools just work—no handshakes, no capability exchanges, no wasted tokens.

The Tools We Use Now

TaskMCP Server (Before)CLI Tool (Now)
Database queriesmcp__supabase__*supabase db execute
Web scrapingmcp__firecrawl__*firecrawl CLI or curl
Browser automationmcp__chrome-devtools__*playwright CLI
API callsCustom MCP serverscurl with proper headers
File operationsVarious MCP toolsNative 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:

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:

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.

Migration Guide

Moving from MCP to CLI:

  1. List your MCP servers: What does each one do?
  2. Find CLI equivalents: Most have them (supabase CLI, gh CLI, aws CLI)
  3. Write wrapper scripts: For APIs without CLIs, wrap curl
  4. Update agent prompts: Teach agents to use shell commands
  5. 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.

Related Reading

Back to Engineering