Skip to main content
Each CLI run connects to a server, invokes the single request you name with --method, prints the result, and exits. That makes it a good fit for CI pipelines, shell one-liners, and coding agents that need to verify a server change immediately.
Running several commands against the same server, or a flow that spans one session (tasks, subscriptions, elicitation)? The mcpdo connection client connects once and keeps the connection open between commands.
The examples below use the installed mcp-inspector binary. Without a global install, prefix each command with npx @modelcontextprotocol/inspector instead, as above.

Choosing a server

The CLI accepts a positional command (stdio), a --server-url (HTTP/SSE), or a named server out of a catalog or config file:
When the server comes from a file, its per-server settings (headers, timeouts, OAuth, protocol era, and roots) apply to the connection, resolved exactly as the TUI and web client resolve them. A --header flag overrides the file’s headers for that run while leaving its timeouts and OAuth in place. Later examples abbreviate whichever of these forms you use, along with its --transport or --config/--server flags, as <server>.
The config file is the only durable way to give a run its roots: there is no roots flag, and the CLI has no roots/set method. Roots configured for a server are advertised at connect, so a server that calls roots/list (as @modelcontextprotocol/server-filesystem does, to learn its allowed directories) gets them.
See Configuration and flags for --catalog vs. --config, the -- separator, and the shared server-selection flags.

Methods

Stream- or session-only methods (logging/tail, for example) are rejected, since a process that exits can’t hold a stream open.

Passing arguments

--tool-arg takes key=value and coerces each value by JSON-parsing it when it parses, so count=1 and zip=10001 both send numbers. A value that is not valid JSON (zip=012, a bare word) is sent as a string, and a string is then converted to the type the tool’s input schema declares for that property, so zip=012 against a numeric zip still sends 12:
--tool-args-json takes the whole argument object at once and skips the key=value parsing, so {"zip":"10001"} sends the string "10001" rather than a number. The schema conversion still applies to string values, so this only keeps a string a string when the schema types that property as a string (or doesn’t declare it). The two flags are mutually exclusive:

Output

--format text (the default) pretty-prints for humans. --format json emits a single JSON object on stdout with no banners, so the whole output pipes cleanly:

Probing MCP Apps

--app-info reports whether a tool ships an MCP App UI (its ui:// resource, CSP, and permissions) without calling the tool, so a pipeline can decide whether it needs a browser before invoking anything:
Exit codes distinguish the outcomes: a tool with an app exits 0, one with no app exits 2, and a missing tool exits 5, so a typo isn’t mistaken for “no app”. A probe failure (unreadable UI resource, malformed resourceUri) is reported in a resourceError field rather than aborting, so one bad tool never kills a whole listing. The CLI can’t render an App, so by default it doesn’t advertise the MCP Apps extension at initialize. A server that exposes its App tools only to clients claiming App support will then look app-less (exit 2). Add --advertise-apps to claim the extension for that run.
tools/list --app-info always emits NDJSON (one line per tool) regardless of --format; --format json reshapes only the single-tool output of tools/call --app-info.

Exit codes and error envelopes

Every non-zero exit maps to a stable failure class, so a caller can branch on why without scraping prose: On any non-zero exit the CLI also writes a single JSON line to stderr:
The envelope carries code and message always, plus cause (the underlying error, such as a DNS failure), status (the HTTP status) and url when known. Query-string secrets in a URL are redacted. code is a stable name for the failure: auth_required, unreachable, tool_not_found, tool_is_error, no_app, schema_unportable, store_unavailable, oauth_state_unrecognized, or the catch-all error, among others. Because it’s one line, a caller can parse it with 2>&1 | tail -1 | jq .error. A tools/call that returns isError: true still prints its payload, but exits 5, so an && chain doesn’t proceed on a failed call.

Authorization in scripts

By default the CLI runs the same loopback OAuth flow as the TUI: it opens a browser and waits on a localhost callback that a CI job can’t complete. Two flags make non-interactive runs predictable:
  • --stored-auth-only: never start interactive OAuth or step-up, and never auto-open a browser. Use tokens from the shared store if present, otherwise fail immediately with auth_required. This is the flag CI wants.
  • --use-stored-auth: reuse a token that the web Inspector already obtained on this machine, refreshing it first when a refresh token is stored. It looks the token up by URL, so it requires --server-url.
Without either, and with no TTY on stdin or stderr, the CLI fails fast with auth_required rather than hanging for fifteen minutes on a callback nobody will complete. See Authorization for the full flow, the web-to-CLI handoff, and --print-handoff.

Recipes

Verify a server in CI

CI gates

Three flags turn a check into a non-zero exit, each with its own code so a job can tell the failures apart:
  • --strict (with tools/list): reports tool-schema portability problems in full on stderr (path, issue, suggested fix) and exits 6 if any is error-severity. Without it, tools/list prints only a one-line count. Under --format json the findings are folded into the output as schemaFindings.
  • --verify (with skills/list or skills/get): runs the SEP-2640 conformance and digest checks, writes one JSON report per skill on stdout, and exits 7 on a violation or 8 when the read bounds stopped it before every skill was checked. --skill-catalog-max-skills and --skill-catalog-max-bytes set those bounds.
  • --require-digests (with --verify): exits 9 for a skill that advertises no digests, instead of reporting it as unverifiable and exiting 0.

Branch on the failure class

Smoke-test every tool that has a UI

Inspect a catalog without connecting

servers/show redacts secret-bearing fields (env values, sensitive headers, OAuth client secrets), but it does not scrub credentials embedded in a server url (userinfo or query tokens) or in stdio args. Treat raw URL and detail fields as sensitive before pasting them into an issue.

Editing the catalog

servers/add, servers/edit, and servers/remove write the catalog without connecting to anything. They go through the same code as the web client’s server list, so env values and client secrets land in the secret store exactly as they would from the UI, and a running web client picks up the change.
Only the writable catalog (--catalog, MCP_CATALOG_PATH, or the default) can be written; --config is refused. With an in-memory secret store, a write that supplies -e values (and a --rename) is refused, since the secrets would be lost when the run exits.

Proxies

Connections to remote HTTP/SSE servers honor the conventional proxy variables: HTTPS_PROXY / HTTP_PROXY (and their lowercase forms) select the proxy and NO_PROXY exempts hosts. No Inspector-specific flag is needed, and the proxy agent is loaded lazily, so runs without a proxy pay nothing. The same applies to the web client’s backend.