Skip to content

MCP vs CLI: when an agent should run a command and when it should call a server

On this page

Use a CLI when the agent already has a shell on a machine where the tool is installed and signed in, and the person at that machine is the one accountable for what it does. Use an MCP server when the agent has no shell, when you want permissions per tool rather than per command string, when the upstream credential shouldn't sit in the agent's environment, or when several people and apps need the same access. You can use both: gh and kubectl for local work in a coding agent, MCP servers for shared services and for agents that run somewhere else.

Checked as of October 2026, against MCP specification revision 2026-07-28 and the current Claude Code documentation.

MCP vs CLI compared

CLI through a shellMCP server
How the agent learns what's available--help, man pages, documentationtools/list returns names, descriptions and JSON Schemas at runtime
CredentialsWhatever the machine has: gh login, kubeconfig, AWS profiles, environment variablesRemote: often an OAuth token issued for the server, though some servers accept a provider token directly. Local stdio: environment variables
OutputText, plus JSON where the CLI offers it (gh --json)content blocks, plus structuredContent that can match a declared outputSchema
Upfront context costOne shell toolTool definitions, unless the client defers them
Needs a shell and a local installYesNo, for remote servers
Permission unitThe command textThe tool name
Audit trailShell history, client logs, the provider's audit log under your userEach tools/call has a name and arguments; a gateway can log every call centrally

The same task both ways

Say the agent needs the open pull requests in a repository.

With the GitHub CLI, the agent writes a command. gh has a --json flag that takes a field list and a built-in --jq filter, so the agent can ask for exactly what it needs:

Shell
gh pr list --state open --json number,title,author --jq '.[] | "\(.number) \(.title)"'

That works because gh is installed and authenticated on this machine.

With MCP, you register GitHub's remote server once. The GitHub MCP server README gives the URL https://api.githubcopilot.com/mcp/ and accepts a personal access token as a bearer header, and Claude Code adds remote servers with claude mcp add --transport http:

Shell
claude mcp add --transport http github https://api.githubcopilot.com/mcp/ \
  --header "Authorization: Bearer $GITHUB_PAT"

The agent then sees tools such as list_pull_requests, pull_request_read and get_file_contents, each with a schema for its arguments, and calls one by name. Nothing needs to be installed locally, and the agent never composes a command line. Note that in this setup the PAT still sits in your Claude Code config; the next section covers when that changes.

Authentication and credential handling

A CLI uses whatever credentials the machine already holds. For gh, that's the stored login, unless GH_TOKEN or GITHUB_TOKEN is set, which take precedence over previously stored credentials. kubectl uses the current context from $HOME/.kube/config or the files in KUBECONFIG. The AWS CLI uses the default profile unless told otherwise.

That's convenient and also the main risk. The agent acts as you, with everything your credential allows, in whichever cluster or account your shell currently points at. If your kubeconfig's current context is production, so is the agent's. And a shell that can run a CLI can usually read the files and variables the CLI reads.

Remote MCP can keep upstream credentials outside the agent's environment, but it doesn't have to. Authorization is optional in MCP, and the example above passes a GitHub PAT straight from the client. When the MCP server requires authentication, the client holds a credential for that connection. Where a server uses OAuth, the authorization specification requires tokens issued for that MCP server as the audience and says servers must not accept or transit any other tokens, so the provider credential stays on the server side. Local stdio servers are closer to a CLI: they read a token from an environment variable in the client's config. MCP authentication walks through the OAuth flow.

Discoverability and structured output

For an unfamiliar CLI, the agent may need --help or documentation before it can call it correctly, and that text costs context. Compare the CLI's available output fields with the MCP tool's schema: gh --json lists its fields when run without any, while an MCP tool publishes its arguments up front.

MCP makes the interface explicit. Each tool has a name, a description and an inputSchema, and the tools specification lets a tool declare an outputSchema, in which case the server must return structuredContent that conforms to it. Tool execution errors come back as results with isError: true and a message the model can act on, such as a date in the wrong format, rather than a nonzero exit code and stderr. Protocol errors, such as an unknown tool or a malformed request, use JSON-RPC error responses.

Context cost

A shell is one tool definition. The cost of a CLI shows up in output: a kubectl logs call or an unfiltered gh api response can be long. The agent can trim with --jq, head or grep before anything reaches the model.

MCP costs context in two places. Tool definitions come first: a server with forty tools adds forty schemas. Claude Code handles that with tool search, on by default, so only tool names and server instructions load at session start and full definitions load on demand. Clients that load all discovered tool definitions into the model's context pay that cost up front, which is why the GitHub server offers toolsets (--toolsets or GITHUB_TOOLSETS for the local server) and a --read-only flag to shrink the list.

Results come second. Filtering here depends on the server: GitHub's list_pull_requests takes an optional fields array, and its description suggests leaving out body to drop the largest data. The client also manages size. Claude Code warns when an MCP tool's output exceeds 10,000 tokens, and when a successful text result passes the limit (25,000 tokens by default, adjustable with MAX_MCP_OUTPUT_TOKENS) it saves the result to a file and tells Claude the path, so Claude can read or search the file instead of taking the whole result into context.

Agents without a shell

Plenty of agents don't run on your laptop:

  • claude.ai, Claude Desktop's Cowork and mobile apps use custom connectors, which connect to remote MCP servers from Anthropic's cloud. The server must be reachable over the public internet, and local servers configured in claude_desktop_config.json aren't available in Cowork or claude.ai.
  • Hosted agents, CI jobs and chat bots often run in a sandbox without your CLIs or your credentials.

For these, a CLI isn't an option unless you build and secure a shell environment for them. A remote MCP server is a URL and a sign-in.

Permissions granularity

In Claude Code you can allow specific commands:

JSON
{
  "permissions": {
    "allow": ["Bash(gh pr list *)", "Bash(gh pr view *)", "Bash(kubectl get *)"],
    "ask": ["Bash(gh pr merge *)"],
    "deny": ["Bash(kubectl delete *)"]
  }
}

Claude Code splits compound commands on &&, ;, | and similar operators, so Bash(gh pr list *) doesn't approve gh pr list && rm -rf build. Its documentation is also direct about the limits: a Bash rule matches the command text Claude writes, so a deny rule on curl doesn't stop /usr/bin/curl or sh -c 'curl …', and the docs call argument-constraining patterns "fragile". For enforcement that doesn't depend on command text, they point to sandboxing. Broad rules have a subtler problem: Bash(gh *) also allows gh api, which makes an authenticated request to any GitHub API endpoint with any method.

MCP permissions work on tool names. mcp__github__list_pull_requests is one tool whatever arguments it gets, and a client or gateway can allow it, require approval for merge_pull_request and block everything else. The server decides what each tool can do, so you're trusting its boundaries, and arguments are still free within a tool. The Claude Code permissions guide covers the naming rules.

Auditability

When a CLI runs, the record is the client's transcript, your shell history if it's kept, and the provider's own audit log, where the action appears under your user. Reconstructing "which agent ran what" across a team is still work.

An MCP call is already structured: a tool name, JSON arguments and a result. The tools specification says clients should log tool usage for audit purposes. If calls go through a gateway, every agent's calls land in one log regardless of which app made them.

Choosing between them

A CLI is the better fit when:

  • A coding agent works in a repository on a developer's machine, and the developer watches the session.
  • The CLI has JSON output and the task benefits from piping and filtering.
  • The tool is local by nature: git, test runners, build tools, linters.
  • You'd otherwise need an MCP server that just shells out to the same CLI.

An MCP server is the better fit when:

  • The agent runs in claude.ai, a hosted product, CI or anywhere without your shell.
  • Different agents need different access to the same service, or a team shares one account.
  • You want per-tool allow, approval and block rules that hold regardless of how a command is phrased.
  • The upstream credential shouldn't be readable by the agent's environment, and the server supports keeping it server-side.
  • You need one log of what agents did across apps.

Access control on each path

The CLI path is governed on the machine: permission rules, a sandbox and the scopes of whatever token the CLI uses. Keep the token narrow, because a rule on command text won't stop every way of invoking the binary.

For MCP, the same client can hold per-tool rules (the MCP allowlist guide compares Claude Code, Cursor, Codex and VS Code), but each machine keeps its own copy. A gateway moves those rules off the laptop. Toolstile, for example, holds the upstream connection, gives each agent Allow, Require approval or Block per tool, and logs every forwarded call; it has nothing to say about CLIs or local stdio servers. What an MCP gateway is describes the model and permissions the rules.