Brief | V1 Problems | V2 Scope | V2 Tech Stack | V2 UX
Web Client | CLI, TUI, Launcher | Server | Storage
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.
- One published entry binary (
mcp-inspector) that forwards argv to the selected client. - CLI and TUI use the same
InspectorClient, managed-state classes, and sharedloadServerEntries()resolution path — adapted to v2'sInspectorServerSettingsmodel (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
--helpsmoke step on every PR.
- CLI v2 sessions (connect once, many subcommands) — tracked separately in #1432.
- npm workspaces — v2 uses a fat root package plus per-client
package.jsonfor dev dependencies; the launcher resolves siblingbuild/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 pinsreact/zustand/@modelcontextprotocol/sdkto its ownnode_modules(seevitest.shared.mts) to avoid dual-package-instance hazards, which hoisting works against. And the published@modelcontextprotocol/inspectoris 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 rootpostinstall(scripts/install-clients.mjs) cascadesnpm installinto every client, so a singlenpm installat 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 fromnode_modules, and the published tarball ships only each client'sbuild/, no clientpackage.json), so end users of the published package are unaffected. SetINSPECTOR_SKIP_CLIENT_INSTALL=1to skip the cascade (e.g. CI that installs each client itself).
- Why not workspaces:
- Per-client coverage gates —
core/coverage stays on the web suite; CLI/TUI source gates are follow-up work (see Known gaps). - Catalog CRUD in TUI — TUI loads and connects; persistent catalog editing remains web-first today.
| 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:
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.jsonRoot scripts inspector, web, and web:dev are thin wrappers around the launcher binary.
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 --helpshows launcher help;mcp-inspector --cli --helpforwards 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.7.5).
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 |
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).
Entry: clients/cli/src/index.ts exports runCli(argv); src/cli.ts owns Commander parsing and InspectorClient orchestration.
Server resolution:
- Positional
[target...](command/URL) and/or--catalog/--config+--server, plus-e,--cwd,--transport,--server-url,--header. loadServerEntries(serverOptions)incore/mcp/node/servers.ts— applies the default catalog path when appropriate and lifts disk settings;selectServerEntry(entries, --server)picks the single server to connect.- Disk
headers/timeouts/OAuth (post-#1358 flat entry shape) and ad-hoc--headerpairs both map toInspectorServerSettingsand pass intoInspectorClientviaserverSettings(notMCPServerConfig.headers). A launch-time--headeroverrides 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).
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 webuseServers). - Catalog file: applies
withDefaultCatalogPath()before reading the file (default catalog when no--catalog/--configor ad-hoc target). - Ad-hoc:
resolveServerConfigs(..., "multi")for a single inline server (default catalog already applied above); merges launch-time--headerintosettings.
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).
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.
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:
- Parses the same server-selection flags as CLI/TUI (
--config,--server, positional target,-e,--cwd,--transport,--server-url,--header,--dev). - Resolves one
MCPServerConfigviaresolveServerConfigs()(explicit options only — no default catalog injection) when server input is present. - Passes
initialMcpConfigintobuildWebServerConfig()→GET /api/configlegacy channel. - Starts
startViteDevServer()(--dev) orstartHonoServer()(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).
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 <path> |
MCP servers JSON file |
--server <name> |
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 <path> |
Stdio working directory |
--transport |
stdio, sse, or http |
--server-url <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.
| 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.
v2 does not use npm workspaces. Each client maintains its own package.json and node_modules:
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 installBuild order for a full local smoke:
npm run build:web # includes build:runner for runWeb
npm run build:cli
npm run build:tui
npm run build:launcherRoot npm run validate currently runs web validate only; extending it to CLI/TUI build + test is follow-up work.
| Area | Gap | Tracking |
|---|---|---|
| CLI config-file settings | Disk headers / timeouts / OAuth not lifted when --config is used |
This spec; catalog doc G1 |
| Web launch headers | runWeb --header warns but does not apply settings to UI |
This spec; #1246 |
| TUI list-changed UX | No stale-list indicator when autoRefreshOnListChanged is false |
#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; #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 |
clients/launcher/src/index.ts— mode routing and dynamic importclients/cli/src/cli.ts— CLI parsing,InspectorClientone-shot flowclients/tui/src/tui-servers.ts— thin re-export of the sharedloadServerEntriesclients/web/server/run-web.ts— launcher web entrycore/mcp/node/servers.ts—loadServerEntries,selectServerEntry(shared CLI/TUI config + settings load path)core/mcp/node/config.ts—resolveServerConfigs,withDefaultCatalogPath,serverSourceConflictcore/mcp/serverList.ts—mcpConfigToServerEntriesvitest.shared.mts— shared Vitest aliases- v2_catalog_launch_config.md — catalog and launch config, import, UC1–UC5
- v2_servers_file.md — catalog file format and web CRUD
- #1246 — port tracking issue
- v1.5 sources:
v1.5/main/clients/{cli,tui,launcher}