MCP Inspector: testing and debugging MCP servers

Kashish Hora

Kashish Hora

Co-founder of AgentCat

Try out AgentCat

The quick answer

MCP Inspector is the official, browser-based tool for testing and debugging MCP servers without wiring up a real client. Run it with npx, no install:

# Local Node/TypeScript server (point at your built entrypoint)
$npx @modelcontextprotocol/inspector node build/index.js

It opens a UI at http://localhost:6274 where you can list and call tools, read resources, render prompts, and watch the raw JSON-RPC traffic. There is also a --cli mode for scripting the same operations in CI, and, as of Inspector 2.0.0, a --tui mode: an interactive terminal UI (built with Ink) for driving the same session from a terminal, no browser required.

The proxy requires an auth token by default. The Inspector prints a tokenized URL on startup and opens your browser to it automatically. Open that URL, not a bare localhost:6274, or the UI can't reach the proxy. The why is CVE-2025-49596, covered below.

What MCP Inspector actually is

It's two processes that start together when you run the npx command:

  • The client (web UI) on port 6274: the React app you interact with.
  • The proxy server (labelled MCPP in logs) on port 6277: it spawns your stdio server as a subprocess, or relays to a remote HTTP server, and forwards JSON-RPC between the UI and your server.

Both bind to localhost only by default. That two-process split is the reason for the auth token: the proxy can launch arbitrary local processes, so it must not accept commands from anything but your authenticated UI. (README, Architecture / Security Considerations)

The Inspector is a manual and scripted testing tool. It is not a replacement for unit tests (use your SDK's in-memory client for those; see writing unit tests for MCP servers); it's the tool you reach for when you want to see what your server exposes and exercise it by hand, or smoke-test it in CI.

Prerequisites

  • Node.js >=22.19.0. This is the Inspector's engines requirement in package.json as of 2.0.0 (2026-07-28), up from ^22.7.5 in the older 0.22.0 line, and it's not a performance suggestion. On older Node you'll get an engine warning or a hard failure. (package.json engines)
  • A runnable MCP server. For a local server that means a built entrypoint (build/index.js, a Python module, etc.); for a remote one, a reachable URL.
  • npx (bundled with Node). For Python servers you'll also want uv/uvx on your PATH.

Launching it (UI mode)

The launch pattern is always npx @modelcontextprotocol/inspector <command> <args...>. Everything after the package name is how to start your server, exactly as a client like Claude Desktop would. (modelcontextprotocol.io, Getting started)

# A local Node/TypeScript server
$npx @modelcontextprotocol/inspector node build/index.js args...
 
# An npm-published server package (note the inner `npx`)
$npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /Users/you/Desktop
 
# A PyPI-published server via uvx
$npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/servers.git
 
# A locally developed Python server via uv
$npx @modelcontextprotocol/inspector \
$ uv --directory path/to/server run package-name args...

All four are quoted from the official docs. The mental model: the part before your server command configures the Inspector; the part after is your server's own launch line.

Passing environment variables and arguments to your server

Use -e key=value to inject environment variables into the spawned server process. If your server takes flags that could be mistaken for Inspector flags, put -- between them (README, From the command line):

# Inject env vars into the server process
$npx @modelcontextprotocol/inspector -e API_KEY=test123 -e DEBUG=true node build/index.js
 
# Use `--` so server-side flags aren't parsed by the Inspector
$npx @modelcontextprotocol/inspector -e API_KEY=$API_KEY -- node build/index.js -e server-flag

This mirrors how a real client launches you with a restricted environment, a common source of "works in my shell, breaks under the client" bugs, since spawned MCP servers don't inherit your full interactive environment.

Custom ports

If 6274 or 6277 is taken, override them (README, Configuration):

$CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector node build/index.js

stdio vs Streamable HTTP vs SSE

The MCP spec defines exactly two transports today: stdio (local, the default) and Streamable HTTP (remote, single endpoint, usually /mcp). The older two-endpoint HTTP+SSE transport has been deprecated since the 2025-03-26 spec revision and is back-compat only. The Inspector still supports all three so it can talk to legacy servers, but for new work you want stdio locally and Streamable HTTP remotely.

In the UI, the Server connection pane has a transport selector. (modelcontextprotocol.io, Server connection pane) You can also preselect the transport and target URL via query params on the Inspector URL, which is handy for bookmarks (README, Transport / URL params):

# Streamable HTTP server
http://localhost:6274/?transport=streamable-http&serverUrl=http://localhost:8787/mcp

# Legacy SSE server (deprecated transport)
http://localhost:6274/?transport=sse&serverUrl=http://localhost:8787/sse

For repeatable setups, a config file is cleaner than long command lines. Inspector 2.0.0 splits this into two flags with different semantics, and they're mutually exclusive with each other and with an ad-hoc server command on the same line (docs, MCP server configuration):

  • --catalog <path> is the Inspector's own writable server list — the one its server-management UI reads from and saves back to. Point it at a file that doesn't exist yet and the Inspector creates and seeds it. Defaults to ~/.mcp-inspector/mcp.json (override with MCP_CATALOG_PATH).
  • --config <path> is a read-only session file: the Inspector loads it as-is and never writes to it, seeds it, or migrates it. Use this when you're pointing at a config that isn't yours to edit — a coworker's, a client application's, one checked into a repo — including one holding plaintext secrets you don't want the Inspector touching. A missing --config file is an error, not something to seed.

Both use the same mcp.json shape, and the type field picks the transport:

{
  "mcpServers": {
    "local": {
      "type": "stdio",
      "command": "node",
      "args": ["build/index.js", "arg1"],
      "env": { "API_KEY": "test123" }
    },
    "remote": {
      "type": "streamable-http",
      "url": "http://localhost:3000/mcp"
    }
  }
}

--server <name> selects a named entry out of whichever file you pointed at, but it only takes effect under --cli; the web UI ignores it (with a warning if you also passed --catalog/--config) and lets you pick a server interactively instead. So the CLI form is:

$npx @modelcontextprotocol/inspector --cli --catalog mcp.json --server local --method tools/list

(Before 2.0.0, a single --config/--server pair did double duty as the writable list; that shape is gone, and --config is now read-only.) The UI's "Server Entry" and "Servers File" buttons still generate this shape from your current connection, so you can copy a working config straight into Cursor, Claude Code, or the Inspector's own CLI. (README, Servers File Export)

The proxy auth token and CVE-2025-49596

In June 2025, Oligo Security disclosed CVE-2025-49596, a critical remote-code-execution flaw. Because the proxy can spawn local processes and earlier versions ran without authentication, a website you merely visited could reach localhost:6277 from your browser and drive the proxy into executing commands on your machine. It wasn't only an "exposed to the internet" risk; it was exploitable purely client-side.

The fix, shipped in the hardened Inspector starting with 0.22.0 and still in place in the current 2.0.0 line:

  • The proxy requires a bearer token by default. On startup it prints a random session token and a pre-filled URL, and auto-opens your browser to it (README, Authentication):

    🔑 Session token: 3a1c267fad21f7150b7d624c160b7f09b0b8c4f623c7107bbf13378f051538d4
    
    🔗 Open inspector with token pre-filled:
       http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=3a1c267fad...

    If you open a bare http://localhost:6274 instead, the UI loads but can't talk to the proxy. Use the tokenized URL, or paste the token into Configuration → Proxy Session Token in the sidebar.

  • localhost-only binding. Both services bind to localhost; override with HOST=0.0.0.0 only in a trusted environment. (README, Local-only Binding)

  • DNS-rebinding protection. The proxy validates the Origin header; add trusted origins with ALLOWED_ORIGINS. (README, DNS Rebinding Protection)

You can pin a known token (useful in scripts/containers) or, against your better judgment, disable auth entirely:

# Set a fixed proxy token
$MCP_PROXY_AUTH_TOKEN=$(openssl rand -hex 32) npx @modelcontextprotocol/inspector node build/index.js
 
# Disable auth — do NOT do this on a machine that browses the web
$DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector node build/index.js

The README's own warning is blunt: disabling auth "leaves your machine open to attack not just when exposed to the public internet, but also via your web browser." Treat DANGEROUSLY_OMIT_AUTH as a last resort, never a default. (README, Authentication warning)

A realistic debugging workflow

The tabs map onto the three things an MCP server exposes (tools, resources, prompts) plus a notifications/history pane for the wire traffic. A productive loop looks like this:

  1. Launch and confirm the connection. Start the Inspector against your server and watch it connect — the initialize handshake for a legacy-era server (2025-11-25 or earlier), or Inspector's modern per-request probe for a server on the 2026-07-28 revision. If it fails here, you have an initialization or transport problem, not a tool problem. Check the Notifications pane for the server's stderr.
  2. Tools tab → list, then call. The Inspector renders each tool's input schema as a form. Call a tool with valid input, confirm the result, then deliberately send invalid input and a missing required field. You're testing that your error responses are clean and don't leak internals.
  3. Resources tab. List resources, open one, verify the MIME type and that the content is what you serialized. Watch for resources that hang or return a different shape than declared.
  4. Prompts tab. Provide sample arguments and confirm the rendered messages are what a model would actually receive.
  5. Read the raw JSON-RPC. The notifications/history view shows the actual request/response frames. This is where serialization bugs surface: a tool that "works" but emits malformed content blocks shows up here long before a real client gives you a useful error. (If you're chasing one of those, see debugging message serialization errors.)

The Inspector's documented best-practice loop is the same idea: launch → verify connectivity and capability negotiation → make a change, rebuild, reconnect, retest, monitor messages → then hammer edge cases (invalid inputs, missing prompt arguments, concurrent operations). (modelcontextprotocol.io, Best practices)

CLI mode for scripting and CI

--cli runs the same operations headless, printing JSON to stdout, ideal for CI smoke tests and feedback loops with coding assistants. The shape is --cli <server command> --method <method>, with the server command first and the method as a flag. (README, CLI Mode)

# List tools
$npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
 
# Call a tool (repeat --tool-arg per argument)
$npx @modelcontextprotocol/inspector --cli node build/index.js \
$ --method tools/call --tool-name mytool --tool-arg key=value --tool-arg another=value2
 
# Pass a structured/JSON argument
$npx @modelcontextprotocol/inspector --cli node build/index.js \
$ --method tools/call --tool-name mytool --tool-arg 'options={"format": "json", "max_tokens": 100}'
 
# List resources / prompts
$npx @modelcontextprotocol/inspector --cli node build/index.js --method resources/list
$npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list

Note the shape: the server command comes first, then --method with --tool-name/--tool-arg (as above). The --cli list-tools / --cli call-tool name '{...}' form is obsolete. (README, CLI Mode)

CLI mode hits remote servers too. The default remote transport is SSE, so pass --transport http for a Streamable HTTP server, and --header for auth headers (README, CLI Mode, remote servers):

# Streamable HTTP remote server, with an API-key header
$npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com \
$ --transport http --method tools/list --header "X-API-Key: your-api-key"

Because output is JSON, you can pipe it into jq and assert on it in a CI step: for example, fail the build if a renamed tool disappears from tools/list.

Testing OAuth-protected servers

Remote MCP servers increasingly sit behind OAuth 2.1, and the Inspector has a dedicated Authentication / OAuth debugger for exactly this. It offers two paths (DeepWiki, Authentication & OAuth, Auth0, Test your MCP server with MCP Inspector):

  • Quick OAuth Flow: runs the full authorization-code-with-PKCE handshake end to end and connects.
  • Guided OAuth Flow: steps through each stage (metadata discovery → registration → authorization → token exchange) and shows you exactly where it is, surfacing the authorization code for manual copying. This is the one you want when a flow is failing: it tells you which step broke instead of just "auth error."

It implements OAuth 2.1: Authorization Code + PKCE, Protected Resource Metadata discovery at /.well-known/oauth-protected-resource (RFC 9728), dynamic client registration with fallback to pre-registered credentials, automatic scope discovery, and token refresh. One framing update as of the 2026-07-28 spec revision: Dynamic Client Registration (RFC 7591) — the mechanism behind that "dynamic registration with fallback" flow — is now deprecated, in favor of Client ID Metadata Documents (CIMD), where the client's client_id is itself an HTTPS URL that the authorization server fetches to get the client's metadata, no registration round-trip needed. DCR keeps working (deprecations carry a minimum 12-month window, and DCR is still the only option for authorization servers that don't support CIMD), so the Inspector's registration flow remains useful, but CIMD is now the spec's recommended path for new integrations. For a server that takes a static bearer token rather than a full OAuth flow, you can instead just enter the token in the connection sidebar and the Inspector sends it in the Authorization header. (README, Authentication)

Timeouts for slow tools

Long-running tools (or ones that elicit user input) can outlast the Inspector's client-side request timeout, which then cancels a request your server is still working on, easy to misread as a server hang. The relevant knobs, set as env vars or in Configuration (README, Configuration):

VariableWhat it doesDefault
MCP_SERVER_REQUEST_TIMEOUTInspector cancels a request after this many ms300000
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESSReset the timeout each time a progress notification arrivestrue
MCP_REQUEST_MAX_TOTAL_TIMEOUTHard ceiling even with progress resets (ms)60000
$MCP_SERVER_REQUEST_TIMEOUT=600000 npx @modelcontextprotocol/inspector node build/index.js

These are Inspector-side (it's acting as the MCP client) and independent of your server's own timeouts; if the server times out first, you'll see the server's error instead. (README, Note on Timeouts)

Common gotchas

  • "Connection refused" / blank UI. Usually you opened a bare localhost:6274 without the token. Re-open the tokenized URL from the console, or paste the token into Configuration. Also check nothing else holds the ports: lsof -i :6274 and lsof -i :6277.
  • Server "crashes on connect." For a server still speaking 2025-11-25 or earlier, the Inspector sends initialize immediately, and if your server throws during startup it dies before you see a tool. For a server on the 2026-07-28 revision there's no initialize handshake to fail on — Inspector connects using the stateless per-request model instead, and a startup crash still surfaces on the first request. Either way, the server's stderr is captured in the Notifications pane. Read it there.
  • A stray print() kills a stdio server. Over stdio, stdout is the JSON-RPC channel. Any non-JSON byte written to stdout (a print, a chatty library banner) corrupts the stream and the connection drops with an unhelpful error. Route all logging to stderr. This is a transport property, not an Inspector quirk.
  • Missing env vars. A spawned server doesn't inherit your interactive shell's environment. If a tool fails only under the Inspector, an unset API_KEY/config var is the usual cause. Pass it with -e.
  • Stale UI after a rebuild. The Inspector doesn't hot-reload your server. Rebuild, then click reconnect (or restart the Inspector) so it re-spawns the new build.
  • npx caching an old version. npx can serve a cached Inspector. If a flag from this guide is missing, force the latest: npx @modelcontextprotocol/inspector@latest ....

Where the Inspector stops

The Inspector is excellent at the development loop: it's interactive, immediate, and shows you the wire. What it can't tell you is what happens in production: which tools real clients actually call, what arguments they send, where calls error or stall once your server is serving live traffic across stdio and HTTP. That's a different question (observability, not testing), and it's the gap AgentCat fills for MCP servers in production. Use the Inspector to get a server correct; use analytics to learn how it's used.