Inspector
Inspectorbeta

Debug a server

beta
v0.1.0-beta.2

The four questions an inspector answers — why a tool is missing, why a call was refused, whether the server is conformant, and what the wire actually said — and which surface answers each.

Four questions cover almost every "it doesn't work" report. Each has a different answer and a different surface.

"My tool isn't there."

Start with what the server actually advertises, rather than what you believe it advertises:

bash
mcpg inspector list http://127.0.0.1:8787/mcp tools --json | jq '.tools[].name'

Two very different things produce an empty or short list.

The plugin behind the binding did not come up. If the server is an mcpg gateway, ask it about itself:

bash
mcpg inspector gateway http://127.0.0.1:8787/mcp

That reads the gateway's own runtime snapshot — readiness, and which plugins loaded in what state. A binding can resolve perfectly in config and still serve nothing because the plugin behind it started degraded; over MCP all you see is a missing tool. The report leads with what is wrong: a gateway running twelve healthy plugins lists none of them.

It exits 5 when something wants attention, so a smoke test can gate on "the gateway is actually serving" rather than "it answered". The data comes from the gateway's /runtime, which mcpg serves without authentication and with operator configuration deliberately left out (sink kinds, not sink settings). Against a server that is not an mcpg gateway, the verb says so rather than guessing.

In the web UI this is the third diagnose tab; in the TUI, g on the diagnose screen.

Or you are anonymous. An empty tool list with no error usually means the trust floor is hiding everything, which is working as intended. See Authorization, and Reach a protected server for getting a credential.

"The server said no."

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

The auth lab makes a deliberately credential-free request and reports what came back — the status, the WWW-Authenticate challenge, and the OAuth discovery it can reach from there. It needs no connected session, because the case you care about is the one where connecting failed.

Reach a protected server covers what it reports and how to get a token.

"Is my server actually conformant?"

bash
mcpg inspector check http://127.0.0.1:8787/mcp

The portable checks assert transport rules a normal client call papers over, so they build their own requests. Each is reported as passed, failed, or skipped, with the frame that decided it. Skipped is not failed — it means the check does not apply to the revision this target negotiated.

For the two probes that go further — latency and schema enforcement — see Gate CI on it.

"What did the wire actually say?"

Every face has a wire view showing raw JSON-RPC frames in both directions, including the malformed ones a typed client would drop. That is usually where a "mysterious" failure stops being mysterious.

bash
# dump frames to stderr while running any verb
mcpg inspector call http://127.0.0.1:8787/mcp dev.mock.echo --args '{"text":"hi"}' --wire

Sending what the schema forbids

Where a tool declares an input schema, arguments are a form: one control per field, typed the way the schema declares it, required fields first, with descriptions and enum choices in place. That typing is the point — a plain text control hands back text for everything, and {"count": "7"} is a different call from {"count": 7}.

The raw JSON is one toggle away and edits the same value (f in the TUI), because sending what a schema does not describe is half of what an inspector is for. Nothing is ever blocked: a value the schema disagrees with is reported and still sent, since finding out how the server handles it is the job.

Where the protocol defines completions — prompt arguments and resource-template variables, not tool arguments — the form can ask the server what fits: suggest beside a field in the web UI, s in the TUI. The values are the server's own, narrowed by whatever is already typed. Nothing is guessed locally, which is why the control only appears where a completion surface exists.

When the server asks you something

MCP runs in both directions. A server may answer a call by asking the client to sample a model, elicit a value, or list its roots — and the call that triggered one waits for the answer.

Those land in pending. Both faces surface it where you already are: the web UI as a banner above whatever pane is open, the TUI by switching to the pending screen and badging the tab. A request filed behind a tab you have not opened is holding a call open that you cannot see, which reads as a hang.

enter sends the answer, x declines — declining is a legitimate answer, not an error. The answer is seeded per request kind and never generated: the inspector will not call a model on your behalf.

For unattended runs, set responder on the target to auto-decline or mock. Under those policies nothing ever queues, which is what the CLI and CI want.

Watching for changes

On subs, an empty watch list is already a real subscription — it follows tools, prompts and resources list-changed notifications. Add comma-separated resource URIs with a to watch those resources' contents too.

A subscription outlives the tab that started it. The stream belongs to the target, so leaving for the tools pane is navigation rather than cancellation, and the tab carries a count of what arrived while you were away.

History

history keeps every tool call, resource read and prompt render: what was sent, what came back, how long it took, and whether it failed. "Load into tools" puts an old call's arguments back in the pane that sent them rather than re-running it — re-running silently is how you lose the difference you were comparing. The TUI has the same screen; enter loads an entry back.

Tools that ship a UI

A tool may ship an HTML app (SEP-1865): _meta.ui.resourceUri names a ui:// resource whose contents are the page. The web UI renders it; the TUI names it and points at the resource, since a terminal cannot run a page.

The inspector is a host pointed at servers nobody trusts, so three rules hold:

  • Nothing runs until you ask. The page renders on a click, and the click says what it is agreeing to.
  • The frame is sandboxed without allow-same-origin. That one attribute is what keeps the page out of this document, its storage, and your session token. The CSP starts at default-src 'none' and adds only what the app declared, so a page that declares nothing reaches nothing.
  • Everything the app asks for is shown — including the calls this host declines, with what was asked, because that is the more interesting half.

See MCP Apps.

Next steps