# MCP Inspector Launcher The launcher is the package that provides the global `mcp-inspector` binary (e.g. when users run `npx @modelcontextprotocol/inspector`). It is not a separate user-facing app—it is the single entrypoint that selects and runs one of the clients (web, CLI, or TUI). ## Responsibility - Parse mode from a leading prefix of `--web` (default), `--cli`, or `--tui` immediately after the script name. - Forward all following arguments unchanged (including tokens that look like mode flags). - Dynamically import that app’s runner from `clients/{web,cli,tui}/build/index.js` (relative to the launcher build output) and call it **in-process** (no `spawn()`). All configuration parsing, config-file loading, and server setup are handled by the app runners and by **core**; the launcher does not interpret server or client configuration, from files or from env vars. Its one exception is the debug switch described below. **Error reporting.** A `--cli` failure is routed through the CLI's own error sink, so `mcp-inspector --cli` preserves the CLI exit-code map (`1` usage, `2` no-app, `3` auth-required, `4` unreachable, `5` tool-error) and its machine-readable `{"error":{…}}` stderr envelope — the same as invoking the CLI bin directly. `--web` / `--tui` failures print a human-readable `Error: ` and exit `1` (append `MCP_DEBUG=1` for the stack). `MCP_DEBUG` (or `DEBUG`) is the only variable the launcher reads itself; the rest are listed in [Environment variables](../../docs/environment-variables.md). ## Web server-list flags (`--web`) `mcp-inspector --web` chooses which server list the UI shows and whether it is editable (see [specification/v2_catalog_launch_config.md](../../specification/v2_catalog_launch_config.md)): | Invocation | Server list | Editable in UI? | | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | --------------- | | `mcp-inspector --web` | Default catalog `~/.mcp-inspector/mcp.json` (seeded with the three sample servers if missing) | Yes | | `mcp-inspector --web --catalog ` (or `MCP_CATALOG_PATH=`) | That file as the active catalog (same seed-if-missing behavior) | Yes | | `mcp-inspector --web --config ` | That file as a **read-only session** — shown but never written, seeded, or migrated (safe for a foreign config) | No | | `mcp-inspector --web --server-url --transport http --header "Name: Value"` (or a positional command) | One ad-hoc server held in memory, connectable with the given `--header`s | No | Rules: `--catalog` and `--config` are mutually exclusive; neither combines with an ad-hoc target, `--header` or `--protocol-era`; `--header` requires an ad-hoc HTTP/SSE server and is applied to that connection (it is no longer a warn-only no-op). `--protocol-era ` requires an ad-hoc server of any transport and sets the era that connection negotiates, so a non-legacy era needs no `mcp.json` entry. **Seed contents are web-specific.** When the web backend creates a missing writable catalog it seeds `DEFAULT_SEED_CONFIG` (`core/mcp/serverList.ts`) — a `filesystem-server-default` scoped to `/tmp`, the canonical `everything-server-default`, and `example-server-default`, the MCP org's remote feature-reference server (Streamable HTTP, no local process and no API key needed) — so a first launch has something to connect to. The CLI and TUI seed an **empty** catalog instead; see the next section. A read-only `--config` is never seeded on any surface. ## CLI and TUI server-list flags (`--cli` / `--tui`) The CLI and TUI use the same `--catalog` / `--config` vocabulary as `--web`, resolved by the shared `core/mcp/node/config.ts` helpers: | Invocation | Server list source | | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | `mcp-inspector --cli` / `--tui` (no source flag, no ad-hoc target) | Default writable catalog `~/.mcp-inspector/mcp.json` (created/seeded empty if missing) | | `--catalog ` (or `MCP_CATALOG_PATH=`) | That file as a **writable catalog** — seeded empty if missing | | `--config ` | That file as a **read-only session** — served as-is, never written or seeded; **errors if absent** | | positional command / `--server-url ` | One ad-hoc server | Note the seed contrast with `--web` above: the CLI and TUI write an **empty** `{ "mcpServers": {} }` (`seedEmptyCatalog` in `core/mcp/node/config.ts`), not the web client's three sample servers — they are non-interactive or list-driven, so sample entries would be noise rather than a starting point. Rules (shared `serverSourceConflict`): `--catalog` and `--config` are mutually exclusive, and neither combines with an ad-hoc command/URL target. The CLI/TUI do not perform catalog CRUD yet — they are read consumers — so the writable/read-only split currently surfaces only as **seed-if-missing** (`--catalog`/default) vs **error-if-missing** (`--config`). Full writable persistence is tracked in #1482 / #1432. ### Production web build (`--web`, no `--dev`) Prod `--web` serves static assets from `clients/web/dist/`, which only exists after a build. In the published package `dist/` always ships. In a fresh dev checkout it is absent, so the runner **builds it on demand** the first time you launch (`vite build` via `npm run build:client`, run in `clients/web`) instead of serving a broken page (#1486). If that build can't run — e.g. dev dependencies are missing — the launcher exits with an actionable error telling you to run `npm run build` (from `clients/web`) or relaunch with `--dev` to use the Vite dev server. `--dev` never needs `dist/`; it runs Vite directly. CI and `npm run validate` (via the top-level `smoke` step) exercise this prod path end-to-end with `npm run smoke:web` (`scripts/smoke-web.mjs`): it starts `mcp-inspector --web` against the built `dist/` and asserts `GET /` returns the SPA (HTTP 200) with the injected `__INSPECTOR_API_TOKEN__`. ### CLI and TUI smokes The top-level `smoke` step also runs end-to-end smokes for the other two modes through the built launcher artifact (beyond the `--help` checks in `smoke:launcher`): - `npm run smoke:cli` (`scripts/smoke-cli.mjs`) — runs `mcp-inspector --cli` against the bundled stdio test server via a temp `--catalog` and asserts `tools/list` returns the server's tools, plus the `--catalog`/`--config` resolution paths (default-catalog seed-on-missing, read-only `--config` error-without-seed, `--catalog`/`--config` conflict). - `npm run smoke:tui` (`scripts/smoke-tui.mjs`) — launches `mcp-inspector --tui --catalog ` **under a pseudoterminal**, asserts the Ink app renders its first frame within a timeout, then waits and asserts it is **still running** before shutting it down (a shallow boot/render/survival check, not full interaction). The second half is the assertion (#2147). Spawned with its stdin on `/dev/null`, Ink cannot enter raw mode for `useInput`, so the TUI painted one frame and exited 1 about 40ms later — and this smoke, which settled OK on the first frame, won that race and reported success on every machine. It is local-only (self-skips under `CI`), which is precisely where a false green goes unnoticed. Both rebuild `test-servers/build` on **every run** — once per process, whether or not it already exists (#2111). Presence is not freshness: a smoke driving a stale fixture reports a product failure rather than a staleness one. ## Development Like the web client, the launcher self-validates from its own folder: ```bash npm run check # format:check && lint && typecheck && build (no tests) npm run validate # check && test ``` This has **no** dependency on the other clients being built — it only checks the launcher's own source. `eslint.config.js` is a Node-only flat config (the web client's React/Storybook plugins stripped out), and the per-file coverage gate covers `parse-launcher-argv.ts` (the pure arg-parsing logic); `src/index.ts` is excluded as binary bootstrap and is instead exercised by the smokes above. The repo-root `validate:launcher` simply delegates here (`cd clients/launcher && npm run validate`), and the root `local:validate` — the first stage of `npm run local:gate` — runs `check` instead, so the gate runs the suite once, under `coverage:launcher` (#2341). ## Publishing The launcher provides the `mcp-inspector` bin for the single `@modelcontextprotocol/inspector` tarball. Packaging is a whole-repo concern — how the one-package/single-version tarball is assembled, the `"files"` allowlist invariants (no source maps, why `clients/web/build` needs `.npmignore`, why `clients/web/static` must ship at that exact path), and the `npm run pack:verify` publish smoke — is documented in [Publishing](../../docs/publishing.md). ## Architecture For how the launcher fits with the shared config processor and app runners, see the [Launcher and config consolidation](../../docs/launcher-config-consolidation-plan.md) document in the repo root `docs/` folder.