* refactor(launcher): self-contained validate; split smokes into top-level step (#1504) Previously `validate:launcher` built cli/tui/web and ran the cross-client smokes, so the launcher's "validation" depended on every other client being built — and the launcher had no lint/format/coverage of its own. - clients/launcher: add a web-style `validate` (format:check && lint && build && test:coverage) with a Node-only eslint flat config, prettier, and a per-file coverage gate (parse-launcher-argv.ts covered; src/index.ts excluded as binary bootstrap). 100% on the gated file. - Root `validate:launcher` now just delegates: `cd clients/launcher && npm run validate` — no dependency on the other clients. - New top-level `smoke` step (smoke:launcher + smoke:cli + smoke:tui + smoke:web) with NO build commands; `validate` runs it last. Each client's own validation builds its bundle first, so nothing builds twice (cli/tui via test:coverage, launcher via its validate; smoke:web builds dist on demand). - clients/tui test:coverage now builds the TUI bundle (mirroring cli) so the build-free smoke has it available. - CI: replace the standalone cli/tui/launcher build step with a "Validate launcher" step. Docs updated (AGENTS.md, launcher README). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(cli,tui,root): every client self-validates; smoke separate from validate Extends the launcher self-validate work to the CLI and TUI, and makes the top-level `validate` nothing but the per-client validations — smokes are now a separate top-level target. - clients/cli, clients/tui: add a web-style `validate` with a Node flat eslint config + prettier. CLI lint/format pass as-is. TUI registers eslint-plugin-react-hooks for the classic rules only (rules-of-hooks + exhaustive-deps, which the source already references); the stricter react-hooks@7 rules are not enforced on the interim component surface (#1501). - Avoid double builds: - CLI's e2e spawns the built binary, so its `test:coverage` builds first; `validate` folds that in (`format:check && lint && test:coverage`, no separate `build`). - TUI tests don't spawn a bundle, so `validate` keeps an explicit `build` (for the smoke) and `test:coverage` is pure. - Root: `validate` = validate:web && validate:cli && validate:tui && validate:launcher (each delegates to the client's own validate). `smoke` is no longer part of `validate` — it's a separate top-level target run after a build/validate, with no build commands of its own. - CI: per-client "Validate X" steps + a single "Run cross-client smokes" (npm run smoke). Docs updated (AGENTS.md, cli/tui READMEs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(root): drop redundant test aggregator scripts Now that every client self-validates and the top-level `validate` chains the per-client validations, the root `test` / `test:web` / `test:cli` / `test:tui` / `test:coverage` aggregators are redundant — run `npm run validate` (all) or `cd clients/<name> && npm run validate` (one). `test:storybook` stays (it's the one check not part of validate). CI was already using each client's own scripts, so it's unaffected. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(web): silence happy-dom infra noise in unit runs Passing unit runs printed dozens of alarming-but-expected lines that made green runs look broken — all from happy-dom performing real I/O with no server behind it: - `<iframe src="/sandbox.html">` (MCP Apps sandbox) made happy-dom fetch and later unload that URL, emitting ~40 `DOMException [NetworkError/ AbortError]` + `AsyncTaskManager destroyed` lines. Disable child-frame navigation in the unit project (the tests only assert on the iframe element/attributes, not its document), removing the noise with no coverage loss. - App-level renders read `GET /api/config` via useSandboxUrl / useServerListWritable, 404-ing ~76x. Add a benign default `fetch` (empty 200) in the unit setup; tests that exercise fetch still override it. Negative tests that assert on errors already suppress their own console output (e.g. jsonUtils/schemaUtils); CLI, TUI, and the web integration suite were already clean. Unit suite is now noise-free; 2424 web tests pass and the coverage gate is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(scripts): make validate fast (test), split coverage into its own target Each client's `validate` now runs `test` instead of `test:coverage`, so the everyday pre-push check skips v8 instrumentation (and, for web, the slower integration project). The per-file coverage gate moves to a dedicated top-level `coverage` script: coverage = coverage:web && coverage:cli && coverage:tui && coverage:launcher coverage:<name> = cd clients/<name> && npm run test:coverage `validate` still builds each client's bundle (cli via its test `pretest`, the others via an explicit `build`), so `npm run validate && npm run smoke` works unchanged. CI now runs `validate` (fast checks + builds), then `coverage` (the gate), then `smoke` — collapsing the old per-client steps. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: drop the coverage gate from CI; keep web integration as its own step CI no longer runs `npm run coverage` — the per-file coverage gate is now a local/manual check (`npm run coverage` before pushing). To avoid silently dropping the web integration suite (it only ran via test:coverage, and `validate` runs the unit project only), CI gains a standalone `clients/web` `test:integration` step that exercises the real stdio/HTTP/OAuth/storage paths without coverage instrumentation. CI is now: validate (fast) → web integration → smoke → storybook. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(smoke): give smoke:launcher its own script like the others smoke:launcher was the odd one out — an inline chain of three `node clients/launcher/build/index.js ... --help` commands while smoke:cli / smoke:tui / smoke:web each run a `scripts/smoke-*.mjs`. Extract it to `scripts/smoke-launcher.mjs`, which runs the same `--help` / `--cli --help` / `--tui --help` dispatch checks but now also asserts each prints that mode's usage banner (proving the launcher resolved the right client build) and verifies the required builds exist up front with a clear error. Prints a `smoke:launcher OK — ...` summary in the same style as the rest. Docs updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: reorder package.json scripts for readability Move frequently-run scripts toward the top and rarely-run-by-hand ones (pretest hooks, lint/format internals, etc.) toward the end, consistently across the root and client manifests. Pure reordering — no script names or values changed, and no other manifest content is touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: drop references to removed root inspector / test:storybook scripts The reordering dropped the root `inspector` and `test:storybook` aggregators (intentional — `inspector` is redundant with `web`, and storybook is run from clients/web in CI). Update AGENTS.md so it no longer points at `npm run inspector`, and run `test:storybook` from `clients/web/`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: address PR #1505 review (format glob gap, fetch-baseline comment) - TUI format/format:check now also cover the client-root source files (index.ts, tui.tsx, dev.ts) that live outside src/ — `eslint .` already lints them, so they were linted but not format-checked. (cli/launcher have no source outside src/; their root .ts are single-quoted configs intentionally left out of prettier, matching web.) - Reword the setup.ts default-fetch comment: it overstated auto-restore (the unit project sets no restoreMocks/unstubGlobals), and now also flags that the permissive 200 default means a test asserting on a fetch FAILURE must install its own rejecting stub. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove redundant root install:clients script `install:clients` ran the same `node scripts/install-clients.mjs` as `postinstall`, which fires automatically on `npm install` — so the manual alias was redundant. Drop it and the doc references (AGENTS.md, spec). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(ci): skip smoke:tui in CI (Ink needs a real TTY) This PR made CI run the full `npm run smoke`, which surfaced that smoke:tui fails in headless CI: the Ink TUI throws "Raw mode is not supported" on mount (no TTY) and exits before rendering its first frame. The old CI never ran smoke:tui, so this was latent. smoke-tui.mjs now self-skips when `process.env.CI` is set — the boot/ render check is inherently local-only (a TUI needs a terminal). The TUI is still built and unit-tested in CI; `npm run smoke` stays the single command everywhere (full locally, tui-skipped in CI). Verified `CI=true npm run smoke` is green (launcher/cli OK, tui SKIPPED, web OK). Docs + CI comment updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
7.3 KiB
Markdown
103 lines
7.3 KiB
Markdown
# 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 config or env vars.
|
||
|
||
## 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` | Yes |
|
||
| `mcp-inspector --web --catalog <path>` (or `MCP_CATALOG_PATH=<path>`) | That file as the active catalog (created/seeded if missing) | Yes |
|
||
| `mcp-inspector --web --config <path>` | 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 <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 or `--header`; `--header` requires an ad-hoc HTTP/SSE server
|
||
and is applied to that connection (it is no longer a warn-only no-op).
|
||
|
||
## 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 <path>` (or `MCP_CATALOG_PATH=<path>`) | That file as a **writable catalog** — seeded empty if missing |
|
||
| `--config <path>` | That file as a **read-only session** — served as-is, never written or seeded; **errors if absent** |
|
||
| positional command / `--server-url <url>` | One ad-hoc server |
|
||
|
||
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 <temp>` and asserts the Ink app renders its
|
||
first frame within a timeout, then shuts it down (a shallow boot/render
|
||
check, not full interaction).
|
||
|
||
Both build `test-servers/build` on demand if it is missing.
|
||
|
||
## Development
|
||
|
||
Like the web client, the launcher self-validates from its own folder:
|
||
|
||
```bash
|
||
npm run validate # format:check && lint && build && test:coverage
|
||
```
|
||
|
||
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`).
|
||
|
||
## Publishing
|
||
|
||
The root `@modelcontextprotocol/inspector` package ships as one fat tarball: `npm run build` at the repo root builds all clients, then `prepack` runs before `npm publish`. Runtime dependencies are declared on the root `package.json`; client builds bundle `@inspector/core` and externalize npm packages resolved from the root install.
|
||
|
||
## 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.
|