Skip to content

Latest commit

 

History

History
240 lines (162 loc) · 19.9 KB

File metadata and controls

240 lines (162 loc) · 19.9 KB

Inspector V2 Tech Stack - CLI, TUI, and Launcher

Brief | V1 Problems | V2 Scope | V2 Tech Stack | V2 UX

Web Client | CLI, TUI, Launcher | Server | Storage

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.

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.
  • 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 / zustand / @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 gatescore/ 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.

Packaging and entrypoints

Artifact Path Build Published bin
Launcher clients/launcher/ tscbuild/index.js Root mcp-inspectorclients/launcher/build/index.js
CLI clients/cli/ tsupbuild/index.js mcp-inspector-cli (client package only)
TUI clients/tui/ tsupbuild/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.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.
  • Helpmcp-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.7.5).


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).

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).


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).

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 <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.


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:

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:

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 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

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.tsloadServerEntries, selectServerEntry (shared CLI/TUI config + settings load path)
  • core/mcp/node/config.tsresolveServerConfigs, withDefaultCatalogPath, serverSourceConflict
  • core/mcp/serverList.tsmcpConfigToServerEntries
  • vitest.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}