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

```bash theme={null}
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:

```bash theme={null}
# 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](/docs/2026-07-28/tools/inspector/protocol-eras), 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](/specification/draft/client/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.
</Note>

See [Configuration and flags](/docs/2026-07-28/tools/inspector/configuration) for `--catalog` vs. `--config`, the `--` separator, and the shared server-selection flags.

## Methods

| `--method`                     | Required companions                                   | Notes                                                                            |
| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
| `initialize`                   | None                                                  | Connect-only probe: `{serverInfo, protocolVersion, capabilities, instructions}`. |
| `tools/list`                   | None                                                  |                                                                                  |
| `tools/call`                   | `--tool-name`, plus `--tool-arg` / `--tool-args-json` |                                                                                  |
| `resources/list`               | None                                                  |                                                                                  |
| `resources/read`               | `--uri`                                               |                                                                                  |
| `resources/templates/list`     | None                                                  |                                                                                  |
| `prompts/list`                 | None                                                  |                                                                                  |
| `prompts/get`                  | `--prompt-name`, `--prompt-args`                      |                                                                                  |
| `logging/setLevel`             | `--log-level`                                         | Legacy era only; modern servers opt in per request instead.                      |
| `servers/list`, `servers/show` | None                                                  | Read 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`:

```bash theme={null}
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:

```bash theme={null}
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:

```bash theme={null}
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](/extensions/apps/overview) UI (its `ui://` resource, CSP, and permissions) **without calling the tool**, so a pipeline can decide whether it needs a browser before invoking anything:

```bash theme={null}
# 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`.
</Note>

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

| Code | Meaning                                                                      |
| ---- | ---------------------------------------------------------------------------- |
| `0`  | Success.                                                                     |
| `1`  | Usage or unexpected error (the catch-all).                                   |
| `2`  | No MCP App found on the tool (`--app-info` probe).                           |
| `3`  | Server requires authentication (401/403, `WWW-Authenticate`, OAuth).         |
| `4`  | Server unreachable (DNS, connection refused, timeout, `fetch failed`).       |
| `5`  | Tool 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**:

```json theme={null}
{
  "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](/docs/2026-07-28/tools/inspector/authorization) for the full flow, the web-to-CLI handoff, and `--print-handoff`.

## Recipes

### Verify a server in CI

```bash theme={null}
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

```bash theme={null}
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

```bash theme={null}
mcp-inspector --cli "$URL" --transport http --method tools/list --app-info \
  | jq -r 'select(.hasApp) | .toolName'
```

### Inspect a catalog without connecting

```bash theme={null}
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.
</Warning>

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