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
| Code | Meaning |
|---|---|
0 | Passed, or there was nothing to compare. |
1 | The change exceeded the fail threshold. |
2 | The 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".
| Command | 0 | 1 | 2 |
|---|---|---|---|
snapshot | captured | — | unreachable, bad config |
diff | at or below fail_on | above it | missing, corrupt, or wrong formatVersion |
check | at or below fail_on, or no baseline | above it | unreachable, bad config |
push | uploaded, or the upload failed | — | the snapshot could not be read |
keys | done | — | no token, or a missing argument |
doctor | every 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.