▸ Agent Skills
6 min read

Documentation Index

Fetch the complete documentation index at: https://modelcontextprotocol.io/llms.txt Use this file to discover all available pages before exploring further.

CLI client

Scripting the MCP Inspector: methods, output formats, exit codes, and CI recipes

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.

npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list

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:

# stdio: everything positional is the command to spawn
mcp-inspector --cli node build/index.js --method tools/list

# HTTP
mcp-inspector --cli https://api.example.com/mcp --transport http --method tools/list

# From a file
mcp-inspector --cli --config ./mcp.json --server myserver --method tools/list

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>.

<Note> The config file is the only durable way to give a run its roots: there is no roots flag, and --method roots/set applies only to that one short-lived connection. 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

--methodRequired companionsNotes
initializeNoneConnect-only probe: {serverInfo, protocolVersion, capabilities, instructions}.
tools/listNone
tools/call--tool-name, plus --tool-arg / --tool-args-json
resources/listNone
resources/read--uri
resources/templates/listNone
prompts/listNone
prompts/get--prompt-name, --prompt-args
logging/setLevel--log-levelLegacy era only; modern servers opt in per request instead.
servers/list, servers/showNoneRead the catalog without connecting to anything.

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 values by JSON-parsing them, so count=1 becomes a number and "012" becomes 12:

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-arg key=value --tool-arg count=1 --tool-arg 'options={"format":"json"}'

--tool-args-json takes the whole argument object at once and passes it verbatim, with no coercion, so "012" stays the string 012. The two are mutually exclusive:

mcp-inspector --cli <server> --method tools/call --tool-name mytool \
  --tool-args-json '{"zip":"10001"}'

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:

mcp-inspector --cli <server> --method tools/list --format json | jq '.result.tools[].name'

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:

# One tool -> one JSON line
mcp-inspector --cli <server> --method tools/call --tool-name my_tool --app-info
# {"hasApp":true,"toolName":"my_tool","resourceUri":"ui://...","csp":{...},"permissions":{...}}

# Every tool -> NDJSON, one line each, over a single connection
mcp-inspector --cli <server> --method tools/list --app-info | jq -c 'select(.hasApp)'

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.

<Note> 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:

CodeMeaning
0Success.
1Usage or unexpected error (the catch-all).
2No MCP App found on the tool (--app-info probe).
3Server requires authentication (401/403, WWW-Authenticate, OAuth).
4Server unreachable (DNS, connection refused, timeout, fetch failed).
5Tool error: tools/call returned isError: true, or the tool wasn’t found.

On any non-zero exit the CLI also writes a single JSON line to stderr:

{
  "error": {
    "code": "auth_required",
    "message": "Unauthorized",
    "status": 401,
    "url": "https://api.example/mcp"
  }
}

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.

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

set -euo pipefail

# Fail the build if the server can't be reached or doesn't expose the tool
mcp-inspector --cli --config ./ci-servers.json --server my-server \
  --stored-auth-only --method tools/list --format json \
  | jq -e '.result.tools | map(.name) | index("get_weather")' > /dev/null

Branch on the failure class

if out=$(mcp-inspector --cli "$URL" --transport http --method tools/list 2>err.json); then
  echo "$out"
else
  case $? in
    3) echo "needs auth: run the web inspector once to sign in" ;;
    4) echo "server unreachable" ;;
    *) jq .error < err.json ;;
  esac
fi

Smoke-test every tool that has a UI

mcp-inspector --cli "$URL" --transport http --method tools/list --app-info \
  | jq -r 'select(.hasApp) | .toolName'

Inspect a catalog without connecting

mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/list
mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/show --server my-server

<Warning> 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.

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.


Last updated Oct 08, 2026