
Error: spawn npx ENOENT. That was the whole morning, gone, spent staring at an MCP config that ran fine on my machine and fell over the second a teammate tried it on a fresh install. The launch post framing treats MCP versus CLI as a clean hybrid answer, but the real failures show up somewhere else entirely: permission errors disguised as network problems, subprocesses that lose venv state between calls, servers that quietly behave differently across Claude Desktop, Cursor, and a browser based client. So where does the hybrid framing actually hold, and where does it fall apart once you wire either approach into a live agent stack? Let's get into it.
Openclaw's blog frames the MCP versus CLI debate as a hybrid answer, and honestly, they're mostly right. But the reasoning underneath deserves more scrutiny than a launch post is built to give it. MCP earns its keep when a tool needs structure, auth, and discoverability across multiple clients. CLI earns its keep when the model already knows the command from a billion lines of training data, and you don't want a server process babysitting a task that ls or grep already solves in one call. Neither side of that tradeoff is philosophical. It's operational. It shows up the moment something breaks in CI instead of on your laptop.
What follows isn't really a rebuttal of the Openclaw framing. It's more of a field report: where it holds up, where it gets fuzzy, and what I'd actually tell someone setting up their first agent tooling stack this month. Start with the case MCP makes well.
Where MCP Earns Its Complexity
Where a Sales Query Silently Fails
Source: Based on scenario described in article
MCP's pitch has always been the typed interface. A server declares its tools, its input schemas, its expected outputs, and the client discovers all of that at connection time instead of the model guessing at command line syntax it half remembers. That part of the Openclaw post checks out, and it matches what I've seen running MCP servers against Claude Code and Cursor over the past several months. When a non technical stakeholder says "check last month's sales" instead of writing a SQL query, an MCP server with a well scoped query tool turns that sentence into a validated, typed call instead of a string the model concatenates and hopes for the best.
The cross client reuse claim holds up too, with a caveat nobody puts in the marketing copy. Reuse across Claude Desktop, Cursor, and Windsurf assumes those clients implement the same subset of the spec. They don't always agree on transport details, or on how they surface resource lists versus tools. I've had a filesystem MCP server work cleanly with Claude Desktop's stdio transport and then need a separate SSE wrapper just to behave in a browser based client. That's not a protocol failure, it's an ecosystem maturity issue, and it's worth knowing before you promise your team "one server, everywhere."
{
"mcpServers": {
"sales-db": {
"command": "node",
"args": ["./servers/sales-mcp/index.js"],
"env": {
"DB_CONN": "postgres://readonly@localhost:5432/sales"
}
}
}
}
That config file looks trivial until the readonly user in DB_CONN doesn't actually have SELECT on the view you added last sprint, and the MCP server returns a generic permission error that the model then tries to explain to a non technical user as a network problem. Typed interfaces prevent malformed calls. They don't prevent malformed permissions, and that distinction cost me a support ticket I didn't need to file.
MCP is the right call when the tool needs auth, needs to be discovered rather than described, and needs to travel across more than one client. Treat the multi client reuse claim as a starting point, not a guarantee, until you've actually run the same server against two different hosts. That auth and discovery advantage is real. It just says nothing about the tasks MCP handles badly, which is exactly where CLI takes over.
Why CLI Skills Quietly Win on Execution
MCP vs CLI: Where Each Approach Actually Holds Up
| Dimension | MCP | CLI |
|---|---|---|
| Interface type | Typed, schema declared | Model recalls syntax from training |
| Best for | Auth, discoverability, multi client tools | Execution heavy tasks, file ops, package mgmt |
| Cross client reuse | Claimed, but transport details vary by client | Not applicable, runs locally per call |
| Common failure | Permission errors read as network issues | Subprocess loses venv state between calls |
| Failure surfaces | In CI or on teammate's fresh install | In CI or on teammate's fresh install |
Source: Based on article analysis of MCP and CLI tradeoffs
The Openclaw post gets this right: execution heavy tasks, file operations, package management, system commands, are usually better served by CLI, because the model already understands the command patterns from training data. This is the part practitioners underrate. Claude Code doesn't need an MCP wrapper around git status or npm install. It needs permission to run them and a sandbox that doesn't let a bad rm -rf ruin your afternoon. Wrapping every shell command in a typed MCP tool adds a layer of indirection that buys you almost nothing when the model's base capability already covers the case.
I've watched this go wrong in the other direction too. A colleague told me about an MCP server they built that exposed a tool called run_python_script, which took a string of code as input and executed it server side. Functionally that's just a CLI call wearing an MCP costume. It inherited every CLI risk, arbitrary code execution, no sandboxing by default, while stacking MCP's connection overhead on top of it. If you're going to shell out, shell out directly and let the agent's existing bash tool handle it with proper permission prompts. Don't launder it through a protocol that implies more safety than it actually delivers.
what actually gets debugged in practice
MCP Behavior Consistency Across Clients
MCP Behavior Consistency Across Clients
Client
Transport Support
Tool Discovery
Resource Listing
Claude Desktop
High (stdio)
High
Medium
Cursor
Medium
High
Medium
Browser Based Client
Low (needs SSE wrapper)
Medium
Low
Green: consistent behavior. Amber: partial support. Red: requires extra workarounds.
Source: Based on article's account of running MCP servers across Claude Desktop, Cursor, and browser based clients
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
ModuleNotFoundError: for a missing dependency such as 'psycopg2', in a scenario like this
turns out the agent ran this inside the wrong venv
because a previous CLI skill call didn't preserve shell state
That venv problem is the real argument against pure CLI skills for anything stateful. When Claude Code sessions lose context mid task, this is usually where it happens. A CLI skill invocation spawns a new subprocess, the subprocess doesn't inherit the activated environment from three tool calls ago, and the model has no idea the pip install it just ran landed in system Python instead of the project venv. MCP servers with persistent connections don't have this failure mode, because the server process holds state across calls. That's a genuine structural advantage for MCP, one the Openclaw framing only mentions indirectly under "services that need authentication," when the real driver is often statefulness, not auth at all.
CLI skills win on tasks the model already knows cold. But the moment a task needs persistent state across multiple calls, plain CLI starts fighting you. That's the actual dividing line, more than the file operations versus API calls split the post leans on. It also changes what "discovery" even means once you put the two approaches side by side, which is the last thing worth testing.
Is Tool Discovery Really MCP's Best Argument?
Openclaw's third point, that MCP servers tell the AI what they can do while CLI requires manually describing every command, is technically accurate and practically overstated. Yes, MCP's list_tools call gives a client structured metadata automatically. But Claude Code already handles a huge chunk of CLI discovery for free, because the model was trained on man pages, help output, and thousands of Stack Overflow threads about what tar -xzf does. You don't manually describe git commit to Claude the way you'd describe a bespoke internal API.
Where discovery actually matters is internal, bespoke tooling with no public documentation trail: your company's deploy script, an internal billing service, a proprietary data pipeline. That's exactly where MCP's self describing schema earns its cost, because there's no training data prior for the model to lean on. For anything with real documentation already floating around the internet, the discovery advantage narrows fast. I've seen teams build MCP wrappers around standard Unix tools that added latency and config surface for a discovery benefit that was already close to zero.
a discovery problem MCP actually solves well
internal tool with zero public docs, zero training data prior
class DeployServer:
def list_tools(self):
return [{
"name": "deploy_to_staging",
"description": "Deploys current branch to staging cluster",
"inputSchema": {
"type": "object",
"properties": {
"branch": {"type": "string"},
"skip_migrations": {"type": "boolean", "default": False}
},
"required": ["branch"]
}
}]
That's the discovery case that justifies MCP's overhead: a tool the model has never seen described anywhere, with a schema that stops it from guessing wrong on skip_migrations and taking down a database mid deploy. Compare that to wrapping curl in an MCP server so the model can call an API it could already hit directly with the right headers, something I've seen recommended in more than one starter template, and which mostly just adds a process you now have to keep alive.
Tool discovery is MCP's strongest real advantage, but only for tools with no training data footprint. Apply it to anything the model already understands from its base knowledge and you're paying protocol tax for a benefit that was never there. And that's the actual fix for the morning this post started with: the config wasn't broken because MCP or CLI was the wrong protocol. It failed because a stateful, permissioned task got treated as a stateless discovery problem. Match the tool to the failure mode, auth and cross client reuse to MCP, execution the model already knows to CLI, state to whichever one actually holds it, and the hybrid framing stops being a slogan and starts being a checklist you can actually debug against.