Why a CLI for MCP
MCP servers were designed to be driven from inside an LLM client. The chat app holds the connection, lists every tool the server exposes, and lets the model call them. That model works well inside a conversation. It is awkward everywhere else.
This page explains where a shell-level client like mcpal earns its place, and where it does not.
Tool definitions cost tokens you may not need
When a chat client connects to an MCP server, it loads every tool's name, description, and JSON schema into the model's context. A server with forty tools costs context whether the model uses two of them or all of them. Connect four such servers and a non-trivial slice of the context window is gone before the first user message.
mcpal calls into the same servers without paying that price. The shell types one line:
mcpal tool call cursor:linear get-issue --id ENG-123
When an agent script needs to discover what's available, it asks for exactly what it needs:
mcpal tool list cursor:linear --names-only # names only
mcpal tool describe cursor:linear get-issue # one schema
mcpal tool template cursor:linear get-issue # a known-good skeleton
No upfront catalogue. Cost scales with what the script invokes, not with what the server advertises.
The shell already knows how to compose things
MCP responses are JSON-RPC frames inside a chat-only protocol. Once the chat tab closes, the call is gone. mcpal returns the same data to standard output, with stable exit codes:
mcpal --output json tool list cursor:linear |
jq -r '.[] | select(.name | startswith("get_")) | .name'
mcpal --query 'content[0].text' tool call ev echo --message hi
That's pipes, redirection, jq, xargs, cron, CI runners, exit-code
branching. None of it needs the model in the loop.
Reproducing a failure is mcpal server ping <ref> followed by the
exact command that broke, pasted into an issue with a stack trace
instead of a screenshot.
One authentication, shared across invocations
mcpal auth login runs the OAuth 2.1 + PKCE flow (or stores a
bearer token) once and writes credentials to the OS keyring. Every
subsequent mcpal tool call, every CI job that exports the same
profile, and every script in mcpal.yml reads from the same place.
There is no per-call browser dance, no token pasted into shell
history.
Servers your editor already configured
Most teams already have a working mcp.json somewhere. Claude
Desktop, Cursor, Zed, opencode, VS Code, Codex CLI — each writes
config to disk. mcpal server discover reads those files and makes
the servers addressable:
mcpal server discover
mcpal tool list cursor:linear
mcpal server list --all # owned + discovered
No duplication. The Linear server your editor configured is the same Linear server your shell can now drive.
When mcpal is the wrong answer
A CLI in front of MCP is not always the right move:
- Interactive use from inside the chat app. If the workflow is "ask the model, watch it call the tool," the chat client already owns that path. mcpal adds nothing.
- Real-time bidirectional flows. Sampling, elicitation, and
long-lived subscriptions live more naturally inside a connected
client. mcpal exposes them (
mcpal watch,--sampling-handler) but they are not its strongest surface. - End users without a terminal. A non-developer running a SaaS integration is not the audience.
Limitations
mcpal does not remove every MCP cost the published critiques point at:
- Each
mcpal tool callspawns the server fresh over stdio. Initialization (theinitializehandshake, thetools/listexchange a server may do on connect) runs every call. A long-runningmcpal servedaemon that holds warm sessions is on the roadmap. - The tool catalogue is not cached on disk.
tool listround-trips to the server. A local cache with a TTL is plausible future work.
Known gaps. The shell surface is what exists today; the project sits alongside MCP-aware chat clients, not in their place.