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
MCPPin logs) on port6277: 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'senginesrequirement inpackage.jsonas of2.0.0(2026-07-28), up from^22.7.5in the older0.22.0line, and it's not a performance suggestion. On older Node you'll get an engine warning or a hard failure. (package.jsonengines) - 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 wantuv/uvxon 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/sseFor 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 withMCP_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--configfile 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:6274instead, 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 tolocalhost; override withHOST=0.0.0.0only in a trusted environment. (README, Local-only Binding) -
DNS-rebinding protection. The proxy validates the
Originheader; add trusted origins withALLOWED_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:
- Launch and confirm the connection. Start the Inspector against your server and watch it connect — the
initializehandshake for a legacy-era server (2025-11-25or earlier), or Inspector's modern per-request probe for a server on the2026-07-28revision. If it fails here, you have an initialization or transport problem, not a tool problem. Check the Notifications pane for the server's stderr. - 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.
- 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.
- Prompts tab. Provide sample arguments and confirm the rendered messages are what a model would actually receive.
- 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
--methodwith--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):
| Variable | What it does | Default |
|---|---|---|
MCP_SERVER_REQUEST_TIMEOUT | Inspector cancels a request after this many ms | 300000 |
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS | Reset the timeout each time a progress notification arrives | true |
MCP_REQUEST_MAX_TOTAL_TIMEOUT | Hard 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:6274without 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 :6274andlsof -i :6277. - Server "crashes on connect." For a server still speaking
2025-11-25or earlier, the Inspector sendsinitializeimmediately, and if your server throws during startup it dies before you see a tool. For a server on the2026-07-28revision there's noinitializehandshake 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 (aprint, 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.
npxcaching an old version.npxcan 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.
Related Guides
Debugging message serialization errors in MCP protocol
Debug and fix MCP message serialization errors with proven troubleshooting techniques.
Writing unit tests for MCP servers
Unit-test MCP tool handlers in-process with an in-memory transport: FastMCP's Client and the SDK's connected client-server helpers in Python, and InMemoryTransport.createLinkedPair in TypeScript, including the isError result the SDK returns instead of throwing.
Integration tests for MCP flows
Test a full MCP flow end to end: connect an in-memory client to your real server, run initialize then list then a chain of tool calls, and assert on what came back.