CLI

MCP Inspect CLI reference for snapshot, diff, check, watch and push. Compare MCP contracts locally or in CI without an account.

mcp-inspect <command> [options]

#Commands

#init

mcp-inspect init --snapshot
mcp-inspect init --server search --snapshot
mcp-inspect init --workflow

Discover a server and capture its baseline in one step with --snapshot. Use --server to select a named server when a client config contains several. --workflow generates a repository-appropriate GitHub workflow without replacing an existing workflow. Review its build command and configure server secrets in CI.

A capture establishes a baseline; it does not check compatibility. After changing and rebuilding your server, compare locally with mcp-inspect check --baseline-file .mcp-inspect/surface.json.

#snapshot

Capture the MCP surface.

mcp-inspect snapshot
mcp-inspect snapshot --stdio "node dist/server.js"
mcp-inspect snapshot --url https://api.acme.com/mcp --header "Authorization: Bearer $T"
mcp-inspect snapshot --out surface.json --pretty --push

Only server/discover and the */list methods are ever called — all read-only by specification. The inspector never invokes a tool. Pagination is exhaustive: on hitting a page cap it fails rather than recording a truncated surface, because a truncated list reads as a mass removal on the next diff.

An HTTP server that answers 401 starts the MCP authorization flow: resource metadata, authorization server metadata, PKCE, a browser tab, a redirect back to a listener on 127.0.0.1. It runs when a terminal is attached (or with --oauth); in CI a 401 is an error that names the alternatives, such as --header "Authorization: Bearer …" or --oauth-client-id. The token stays in memory for one process and is never written to a snapshot, a config, or a file.

Use --out - --raw capture.json to pipe the normalized JSON while retaining raw protocol evidence in a file. Explicit --out or --raw paths require --server <name> when capturing several configured servers.

#diff

mcp-inspect diff old.json new.json --format markdown --fail-on POTENTIALLY_BREAKING

Formats: text (default), markdown, json, sarif. The JSON report is a supported integration surface and is versioned.

#check

Snapshot the working tree's server, resolve a baseline, diff, apply ignores, exit.

mcp-inspect check --baseline origin/main
mcp-inspect check --format markdown --output comment.md

Baseline resolution, in order: --baseline-file, then a git ref (git show <ref>:<path>), then the hosted API, then nothing. A missing baseline is explicitly reported as comparison skipped, with exit 0 so the setup PR can merge. It is not evidence of a passing comparison.

For automation, --format json --output report.json writes a report even on exit 1. A missing baseline reports status: "skipped" and compared: false. Checks covering several servers emit one JSON document with { formatVersion: 1, reports: [{ server, report }] }; inspect each named outcome. SARIF contains one run per server with runs[].properties.server and retains skipped-comparison metadata. Selecting --server <name> gives the existing single-report shape. The process exits with the worst code across the checks; a skipped entry still needs a baseline before its compatibility can be assessed.

Under the summary, one advisory line compares the declared version with what the diff found — breaking changes suggest a major bump to 3.0.0 — when both surfaces declare semver. It never changes the verdict.

#watch

mcp-inspect watch --path src

The develop loop: snapshot, diff against the baseline, print, then re-run on every save under the watched paths. What it prints is what check prints in CI. Ctrl-C stops it.

#doctor

Config, server reachability, what it advertises, baseline, hosted status. The command to run when check did something unexpected.

#keys

mcp-inspect keys list
mcp-inspect keys create --kind api --name github-actions
mcp-inspect keys rotate key_01k4…
mcp-inspect keys revoke key_01k4…

The plaintext goes to stdout alone and everything else to stderr, so mcp-inspect keys create … > key.txt captures the key and nothing else.

Rotation mints a replacement and leaves the previous key working for a week. A rotation that breaks every build the instant it happens is one nobody performs twice. revoke stops a key immediately.

#push and usage

push records a snapshot as a deployment. usage prints observed usage for a tool, including argument presence and the observation window. Both need an account; push exits 0 even when the upload fails, because losing a snapshot must never break a build.

#Exit codes

CodeMeaning
0Passed, or there was nothing to compare.
1The change exceeded the fail threshold.
2The command could not run: server unreachable, bad config, bad JSON.

2 is never 0. A snapshot that could not be taken is not "no changes".

Command012
snapshotcaptured—unreachable, bad config
diffat or below fail_onabove itmissing, corrupt, or wrong formatVersion
checkat or below fail_on, or no baselineabove itunreachable, bad config
pushuploaded, or the upload failed—the snapshot could not be read
keysdone—no token, or a missing argument
doctorevery check passed or warned—a check failed

#Configuration

.mcp-inspect.yml, resolved from the working directory upward. .mcp-inspect.json is accepted identically.

server:
  command: node dist/server.js # or: url: https://api.acme.com/mcp
  env:
    DATABASE_URL: ${DATABASE_URL} # ${VAR} interpolates; an unset one is an error
  # headers:
  #   Authorization: Bearer ${MCP_TOKEN}
  timeout_ms: 30000

baseline:
  source: git # git | file | api
  ref: origin/main
  path: .mcp-inspect/surface.json

fail_on: BREAKING

ignore:
  - rule: tool.icons.changed
severity_overrides:
  tool.annotations.safetyWeakened: BREAKING

project: acme/search-mcp # hosted; everything above works without it

Several servers in one repository: replace server: with a servers: map, one named entry each, and every command runs against each in turn with its own baseline at .mcp-inspect/<name>.json; --server <name> picks one.

servers:
  api:
    command: node dist/api.js
  internal:
    url: https://internal.example/mcp
    oauth:
      client_id: ${INTERNAL_OAUTH_CLIENT} # optional; `oauth: false` disables the browser flow

An unset ${VAR} is a hard error rather than an empty string. An empty auth header produces a smaller surface, which the next diff reports as a mass removal — a loud failure is the only safe behaviour.

A top-level key within two edits of a real one (sever:, failon:) is an error naming what you probably meant. A genuinely unrelated key is ignored, so a config written for a newer version still loads.