Skip to main content

Connecting stdio vs. HTTP servers

stdio

A stdio server is a process the Inspector spawns. Everything positional is the command line:
Put -- before any arguments meant for your server. Without the separator, --verbose would be parsed by the Inspector and never reach the server. Give the process environment variables with -e and a working directory with --cwd:
The server’s stderr lands in the Console tab (web) or the Console tab (o, TUI), which is where most stdio servers put their diagnostics, so check there first when a connection fails for no visible reason.

HTTP and SSE

--transport accepts http (Streamable HTTP) and sse. If the server is protected, see Authorization: no setup is needed in advance, because when the server answers 401 the Inspector runs the OAuth flow described there and retries the connection. For an HTTP server, also decide its protocol era. The default is legacy; set modern or auto in Server Settings (or protocolEra in the catalog file) to exercise the 2026-07-28 behavior. For an ad-hoc target, pass it at launch with --protocol-era legacy|auto|modern.

Importing an existing client config

On the Servers screen, Add Servers can import MCP servers you have already configured elsewhere instead of retyping them. It parses Claude Desktop, Cursor, Cline, and VS Code client configs directly, and it also reads a server’s own MCP Registry server.json. Import merges into the active catalog (the Inspector’s writable server list). When an imported server’s id is already taken, you choose whether to overwrite, skip or rename it. If you’d rather not touch your catalog at all, launch against the foreign file read-only instead:
--config guarantees the file is served as-is and never written, seeded, or migrated.

Add Servers offers import from an existing client config or from a registry server.json.

Reviewing an MCP App

MCP Apps are tools that carry a UI widget. For an automated reviewer (CI or an agent), use the CLI for every check that returns JSON, and open a browser only to inspect the rendered widget.
1

Probe the security posture without calling the tool

--advertise-apps makes the CLI claim MCP Apps support at initialize. It is off by default because the CLI cannot render an app, but a server that shows its app tools only to app-capable clients would otherwise report no app.One JSON line on stdout; exit 0 if the tool has an app, 2 if not, so an && chain short-circuits:
csp and permissions (and domain, when the resource declares one) live on the UI resource rather than the tool, so --app-info reads that resource. The tool is never called.
2

Get the full result payload, still with no browser

3

Launch the web Inspector once, loopback-only

Pinning MCP_SANDBOX_PORT keeps the address explicit: the app’s UI is served from a separate sandbox port, and your automation needs to know where it is.
4

Navigate one deep link to a rendered widget

appArgs is the tool’s arguments as base64url-encoded JSON, and every deep-link parameter is described under Deep links. autoConnect and autoOpen must both equal the session token, since autoOpen fires a tool call straight from the URL and needs the same gate as autoConnect.
5

Wait on a deterministic signal instead of sleeping

The Apps screen exposes a stable automation contract. Poll these attributes instead of sleeping:

Docker

A container image is published to GitHub Container Registry for linux/amd64 and linux/arm64:
Read the session token from the container logs, or pin it with -e MCP_INSPECTOR_API_TOKEN=<value>. The image defaults to --web, bound to 0.0.0.0:6274 with browser auto-open off, and runs as the non-root node user (uid 1000). It sets DANGEROUSLY_BIND_ALL_INTERFACES=true because a container must bind the wildcard address to be reachable through -p.
Keep the 127.0.0.1: prefix on every published port. A bare -p 6274:6274 publishes on every host interface, putting a backend that spawns processes, and the page that discloses its token, on your local network. The image’s DANGEROUSLY_BIND_ALL_INTERFACES covers the container’s interfaces, not the host’s. See Publish the port on loopback only.
To use the Apps tab, also publish the MCP Apps sandbox port, 6275, and 6278 for an app that declares _meta.ui.domain. Publish each on the same port number inside and out, since the browser is handed the in-container port:

Keeping your servers and secrets

The server list, OAuth state and secrets live under /home/node/.mcp-inspector, in the container’s writable layer, so --rm discards them and every run starts empty. Mount a volume there to keep them:
A container has no OS keychain, so where secrets go depends on that volume: If you bind-mount a host directory instead of a named volume, it keeps its host ownership, so on Linux add --user "$(id -u):$(id -g)" or chown it to uid 1000, or saves fail with EACCES. Don’t bind-mount the secrets file on its own: it isn’t recognized as durable, and it can’t be replaced atomically.
Mounting that volume turns on file storage of secrets, and without a key the file is plaintext. Every OAuth token acquired, and every client secret and stdio env: value you save, is then written to secrets.json on the volume. It is readable by root and every member of the host’s docker group, and by anyone who gets a backup, snapshot or copy of the volume.Give it a key, generated into a file that only you can read and that sits outside the volume, its backups and any repository:
Even encrypted, secrets on disk carry moderate risk. See what the file store protects against.
Hand the key to the container as a file with MCP_INSPECTOR_SECRET_KEY_FILE, not as an environment variable. A key passed with -e MCP_INSPECTOR_SECRET_KEY=… is readable by anyone who can run docker inspect or docker exec.
Without Swarm, Compose secrets are bind mounts that keep the host file’s owner and mode, so the 0600 key file must be owned by uid 1000. On a Linux host where your uid is different, run sudo chown 1000 ~/.config/mcp-inspector/secret-key rather than loosening its mode. Supply the same key on every run. If the key file is missing, unreadable or empty, or both key variables are set, the Inspector refuses to read or write the secrets file rather than falling back to plaintext, and says why in the log and in the settings dialogs’ footer. Everything else about the store (selection order, location, permissions) is under Where secrets are stored.

Health checks and other modes

The image’s HEALTHCHECK probes the web UI at the address HOST binds. --cli and --tui have no web server, so the probe detects those modes from the container’s arguments and reports healthy while they run. An external orchestrator (a Kubernetes probe, a Compose healthcheck) can call GET /healthz on the web port, which needs no token and returns only {"status":"ok"}. <target> below is an ad-hoc target: a positional stdio command, or --server-url <url> --transport http.
If you remap the published port, set ALLOWED_ORIGINS. With -p 127.0.0.1:8080:6274 the browser’s origin becomes http://localhost:8080, which no longer matches the in-container port, and connects will 403. Either run -e CLIENT_PORT=8080 -p 127.0.0.1:8080:8080, or set -e ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080.

Hosting on a network

The Inspector binds 127.0.0.1 by default and its backend spawns processes, so treat exposing it to a network as a deliberate decision. The Inspector refuses to bind the wildcard all-interfaces addresses (0.0.0.0, ::, and every equivalent spelling) unless you set DANGEROUSLY_BIND_ALL_INTERFACES=true. Binding a specific address is allowed with no opt-in, because that’s one deliberate exposure rather than every interface at once, which is the shape DNS-rebinding attacks target.
ALLOWED_ORIGINS replaces the default list rather than merging with it. List every origin you’ll browse from, including the loopback forms you want to keep:
Each entry must include the scheme; a scheme-less value is dropped with a warning. A blank value does not disable the check; it falls back to the default. There is no knob to turn origin validation off.
Two further caveats when going off loopback:
  • MCP Apps need their sandbox port reachable too. It’s a separate listener (6275 by default, set with MCP_SANDBOX_PORT), so expose or forward it alongside the web port.
  • Behind TLS or a reverse proxy, give MCP Apps their public addresses. By default the sandbox is advertised as http://<bind host>:6275, which an https:// page blocks as mixed content. Set MCP_SANDBOX_FULL_ADDRESS (for example https://inspector-sandbox.example.com/sandbox) and, for apps that declare _meta.ui.domain, MCP_APP_ORIGIN_FULL_ADDRESS (for example https://inspector-apps.example.com). Each needs its own hostname or port; an address that shares the Inspector’s origin is refused.
  • MCP Apps can’t render at a bare IPv6 literal. A bracketed IPv6 literal isn’t a valid CSP host-source, so browse at a name or an IPv4 address.
Whatever the shape: keep authentication on. Do not set DANGEROUSLY_OMIT_AUTH on anything reachable by anyone but you. The reasoning is under Security.

Keeping a connection open with mcpdo

When an exploration spans many calls (a multi-step flow over one session, a log you watch, an agent using a server’s tools mid-session), reconnecting for every --cli invocation gets in the way. mcpdo holds the connection for you:
A connection runs one call at a time, so commands against the same connection from other shells wait their turn. The daemon that holds the connection starts automatically and exits about a minute after the last connection closes. Read The mcpdo connection daemon before using it on a shared machine.

Development workflow

A loop that works well in practice:
1

Start with the CLI

--method initialize confirms the server starts, handshakes, and reports the capabilities you expect, in one second, with a machine-readable answer. Most “it doesn’t work” turns out to be here.
2

Move to the web client for exploration

Schema-driven forms, rendered results, and the Protocol tab beside them make it fast to find the case where a tool misbehaves.
3

Test the edges

Invalid inputs, missing required prompt arguments, concurrent calls, and, for HTTP servers, both protocol eras. Verify the errors are as intentional as the successes.
4

Lock it in with the CLI

Turn what you found into a CI assertion: pipe the CLI’s --format json output to jq -e with --stored-auth-only, so a missing token fails fast instead of starting interactive OAuth. See Verify a server in CI for the full command.