# Inspector V2 Tech Stack - CLI, TUI, and Launcher ### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | V2 Tech Stack | [V2 UX](v2_ux.md) | [V2 Auth](v2_auth.md) | [V2 New Spec Impact](v2_new_spec_impact.md) #### [Web Client](v2_web_client.md) | CLI, TUI, Launcher | [Server](v2_server.md) | [Storage](v2_storage.md) ## Summary v2 ships three non-web Inspector incarnations alongside the web client: a **one-shot CLI**, an **interactive TUI**, and a **launcher** that routes to web, CLI, or TUI from a single `mcp-inspector` binary. All three consume the same `core/` source as the web client via the `@inspector/core` path alias and run on the shared `InspectorClient` stack ported from v1.5/main. This document describes how those clients are built, wired, and tested today, and records known gaps. For catalog vs launch-time config semantics (`--config`, `--catalog`, import), see [Catalog and Launch Configuration](v2_catalog_launch_config.md). ## Goals - One published entry binary (`mcp-inspector`) that forwards argv to the selected client. - CLI and TUI use the same `InspectorClient`, managed-state classes, and shared `loadServerEntries()` resolution path — adapted to v2's `InspectorServerSettings` model (post-#1358). - Bundle `core/` into CLI/TUI Node artifacts with **tsup** (analogous to Vite bundling core for the browser). - Port v1.5 CLI integration tests (86 cases) and TUI smoke tests without duplicating `core/` unit tests (web suite remains canonical for core). - CI runs CLI and TUI tests plus a launcher `--help` smoke step on every PR. ## Non-goals - **CLI v2 sessions** (connect once, many subcommands) — tracked separately in [#1432](https://github.com/modelcontextprotocol/inspector/issues/1432). - **npm workspaces** — v2 uses a fat root package plus per-client `package.json` for dev dependencies; the launcher resolves sibling `build/` outputs via relative paths, not workspace hoisting. - _Why not workspaces:_ `core/` is consumed by **bundling** — a Vite alias for the browser, tsup inlining for the Node clients — not by symlinked package resolution, so workspaces' main benefit (cross-package linking) does not apply. Each client also pins `react` / `@modelcontextprotocol/sdk` to its own `node_modules` (see `vitest.shared.mts`) to avoid dual-package-instance hazards, which hoisting works against. And the published `@modelcontextprotocol/inspector` is a single flat fat package that workspaces would complicate rather than simplify. - _Cost (from-source dev only):_ there is no hoisting, so each client keeps its own `node_modules`. A root `postinstall` (`scripts/install-clients.mjs`) cascades `npm install` into every client, so a single `npm install` at the repo root populates them all — re-run it after a pull that changes a client's dependencies. The cascade no-ops outside a source checkout (it exits early when running from `node_modules`, and the published tarball ships only each client's `build/`, no client `package.json`), so end users of the published package are unaffected. Set `INSPECTOR_SKIP_CLIENT_INSTALL=1` to skip the cascade (e.g. CI that installs each client itself). - **Per-client coverage gates** — `core/` coverage stays on the web suite; CLI/TUI source gates are follow-up work (see [Known gaps](#known-gaps)). - **Catalog CRUD in TUI** — TUI loads and connects; persistent catalog editing remains web-first today. --- ## Packaging and entrypoints | Artifact | Path | Build | Published bin | | ---------- | ------------------------------- | ------------------------------------------------------ | -------------------------------------------------------- | | Launcher | `clients/launcher/` | `tsc` → `build/index.js` | Root `mcp-inspector` → `clients/launcher/build/index.js` | | CLI | `clients/cli/` | `tsup` → `build/index.js` | `mcp-inspector-cli` (client package only) | | TUI | `clients/tui/` | `tsup` → `build/index.js` | `mcp-inspector-tui` (client package only) | | Web runner | `clients/web/server/run-web.ts` | `tsup` (`build:runner`) → `clients/web/build/index.js` | `mcp-inspector-web` (client package only) | Root `package.json` (`@modelcontextprotocol/inspector` v2) publishes a **fat package**: merged runtime `dependencies`, `files` manifest listing each client's `build/` (and web `dist/`), and `prepack` → full `npm run build`. On the dev side, a `postinstall` runs `scripts/install-clients.mjs`, which cascades `npm install` into each client so one `npm install` at the repo root sets up every client; it no-ops when the package is installed as a dependency. The launcher does not declare `file:` sibling dependencies; it dynamically imports `../web/build/index.js`, `../cli/build/index.js`, or `../tui/build/index.js` relative to its own `build/` directory. **Published runtime deps — Vite:** `vite` and `@vitejs/plugin-react` are production `dependencies` (not devDependencies) because `mcp-inspector --web --dev` starts an in-process Vite dev server at runtime (`start-vite-dev-server.ts`). A `npx @modelcontextprotocol/inspector` install therefore pulls the Vite toolchain even for CLI/TUI-only users; that install footprint is intentional so `--web --dev` works without a separate dev setup. **Typical invocations:** ```bash mcp-inspector # web (prod Hono server) mcp-inspector --web --dev # web (Vite dev server) mcp-inspector --cli --config mcp.json --server my-api --method tools/list mcp-inspector --tui --config mcp.json ``` Root scripts `inspector`, `web`, and `web:dev` are thin wrappers around the launcher binary. --- ## Launcher `clients/launcher/src/index.ts` reads mode only from a **launcher prefix**: a contiguous run of `--web`, `--cli`, or `--tui` immediately after the script name (default **web** when omitted). Prefix mode flags are stripped; everything from the first non-mode token onward is forwarded unchanged. Later tokens equal to `--web`/`--cli`/`--tui` are treated as app args (e.g. stdio server config), not launcher mode. More than one mode flag in the prefix is an error. - **No core imports** — plain TypeScript compile is sufficient. - **Argv forwarding** — app flags and positionals pass through unchanged so each client's Commander parser owns server and method options. - **Help** — `mcp-inspector --help` shows launcher help; `mcp-inspector --cli --help` forwards to CLI help. **Production web caveat:** launcher `prebuild` chains `build:runner` for web, not a full `vite build`. Running `mcp-inspector --web` (without `--dev`) requires `clients/web/dist/` from a prior `cd clients/web && npm run build`. CI builds CLI, TUI, and launcher before the launcher smoke step but does not currently validate prod web startup end-to-end. **Build targets:** client `tsup` bundles target `node22`, aligned with root `engines.node` (`>=22.19.0`). --- ## Shared core consumption All three clients import from `@inspector/core/...` (mapped to `../../core/` source). | Concern | Web | CLI / TUI | Launcher | | -------------- | --------------------------------- | -------------------------------------------------------- | ------------------------- | | Dev typecheck | `tsconfig.app.json` paths | per-client `tsconfig.json` paths | `tsconfig.json` (no core) | | Runtime bundle | Vite alias | tsup `noExternal: [/^@inspector\/core/]` + esbuild alias | n/a | | Tests | Vitest projects in `clients/web/` | Vitest + `vitest.shared.mts` aliases | none | `vitest.shared.mts` at repo root centralizes `@inspector/core` and test-server aliases plus bare-module pins (`react`, `pino`, SDK, etc.) so CLI/TUI Vitest configs stay aligned with web. **Resolved design choices:** | Topic | Decision | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | Core package | No separate `inspector-core` npm package; source-only `core/` | | CLI/TUI build | tsup bundles `@inspector/core` into `build/index.js` | | Core tests | Not duplicated under cli/tui; web unit + integration suites cover `core/` | | Default config path | `loadServerEntries()` applies `withDefaultCatalogPath()` → `~/.mcp-inspector/mcp.json` when no `--catalog`/`--config` and no ad-hoc target | --- ## CLI **Model:** one-shot — each invocation connects, runs a single `--method`, prints JSON to stdout, disconnects, exits. Same surface as v1.5; session-oriented CLI v2 is future work ([#1432](https://github.com/modelcontextprotocol/inspector/issues/1432)). **Entry:** `clients/cli/src/index.ts` exports `runCli(argv)`; `src/cli.ts` owns Commander parsing and `InspectorClient` orchestration. **Server resolution:** 1. Positional `[target...]` (command/URL) and/or `--catalog`/`--config` + `--server`, plus `-e`, `--cwd`, `--transport`, `--server-url`, `--header`. 2. `loadServerEntries(serverOptions)` in `core/mcp/node/servers.ts` — applies the default catalog path when appropriate and lifts disk settings; `selectServerEntry(entries, --server)` picks the single server to connect. 3. Disk `headers`/timeouts/OAuth (post-#1358 flat entry shape) and ad-hoc `--header` pairs both map to `InspectorServerSettings` and pass into `InspectorClient` via `serverSettings` (not `MCPServerConfig.headers`). A launch-time `--header` overrides the file's headers. **Config-file settings (resolved #1482):** the CLI now routes file-sourced servers through the same `loadServerEntries()` → `mcpConfigToServerEntries()` path as the TUI, so persisted `headers`, timeouts, and OAuth are lifted into `serverSettings`. (Previously the CLI used bare `MCPServerConfig` and dropped them.) **Tests:** 86 cases in `clients/cli/__tests__/` spawn `node build/index.js` via `cli-runner.ts` (`pretest` builds test-servers + CLI). Core behavior is exercised; Vitest coverage on `src/cli.ts` is invisible to the coverage instrumenter because tests run out-of-process (see [Known gaps](#known-gaps)). --- ## TUI **Model:** interactive terminal UI (Ink + React 19) mirroring web information architecture: Servers, Tools, Prompts, Resources, Logs, OAuth, etc. **Entry:** `clients/tui/index.ts` exports `runTui(argv)`; `tui.tsx` parses argv and renders `App`. **Server resolution:** `loadTuiServers()` in `clients/tui/src/tui-servers.ts` — a thin re-export of the shared `loadServerEntries()` in `core/mcp/node/servers.ts`: - **Config file path:** reads JSON, runs `mcpConfigToServerEntries()` — returns `{ config, settings }` per server (correct post-#1358 path; matches web `useServers`). - **Catalog file:** applies `withDefaultCatalogPath()` before reading the file (default catalog when no `--catalog`/`--config` or ad-hoc target). - **Ad-hoc:** `resolveServerConfigs(..., "multi")` for a single inline server (default catalog already applied above); merges launch-time `--header` into `settings`. **Core hooks:** TUI uses the same managed-state and `useInspectorClient` patterns as web (`ManagedToolsState`, etc.) inside Ink components. **Behavior note:** v2 core only auto-refreshes tool/resource lists when `autoRefreshOnListChanged` is true in server settings; v1.5 TUI always auto-refreshed. Port accepts v2 core behavior; UI indicator parity with web is open ([#1402](https://github.com/modelcontextprotocol/inspector/issues/1402)). **Tests:** 2 cases in `clients/tui/__tests__/tui.test.ts` assert `tabsConfig` shape only. Config parsing, OAuth, and Ink component flows are untested. **Dev:** `npm run dev` in `clients/tui` uses `vite-node` + `dev.ts` for fast iteration without a full tsup rebuild. --- ## Web runner (`runWeb`) The launcher `--web` path does not start Vite via the `npm run dev` script. It calls `runWeb(argv)` from `clients/web/server/run-web.ts`, which: 1. Parses the same server-selection flags as CLI/TUI (`--config`, `--server`, positional target, `-e`, `--cwd`, `--transport`, `--server-url`, `--header`, `--dev`). 2. Resolves one `MCPServerConfig` via `resolveServerConfigs()` (explicit options only — no default catalog injection) when server input is present. 3. Passes `initialMcpConfig` into `buildWebServerConfig()` → `GET /api/config` legacy channel. 4. Starts `startViteDevServer()` (`--dev`) or `startHonoServer()` (prod). **Gap — launch-time headers:** `--header` is accepted but logs a warning; headers are not applied to the web session. The v2 web UI configures HTTP headers via per-server settings in the catalog (`ServerSettingsForm`), not via `initialMcpConfig`. Extending `InitialConfigPayload` (or equivalent) is required for launcher-passed headers to reach the UI. Day-to-day web development still uses `cd clients/web && npm run dev` (Vite CLI + env-based `buildWebServerConfigFromEnv`). --- ## Server configuration flags (shared) CLI, TUI, and `runWeb` share launch-time server options documented in v1.5's `docs/mcp-server-configuration.md` (port to v2 pending). Common flags: | Flag | Purpose | | ------------------------ | -------------------------------------------------------------------------------- | | `--config ` | MCP servers JSON file | | `--server ` | Named entry within config (required when file has multiple servers) | | `[target...]` | Ad-hoc stdio command/args or HTTP URL | | `-e KEY=VALUE` | Stdio environment overrides | | `--cwd ` | Stdio working directory | | `--transport` | `stdio`, `sse`, or `http` | | `--server-url ` | HTTP/SSE endpoint | | `--header "Name: Value"` | Launch-time HTTP headers (CLI/TUI ad-hoc → `serverSettings`; web → warning only) | CLI additionally requires `--method` and method-specific args (`--tool-name`, `--uri`, etc.). Semantics for catalog vs session config, `--catalog`, and `servers/import` are in [v2_catalog_launch_config.md](v2_catalog_launch_config.md). --- ## Testing and CI | Suite | Location | Count | How | | -------- | ------------------------ | ----- | ---------------------------------------- | | CLI | `clients/cli/__tests__/` | 86 | Subprocess `node build/index.js` | | TUI | `clients/tui/__tests__/` | 2 | In-process imports | | Launcher | CI smoke only | 3 | `--help`, `--cli --help`, `--tui --help` | | Core | `clients/web/src/test/` | — | Canonical; not duplicated in cli/tui | **Root orchestration:** `npm run test` chains web unit tests, then `clients/cli` and `clients/tui` tests. `npm run test:coverage` runs web coverage only. **CI** (`.github/workflows/main.yml`): after web validate + coverage, installs and tests CLI and TUI, builds TUI + launcher, runs launcher help smoke. **Prerequisites:** CLI `pretest` runs `test-servers:build` (`tsc -p test-servers`) so stdio integration tests can spawn `test-servers/build/test-server-stdio.js`. --- ## Developer setup v2 does not use npm workspaces. Each client maintains its own `package.json` and `node_modules`: ```bash npm install # root (launcher runtime deps) cd clients/web && npm install # required for web dev/test cd clients/cli && npm install # required for CLI build/test cd clients/tui && npm install # required for TUI build/test cd clients/launcher && npm install ``` **Build order for a full local smoke:** ```bash npm run build:web # includes build:runner for runWeb npm run build:cli npm run build:tui npm run build:launcher ``` Root `npm run validate` currently runs web validate only; extending it to CLI/TUI build + test is follow-up work. --- ## Known gaps | Area | Gap | Tracking | | ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **CLI config-file settings** | Disk `headers` / timeouts / OAuth not lifted when `--config` is used | This spec; [catalog doc](v2_catalog_launch_config.md) G1 | | **Web launch headers** | `runWeb --header` warns but does not apply settings to UI | This spec; [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) | | **TUI list-changed UX** | No stale-list indicator when `autoRefreshOnListChanged` is false | [#1402](https://github.com/modelcontextprotocol/inspector/issues/1402) | | **TUI test coverage** | Only `tabsConfig` shape tested | Expand `tui-servers.ts`, Ink flows | | **CLI coverage gates** | Subprocess tests don't instrument `src/cli.ts` | In-process `runCli()` runner + thin binary E2E suite | | **Prod web via launcher** | Requires pre-built `clients/web/dist/` | Document or wire into launcher prebuild | | **Import / `--catalog`** | `servers/import`, `--catalog` not implemented | [catalog doc](v2_catalog_launch_config.md); [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348) | | **README refresh** | Client READMEs may lag v2 install/build commands | `clients/*/README.md`, `AGENTS.md` | | **Root validate** | Does not build/test CLI/TUI | Root `package.json` | --- ## References - `clients/launcher/src/index.ts` — mode routing and dynamic import - `clients/cli/src/cli.ts` — CLI parsing, `InspectorClient` one-shot flow - `clients/tui/src/tui-servers.ts` — thin re-export of the shared `loadServerEntries` - `clients/web/server/run-web.ts` — launcher web entry - `core/mcp/node/servers.ts` — `loadServerEntries`, `selectServerEntry` (shared CLI/TUI config + settings load path) - `core/mcp/node/config.ts` — `resolveServerConfigs`, `withDefaultCatalogPath`, `serverSourceConflict` - `core/mcp/serverList.ts` — `mcpConfigToServerEntries` - `vitest.shared.mts` — shared Vitest aliases - [v2_catalog_launch_config.md](v2_catalog_launch_config.md) — catalog and launch config, import, UC1–UC5 - [v2_servers_file.md](v2_servers_file.md) — catalog file format and web CRUD - [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) — port tracking issue - v1.5 sources: `v1.5/main/clients/{cli,tui,launcher}`