Debug a server
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:
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:
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."
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?"
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.
# 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 atdefault-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
- Reach a protected server — why the call was refused, and how to get a credential.
- Recordings — turn what you just found into a reproduction someone else can replay.
mcpg inspectorreference — every verb and flag.