Inspector
Inspectorbeta

Gate CI on it

beta
v0.1.0-beta.2

Exit codes are a contract, so a pipeline can fail on capability drift, on a server that accepts what its own schema forbids, or on a gateway that answered without actually serving.

Every one-shot verb takes --json and prints its result document as the only thing on stdout. That, plus exit codes that mean something, is what makes these usable as gates rather than as things a human reads.

Exit codes

CodeMeaning
0ok
1usage
2connect / probe failed
3auth required
4unreachable
5the operation failed, or the report found something

5 is the interesting one. It separates "I could not ask" from "I asked, and the answer is bad" — a distinction a pipeline needs, because the first is usually infrastructure and the second is usually your change.

Fail a PR when the tool surface drifts

bash
mcpg inspector snapshot https://gw.example/mcp --json > baseline.json
mcpg inspector diff https://gw.example/mcp --against baseline.json --mode strict

diff exits 5 when the diff fails its mode. Commit the baseline, and a config change that quietly adds, removes or reshapes a tool stops being something you discover from a client.

Fail when a server accepts what it forbids

bash
mcpg inspector fuzz https://gw.example/mcp

fuzz derives its cases from each tool's own inputSchema — a missing required property, the wrong type, an oversized string, an unknown property — and reports what came back. A server that enforces its schema rejects the invalid cases and accepts the valid one. Anything else is flagged surprising, and the run exits 5.

It only calls tools that declare readOnlyHint: true. Everything else is skipped by name, with the reason, because these are real calls:

bash
mcpg inspector fuzz https://gw.example/mcp                      # read-only tools
mcpg inspector fuzz https://gw.example/mcp incident.file --include-writes

--include-writes is deliberately awkward to reach for. The tool that forgot to annotate itself is often the one that moves something.

Fail when the gateway answered but is not serving

bash
mcpg inspector gateway https://gw.example/mcp --json \
  | jq '.plugins[] | select(.state != "active")'

The verb exits 5 when a readiness check is not passing or a plugin loaded but is not active. A plain HTTP health check cannot see either — the gateway answers fine while the plugin behind half its tools sits degraded.

Measure latency

bash
mcpg inspector bench https://gw.example/mcp dev.mock.echo -n 50

bench calls one tool -n times and reports min / p50 / p90 / p99 / max in milliseconds, fractional — a local stdio server answers well inside a millisecond. The first calls are warmup (--warmup), so a cold connection is not reported as the server's latency.

Calls go out one at a time. Concurrency measures something else, and an inspector pointed at someone else's server should not be the thing that decides to load-test it.

Conformance in a pipeline

bash
mcpg inspector check https://gw.example/mcp

Checks that do not apply to the negotiated revision are skipped, not failed, so this stays meaningful across both wires without a per-revision matrix.

Unattended runs

A server can ask the client to sample a model, elicit a value, or list roots — and the call that triggered one waits for an answer. In CI there is nobody to answer, so set responder on the target to auto-decline or mock. Under those policies nothing ever queues.

The inspector never generates a model answer on your behalf under any policy.

A worked example

yaml
- name: The tool surface has not drifted
  run: |
    mcpg inspector diff "$GW" --against .ci/tools-baseline.json --mode strict

- name: The gateway is serving, not merely answering
  run: mcpg inspector gateway "$GW"

- name: Tools enforce their own schemas
  run: mcpg inspector fuzz "$GW"
  env:
    GW: ${{ secrets.GATEWAY_URL }}

Each step fails the job on 5 without any parsing, because the exit code already carries the verdict. Add --json and redirect when you want the document as an artifact too.

Next steps