Part 18 of 22

How to test an MCP server with MCP Inspector

LLM Mart · Sep 17, 2026 · 4 views 221 listing impressions
How to test an MCP server with MCP Inspector

MCP Inspector connects directly to a local or remote MCP server so you can see exactly what it exposes and call each capability yourself. Use the web client to explore interactively, the CLI to assert behaviour in CI, and the protocol and network views to diagnose failures that an AI client would only report as "the tool didn't work."

Testing through a chat client tells you whether a model chose your tool. Testing through Inspector tells you whether your tool is correct. Those are different questions, and only the second one is under your control.

Install and launch

Inspector ships as a single package, @modelcontextprotocol/inspector, with three clients behind one binary. It requires Node 22.19.0 or newer and runs through npx with no installation.

Client Invocation What it is for
Web npx @modelcontextprotocol/inspector A graphical inspector in the browser. The default and the richest surface.
CLI npx @modelcontextprotocol/inspector --cli A scriptable, machine-readable client for CI and shell pipelines.
TUI npx @modelcontextprotocol/inspector --tui A terminal UI, when a browser is not available.

All three share one core, so a connection behaves identically across them: the same transports, the same configuration files, the same OAuth state on disk.

The mode flag must come first. The first token that is not --web, --cli, or --tui ends launcher parsing, and everything after it is forwarded to the client unchanged — which is what lets a literal --cli appear later as one of your own server's arguments.

Connect to the server under test

Point Inspector at a stdio command, a remote URL, or a named server in a config file.

# stdio: everything positional is the command to spawn
npx @modelcontextprotocol/inspector node build/index.js

# Remote Streamable HTTP
npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http

# A published package, without installing it globally
npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem ~/Desktop

Read the server's own README first. Every server takes different arguments, and a server launched without its required environment usually connects and then exposes nothing, which looks like a bug in your test rather than a missing variable.

Protect the web client's session token

The Node server behind the web client can spawn processes on your machine, so it guards every /api/* route with a per-launch token. The launcher prints a URL containing that token. Open the printed URL rather than typing localhost:6274 from memory.

You can pin a known token with the MCP_INSPECTOR_API_TOKEN environment variable for scripted launches. DANGEROUSLY_OMIT_AUTH=true disables the check entirely and should only ever be used on a machine where nothing else can reach the port.

Start with capability discovery

The first meaningful test is what the server claims to be. In the web client, the tab bar itself is the answer: Tools, Prompts, and Resources appear only when the server declares the matching capability.

npx @modelcontextprotocol/inspector --cli <server> --method tools/list --format json

Compare that list against the server's documentation before you call anything. This is the step that catches the most consequential problem in the ecosystem: a server that describes itself as read-only while advertising tools that delete, publish, or send. If the list and the README disagree, stop and vet the server properly before connecting it to anything real.

resources/list, resources/templates/list, and prompts/list complete the picture. A server that exposes only resources and no tools is not broken — it is a different MCP primitive.

Call tools with valid and invalid input

In the web client, selecting a tool renders its input schema as a form. Fill it, call it, and read the result panel, which handles structured content, embedded resources, and images natively.

From the CLI, arguments come in two mutually exclusive forms, and the difference matters:

# --tool-arg JSON-parses each value: count=1 becomes a number, "012" becomes 12
npx @modelcontextprotocol/inspector --cli <server> --method tools/call --tool-name search \
  --tool-arg query=mug --tool-arg limit=5

# --tool-args-json passes the object verbatim: "012" stays the string 012
npx @modelcontextprotocol/inspector --cli <server> --method tools/call --tool-name lookup \
  --tool-args-json '{"zip":"10001"}'

Use --tool-args-json for anything where a leading zero, a numeric-looking string, or a precise type matters. Zip codes, account numbers, and version strings have all been silently coerced by the convenience form.

Then run the calls you would rather not think about:

Test What you are checking
Valid arguments The happy path returns what the description promises.
A value outside the schema's range The SDK rejects it before the handler runs, with a message the model can act on.
A missing required argument A clear protocol error, not a handler crash.
A record that does not exist A tool error, not a stack trace or an internal identifier.
A record the caller is not authorized to see Denied — and denied the same way as "not found," so the error does not confirm the record exists.

The last two are where servers most often leak information, and neither shows up in a happy-path demo.

Read resources and render prompts

The Resources tab lists resources and templates with their MIME types, reads content on selection, and offers Subscribe on servers that support subscriptions. Confirm that a file:// resource cannot be coaxed outside its allowed root with a traversal path — the specification requires servers to sanitise those, which means it is worth testing that yours does.

The Prompts tab renders the generated messages for the arguments you supply. This is the fastest way to confirm a prompt template produces what you intended, including which arguments actually reach the output. A prompt that silently drops an argument looks fine in the list and wrong in use.

Watch the traffic while you work

Five views form a monitor group: Tasks, Logs, Protocol, Network, and Console. Pin the group and it moves into a resizable right-hand column, so protocol traffic stays visible while you work in Tools or Resources.

  • Protocol is the JSON-RPC transcript: requests, responses, notifications.
  • Network appears for HTTP and SSE servers and shows status codes, headers, and bodies.
  • Console appears for stdio servers and shows the server process's stderr.

Network and Console never appear together, because a server is one or the other.

The Console view resolves the single most common stdio failure. If a connection drops for no visible reason, look for ordinary application logging on stdout: stdout is the protocol channel, and one stray console.log or print() corrupts the JSON-RPC stream. Everything the server wants to say to a human belongs on stderr.

Handle authentication without hanging

By default the CLI runs the same loopback OAuth flow as the interactive clients: it opens a browser and waits on a localhost callback. In CI, nobody completes that callback.

Two flags make non-interactive runs predictable:

  • --stored-auth-only never starts interactive OAuth and never opens a browser. It uses tokens from the shared store if present and fails immediately with auth_required otherwise. This is the flag CI wants.
  • --use-stored-auth reuses a token the web Inspector already obtained on this machine, refreshing it first when a refresh token is stored.

Without either flag, and with no TTY, the CLI fails fast rather than hanging on a callback nobody will complete.

Assert behaviour in CI

Every non-zero exit maps to a stable failure class, so a pipeline can branch on why a run failed without scraping prose.

Code Meaning
0 Success
1 Usage or unexpected error
2 No MCP App found on the tool (--app-info probe)
3 Server requires authentication (401/403, WWW-Authenticate, OAuth)
4 Server unreachable (DNS, connection refused, timeout)
5 Tool error: tools/call returned isError: true, or the tool was not found

On any non-zero exit the CLI also writes a single JSON line to stderr, so a caller can parse it with 2>&1 | tail -1 | jq .error.

A regression test for a server is then a few lines:

set -euo pipefail

# Fail the build if the server is unreachable or has dropped a tool
npx @modelcontextprotocol/inspector --cli --config ./ci-servers.json --server catalog \
  --stored-auth-only --method tools/list --format json \
  | jq -e '.result.tools | map(.name) | index("search_products")' > /dev/null

Because a tools/call that returns isError: true exits 5, an && chain will not proceed past a failed call — which is the behaviour you want when the next step depends on the first having worked.

Keep a small fixed set of regression cases rather than one broad smoke test: one call per tool with known-good arguments, one deliberate schema violation, and one authorization check. Each is cheap, and together they catch the changes that matter.

Catalog files versus config files

Both --catalog and --config name a file of servers, and the difference is write access.

--catalog <path> --config <path>
Writable Yes — Inspector's own server list No, never written or seeded
Missing file Created and seeded Errors
Editable in the web UI Yes No
Use it for Your own working set of servers A read-only session against someone else's config

Use --config whenever you are inspecting a configuration file you did not write — a teammate's, a client application's, or one checked into a repository. It guarantees Inspector will not modify it.

The config file is also the only durable way to give a run its roots: there is no roots flag, and roots configured for a server are advertised at connect time, which servers such as @modelcontextprotocol/server-filesystem rely on to learn their allowed directories.

One caution when sharing output: servers/show redacts secret-bearing fields such as env values and sensitive headers, but it does not scrub credentials embedded in a server url or in stdio args. Treat raw URL and detail fields as sensitive before pasting them into an issue.

Record the evidence

When a server passes, write down what "passes" meant. A short release note is enough:

  • server name, version, and commit or package release;
  • transport and endpoint or launch command;
  • the exact tool, resource, and prompt list at that version;
  • the regression cases run and their results;
  • authentication scopes exercised;
  • anything that failed and was accepted anyway, with the reason.

That record is what makes the next release reviewable. Without it, "we tested it" means whatever the person who tested it happens to remember.

Next step: Test one MCP server with Inspector, then compare its published capabilities with its LLM Mart listing — the gap between the two is the part worth reading closely.

Sources

0 0 0 0 Sign in to react

Comments (0)

Sign in to join the conversation.

No comments yet.