For the complete documentation index, see llms.txt. This page is also available as Markdown.

CLI reference

Complete reference for all mcp commands.

Output format

By default, mcp detects the output context:

  • Interactive terminal — human-readable tables with colors

  • Piped or redirected — JSON (for scripting with jq, etc.)

Use --json anywhere to force JSON output regardless of context:

mcp --list --json          # JSON even in terminal
mcp sentry --list --json   # JSON tool list

Global commands

mcp --help, mcp -h

Show usage information.

mcp --list

List all configured servers.

mcp --list

Interactive output:

JSON output (--json or piped):

Backends that cannot start here

A stdio or cli backend whose command is not executable in this environment is reported without spawning anything. This is the common case inside a container, where a binary installed on your host is absent.

Field
When it appears

available

Only on a backend that cannot start, always false

unavailable_reason

Alongside available, one of the three messages below

A runnable backend carries neither key, so select(.available) matches nothing even when everything is healthy. Filter on the reason instead:

The reasons:

Message
Meaning

command not found in PATH: <name>

Bare name, no PATH entry provides it

command does not exist: <path>

Explicit path, nothing there

command is not executable: <path>

Exists, but is a directory or has no execute bit

An http backend is never marked unavailable: reachability needs a request, and --list reads config without touching the network. A backend that pins its own PATH in env is resolved against that PATH, not the one mcp runs with.

In the table output, a Status column appears only when at least one backend is unavailable, and the footer becomes N server(s) configured, M unavailable here.

Global flags

--json

Force JSON output. Can be placed anywhere in the command:

Server commands

mcp <server> --list

Connect to the server and list available tools.

Interactive output:

If the server name alone is provided (no flags, no tool), this is the default behavior:

mcp <server> --info

Like --list, but includes parameter details for each tool.

Interactive output:

JSON output includes full JSON Schema for each tool's input parameters.

mcp <server> --health

Connect to the server and disconnect. Exits non-zero if the connection fails.

Interactive output:

JSON output also reports the MCP protocol revision the two sides agreed on — the quickest way to see whether a backend negotiated 2026-07-28 or fell back to the legacy initialize handshake:

With MCP_PROXY_URL set, the connection goes through the running mcp serve proxy, so protocolVersion is the revision negotiated with the proxy, not with the backend behind it.

mcp <server> <tool> [json]

Call a tool on the server. The optional json argument is a JSON object with the tool's parameters.

If json is omitted:

  • Interactive terminal — Uses {} (empty object)

  • Piped input — Reads JSON from stdin

Interactive output prints text content directly:

Errors are prefixed with error: on stderr.

JSON output wraps content in the MCP protocol format:

Content items have a type field:

  • "text" — Text content in the text field

  • "image" — Base64-encoded image in data field, with mimeType

  • "resource" — Embedded resource content

Proxy commands

mcp serve

Start a proxy server that aggregates all configured backends into a single MCP endpoint.

The proxy connects to every server in servers.json, merges their tool lists with namespaced names (server__tool), and routes tools/call requests to the correct backend.

Stdio mode (default)

Designed to be used as a stdio transport in any MCP client:

Diagnostics are logged to stderr. Protocol messages use stdin/stdout.

HTTP mode (--http)

Exposes the proxy over HTTP with the following endpoints:

Method
Path
Description

POST

/mcp

JSON-RPC 2.0 request/response endpoint

GET

/mcp/sse

SSE endpoint for streaming (per MCP spec)

GET

/health

Health check (returns JSON status, see below)

Default bind address is 127.0.0.1:8080 (localhost only). To bind to a different address:

--insecure

Allow binding to non-loopback addresses without TLS. Required when using addresses like 0.0.0.0, 192.168.x.x, etc. Without this flag, the server refuses to start on non-loopback interfaces to prevent accidental plaintext exposure.

Health check (GET /health)

Returns the proxy status as JSON:

Field
Description

status

Always "ok"

backends_configured

Total servers in servers.json

backends_connected

Backends currently running (others are idle-shutdown or not yet connected)

active_clients

Number of SSE sessions currently registered

tools

Total tools across all backends (including idle ones — tools are cached)

version

mcp binary version

backends_connected should not grow with active_clients — that's the proxy doing its job (N clients sharing M backends). If they grow together, clients may be bypassing the proxy.

Graceful shutdown

The HTTP server handles SIGTERM and SIGINT (Ctrl+C) gracefully: stops accepting new connections, finishes in-flight requests, and disconnects all backends.

See Proxy mode guide for full details, client configuration examples, and team setup.

mcp reload

Apply a config change to proxies that are already running.

--url sends MCP_ADMIN_TOKEN as a bearer token. Over plain HTTP to a host that is not loopback, that send is refused: the token administers a proxy fronting every configured backend, so leaking it is worse than a failed reload. Use https, or pass --insecure to accept the risk.

mcp serve reads servers.json once, at boot. mcp add foo or an edit to the file does not reach a proxy that is already running. Restarting it works, but kills every stdio backend child and every open session. mcp reload avoids that.

In order. Only step 1 aborts the command, because every later step assumes a valid config. Steps 2 to 5 all run, and failures among them are reported together at the end:

  1. Validate the config and report the server count and any reserved-name conflicts. An invalid config exits non-zero and nothing else runs.

  2. Evict stale cache entries. Tool-cache entries are dropped when their config hash no longer matches, or when their server left the config. Classifier cache entries go with them, plus any entry for a server the config no longer knows: a classification outlives its tool cache entry, so without that second sweep a removed server would keep entries in tool-classification.json forever.

  3. Rewrite installed completions. Only files that already exist (fish and zsh). It never creates one, and never touches .bashrc.

  4. Signal every local proxy with SIGHUP.

  5. With --url, POST <url>/reload to a remote proxy, with the token from MCP_ADMIN_TOKEN. --url adds to the local step, it does not replace it. See Reloading a running proxy for the endpoint.

What a running proxy reloads

  • Servers. The proxy diffs the new config against the old one. A backend whose config did not change is not touched: same child process, same usage stats. Added backends are discovered, changed ones are shut down and reconnected with the new config, removed ones are shut down.

  • serverAuth. Providers, ACL and adminRoles are swapped in one step. If the new serverAuth fails to build, the reload is rejected and the old auth stays in place.

  • Tool cache and classifier cache, for backends that changed or were removed.

If the new config is invalid, the proxy keeps serving the old one.

What still needs a restart

Some state is fixed at boot. A reload applies everything else and lists these under requires_restart instead of claiming it applied them:

Reported as
Change
Why

serverAuth.providers.oauth_as

Turning oauth_as on or off

Its router is mounted at boot

audit.output

A different audit destination

The database pool and the logger are opened at boot

The bind address is not in that list: it comes from --http, not from the config file, so reloading the file can never change it.

Filters are kept

A proxy started with --only or --exclude re-applies the same filter on every reload. A reload never makes it publish a backend it was told to hide.

How local proxies are found

Each mcp serve writes <pid>.json in ~/.config/mcp/instances/ (mode 0700) and holds an flock on it until it exits. The lock proves the proxy is alive, not the pid: the kernel drops the lock when the process dies, however it dies. A leftover file whose lock is free is removed, and a recycled pid never gets a signal. That matters because the default action of SIGHUP is to terminate the process.

Signal-based reload is Unix only. On other platforms use --url.

Output

The remote summary reads +added ~changed -removed. When the proxy reports requires_restart, it appends , N change(s) need a restart. The shell line appears once per rewritten file. With no proxy running the instance line reads none running.

With --json:

name_conflicts, completions and remote are left out when empty or unused. reloaded: false on an instance carries an error string. authenticated says whether the request carried a token, which is what tells a 401 apart from a rejected token.

SIGHUP is asynchronous: reloaded: true means the signal was delivered, not that the proxy accepted the new config. A proxy that rejects the config logs it and keeps serving the old one. Check with mcp reload --url (which returns the result) or GET /health.

Exit status

  • 0: config is valid and every targeted proxy was reached. Finding no running proxy is a success: caches and completions were still refreshed, and the next mcp serve reads the new config.

  • non-zero: the config is invalid, a local proxy could not be signalled, or --url failed to connect or answered a non-2xx status. A non-2xx answer is explained in the error: 401 names MCP_ADMIN_TOKEN, 403 names serverAuth.adminRoles, and 404 says reload is disabled on that proxy.

Registry commands

mcp search <query>

Search the MCP server registry.

Interactive output shows a table of matching servers. JSON output returns full server metadata including repository URL and install instructions.

mcp add <name>

Add a server from the registry. Looks up the server by name, generates a config entry, and writes it to servers.json.

Fails if:

  • Server not found in registry

  • Server already exists in config

  • Name is reserved (search, add, remove, update, list, help, version, serve, logs, acl, config, completions, healthcheck, reload)

mcp add --url <url> <name>

Add an HTTP server manually.

mcp remove <name>

Remove a server from the config file.

Fails if the server is not in the config.

mcp update <name>

Refresh a server's config entry from the registry, preserving your customizations.

Use this when the registry metadata for a server changed (new package version, new env vars, updated args) and you want to pull the changes without losing what you customized locally.

What gets refreshed (from the registry):

  • command and args

  • url (HTTP servers)

  • env schema — new vars are added as ${VAR_NAME} placeholders, vars removed from the registry are dropped

What is preserved (your customizations):

  • Filled-in env values (anything that isn't a ${VAR_NAME} placeholder)

  • idle_timeout, min_idle_timeout, max_idle_timeout

  • headers, forward_identity (HTTP servers)

  • tool_acl

  • cli, cli_help, cli_depth, cli_only, tools

If the entry already matches the registry, the file is not rewritten and mcp reports already up to date. New env vars introduced by the update are listed at the end so you know what to fill in.

Fails if:

  • Server is not in the local config (use mcp add <name> first)

  • Server is not in the registry

If the server changed type in the registry (stdio ↔ http), mcp update warns and drops type-specific fields that no longer apply.

Config commands

mcp config path

Print the resolved path to the config file (servers.json).

With --json, includes metadata:

mcp config edit

Open the config file in your editor. Uses $EDITOR, falls back to $VISUAL, then vi (or notepad on Windows).

If the config file doesn't exist, it's created with {} before opening.

Shell completions

mcp completions <shell>

Generate shell completion scripts. Supported shells: bash, zsh, fish.

Completions cover all subcommands, flags, and nested subcommands (config path|edit, acl classify|check, logs flags, etc.).

ACL commands

mcp acl classify

Classify every tool of every configured backend as read, write, or ambiguous, using the automatic classifier combined with manual tool_acl overrides from servers.json. This is metadata only — no enforcement path changes yet.

Flags:

  • --server <alias> — restrict to one backend

  • --format table|json — override the auto-detected output format

Table columns:

Column
Meaning

SERVER

Backend alias

TOOL

Upstream tool name (not namespaced)

KIND

read, write, or ambiguous

CONF

Classifier confidence (0.00–1.00)

SOURCE

override, annotation, classifier, or fallback

reasons

Which signals fired (name tokens, description patterns, schema hints)

Rows prefixed with [!] are ambiguous — the classifier could not decide. They are treated as write at runtime (fail-safe). Add a tool_acl entry in servers.json to pin them explicitly.

Example (JSON):

The JSON form is an array of {server, tool, kind, confidence, source, reasons} objects, suitable for scripting or diffing across config changes.

See Tool ACL overrides for how to pin tools manually, and docs/acl-redesign-plan.md for the full redesign context.

mcp acl check

Test an ACL decision without starting the proxy. Useful for validating policy changes before rolling them out.

Flags:

  • --subject <name> — subject to check (looks up roles from subjects map in ACL config)

  • --server <alias> — backend server alias (required)

  • --tool <name> — single tool to check

  • --resource <uri> — resource URI to check (mutually exclusive with --tool and --prompt)

  • --prompt <name> — prompt name to check (mutually exclusive with --tool and --resource)

  • --access read|write — override the tool classification (if omitted, the CLI connects to the backend to classify the tool automatically; not applicable to resources/prompts)

  • --role <name> — check a hypothetical role (creates a synthetic identity)

  • --all-tools — connect to the backend, list all tools, and check each one

  • --format table|json — override the auto-detected output format

One of --tool, --resource, --prompt, or --all-tools is required.

Single-tool output:

Multi-tool output (--all-tools):

Exit code: 0 for allow, 1 for deny (single-tool mode only). --all-tools always exits 0 since mixed results are expected.

Examples:

Last updated

Was this helpful?