
arewecooked.dev
Tinkering the bleeding edge. AI writes the code, I curate.
Stop Comparing MCP to Your Shell
There has been a lot of chatter about MCP vs CLI, both being positioned as equal or competing options for building agents and giving them access to services and tools.
However, most of these conversations evaluate them as equals, or equivalent offerings. They are not. The end goal for MCP is integration, not invocation.
CLIs Work Because Shell is Good, and Popular
Here's an example of Claude leveraging gh and jq to find "Which of my open issues on acme/backend haven't had any activity in over a week?" We didn't even have to give it the spec. It just worked.
gh issue list --repo acme/backend --assignee @me \
--state open --json number,title,updatedAt \
| jq '[.[] | select((now - (.updatedAt | fromdateiso8601)) > 604800)]'
No tool definitions. Just one-shot. Impressive, right?
Claude can do this because it's deeply familiar with the syntax of the shell, and knows how to use gh and jq from the enormous amount of training data it's been trained on.
For developer workflows, CLIs are the de-facto tool of choice, so if you evaluate the agent performing development tasks, and development workflows. The CLI is always going to be a key building block.
Hate the Harness, Not the Protocol
If we compare the CLI example to a naive implementation of MCP, it'll be a single tool call:
github_list_issues(owner="acme", repo="backend", assignee="abe", state="open")
This tool call however, will result into a massive JSON object that the model now has to read, polluting context, and reducing its effectiveness.
But the core problem here isn't MCP, it's the harness that is completely limiting what the agent can do. What if the agent could express a transform, on the result:
jq(
github_list_issues(owner="acme", repo="backend", assignee="abe", state="open"),
"[.[] | select((now - (.updatedAt | fromdateiso8601)) > 604800"
)
In-fact you could take this to the next level and just let it write javascript code, leveraging MCP as a type definition and RPC format:
const issues = await github_list_issues({
owner: "acme",
repo: "backend",
assignee: "abe",
state: "open"
});
const stale = issues.filter(i =>
Date.now() - new Date(i.updatedAt) > 7 * 24 * 60 * 60 * 1000
);
This keeps all the flexibility of the CLI while retaining the advantages of MCP. This is exactly what @RhysSullivan (executor.sh), @mattzcarey (https://blog.cloudflare.com/code-mode-mcp/) have been proposing with different degrees of integration surfaces.
Where MCP Starts to Shine
MCP provides a consistent primitive, that connects an agent to something it wants to interact with like a service, or app.
This turns a bespoke integration for every agent:service pair, into a single pattern that all services and agents can follow, a consistent "library format" for all agents.
Context Engineering and Interaction Model
Model Context Protocol has Context in its name. Its goal is to allow you, as a builder, to give agents the most appropriate context possible, when they need it.
- You can leverage this to model exactly what gets sent to the model, an image, a table, text, a summary, whatever you need.
- You can offer a tool that is dedicated to a specific use-case, that might itself leverage many API Calls, and respond with a summary.
Beyond just Context, MCP also offers a stable surface area for how to capture user interaction, define authorization etc. An agent using a cli, can simply do yes | process-with-confirmation to blast through approval steps.
Outside the Developer Terminal
People don't think of agents as software building machines with access to a terminal. They expect things to work everywhere.
Once you connect an MCP Server to your agent say: Claude. It works from everywhere you can talk to Claude. Your phone, in a web-browser, across any device.
Security, Audit, and Loss of Visibility
When Claude Code used the gh cli, the audit logs would show that I performed the queries. The access from Claude Code is completely missing in the picture.
This is a security nightmare, and @yenkel talks a lot more about this in his article about MCP and security.
Updates are Free
A CLI is packaged software, an MCP Server is an HTTP Resource. The packaged software needs to be installed, updated, supported on every platform the agent may run — for each service the cli will use.
By contrast MCP Servers can dynamically update the tools, resources, and more. The difference is the same between downloading an app, v/s visiting a website. Both are useful, for different purposes.
As an integration point
Consider the scenario for a new application or service. Until the model has been trained on the sample data, it'll need to read the manual each time, for each new service. This is the exact opposite of being context efficient.
Because MCP Provides a single pattern that works for all services, as an agent / harness builder you can, build tooling to address those problems:
- you can leverage tool search, heck, even annotate tools for future use
- cache the work the agent performs is rebuilding the same pattern again, and again, like @AndrewVanBeek1's approach for https://www.keyboard.dev,
- trap actions to perform authorization like @libuild's common sense policies for agents.
and many more things are now possible that work across all services.
N + 1 > N
Tools we use today we never built perfect in the first iteration, they iterated heavily and adapted. We need that to build an interoperable, internet for agents.
Instead of MCP Good / MCP Bad, perhaps we can have more nuanced discussions? Like the ever awesome @kentcdodds take on why MCP Might be uninteresting for Developer Use-cases.