Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single `mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the `mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server double-start), the root `postinstall` install cascade with simplified CI, and spec/AGENTS docs (no-workspaces rationale, Development setup). Closes #1246. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
29 KiB
Inspector V2 Tech Stack - Storage - Catalog and Launch Configuration
Brief | V1 Problems | V2 Scope | V2 Tech Stack | V2 UX
Web Client | CLI, TUI, Launcher | Server | Storage
Overview | Server List File | Catalog and Launch
Table of Contents
- Summary
- Goals
- Non-goals
- Related specifications
- Terminology
- User intents (UC1–UC5)
- Flag reference
- Background (v1.5)
- Normalization
- Cross-client list contract
- Launch behavior by client
- Import (UC2)
- Write-back policy
- Web launch behavior
- Target API (core)
- Open decisions
- Known gaps
- Decision matrix
- Engineering follow-ups
- Related GitHub issues
- References
Summary
v2 treats ~/.mcp-inspector/mcp.json as the default catalog — a persistent, Inspector-owned grouping of MCP servers. The web client loads and mutates it via /api/servers. CLI and TUI resolve servers from that path when launch args omit both --config and ad-hoc targets (resolveLaunchServerConfigs() / withDefaultConfigPath() in core/mcp/node/config.ts).
This specification defines launch-time configuration across web, CLI, and TUI: how --catalog, --config, --server, and ad-hoc flags select a server list; which paths are writable; and how import differs from session launch. On-disk catalog format and web CRUD are specified in Server List File. CLI/TUI/launcher build and per-client implementation gaps are in CLI, TUI, and Launcher.
Goals
- One catalog model shared by web, CLI, and TUI, with a clear split between catalog modes (writable, UC1/UC4) and session mode (read-only, UC3).
--catalogselects the active writable catalog;--configwithout catalog intent supplies a read-only session file — not overloaded.- All reads parse → normalize → lift into canonical
ServerEntry[]; catalog writes serialize through the same contract as Server List File. - List clients (web, TUI) consume the same resolved list plus
writable,defaultServerId, and optionalautoConnect— resolution in core, not the launcher. - Import is an explicit catalog mutation (
servers/importon CLI; web Import menu) — never a side effect of launch. - CLI resolves a single server through the same normalizer as list clients, including disk settings lift.
- Web follows catalog-first launch: full catalog visible;
--serverselects an entry; ad-hoc launches do not auto-persist.
Non-goals
- Syncing with Claude Desktop, Cursor, or other tools' config paths (symlinking is the user's choice — see Server List File).
- Silent write-back of launch overrides (
-e,--cwd,--header) to catalog or session files. - Launcher as config arbitrator — it routes mode and forwards argv only.
- Requiring import support on all three clients at startup (UC2 is CLI + web UI).
- Auto-importing ad-hoc launches into the catalog.
- File watching or two-way sync with external MCP client configs.
Related specifications
| Document | Owns |
|---|---|
| v2_servers_file.md | Catalog path, on-disk shape, web CRUD, /api/servers, per-server settings |
| v2_cli_tui_launcher.md | Bins, tsup, tests, runWeb, CLI config-file settings gap (G1) |
| v2_ux.md | Import config vs Import server.json menu UX |
v1.5 docs/mcp-server-configuration.md |
Baseline shared launch flags (port to v2 pending, G6) |
Terminology
| Term | Meaning |
|---|---|
| Catalog | Inspector-owned mcp.json used as a persistent server grouping. Default: ~/.mcp-inspector/mcp.json. Active catalog selected by --catalog <path> or default (UC1/UC4). CRUD when writable: true. |
--catalog |
Selects the active catalog for this run (writable: true). Omitted → default path (UC1). |
| Alternate config file | Any JSON passed via --config <path> without catalog intent. Read-only, run-scoped server set (UC3). Inspector does not write to this path. |
| Canonical catalog format | Normalized MCPConfig / ServerEntry[] after normalizeServerType, normalizeMcpServers, and mcpConfigToServerEntries — the only shape written to a catalog. |
| Catalog lookup | --server <name> with no --config and no ad-hoc target. resolveLaunchServerConfigs() injects the catalog path via withDefaultConfigPath(), then the named entry is resolved. |
| Ad-hoc / inline config | Positional command/URL and/or --transport, --server-url, -e, --cwd, --header without --config. Ephemeral unless the user persists via CRUD or import. |
| Launch-time initial config (web) | initialMcpConfig from runWeb(argv) → GET /api/config. Legacy v1.5 channel; largely unused by the v2 web UI today (G2). |
User intents (UC1–UC5)
Five distinct user goals. They share flags (--config, --server, --catalog) but differ in whether the list is the catalog, read-only, or mutated, and which client owns each action.
UC1 — Default catalog: run and manage servers in place
Intent: Use the usual server list at ~/.mcp-inspector/mcp.json — the default active catalog (UC4 with default path). Add, edit, remove, and configure settings through the UX; CLI invokes methods against a named catalog entry.
| Today | Target | |
|---|---|---|
| Web | Works — useServers + /api/servers CRUD |
Same |
| TUI | Loads default catalog via withDefaultConfigPath in loadTuiServers; no CRUD UX yet |
Catalog CRUD in TUI (planned) |
| CLI | --server foo --method … with no --config resolves against default catalog |
Same; lift settings from catalog entries (G1) |
mcp-inspector --web
mcp-inspector --cli --server my-api --method tools/list
mcp-inspector --tui
The catalog is both the data source and the write target (writable: true).
UC2 — Import into a catalog (automation / one-off CLI)
Intent: Add servers into an Inspector catalog (default or --catalog target). Distinct from “launch and use for this session” (UC3). Import is a catalog write, not a launch mode.
| Source | CLI shape | Stored |
|---|---|---|
A — mcpServers file |
--config /path/to/source.json [ --server <name> ] --method servers/import |
One or all entries, canonicalized |
| B — Ad-hoc parts | Same argv as ad-hoc launch ([target...], --transport, --server-url, -e, --cwd, --header, …); no --config. Method → servers/import. |
One entry; --server <id> = catalog map key (required for B) |
# A: import all from a foreign mcp.json
mcp-inspector --cli --config /path/to/claude.json --method servers/import
# A: one named entry from source file (--server = name in source)
mcp-inspector --cli --config /path/to/claude.json --server acme --method servers/import
# B: stdio — launch vs import differ only by --method and --server (catalog id)
mcp-inspector --cli \
-e API_KEY=dev --cwd /path/to/server \
node ./build/index.js --method tools/list
mcp-inspector --cli \
--server my-local \
-e API_KEY=dev --cwd /path/to/server \
node ./build/index.js --method servers/import
# B: streamable HTTP
mcp-inspector --cli \
--transport streamable-http \
--server-url https://api.example.com/mcp \
--header "Authorization: Bearer xxx" \
--method tools/list
mcp-inspector --cli \
--server staging-api \
--transport streamable-http \
--server-url https://api.example.com/mcp \
--header "Authorization: Bearer xxx" \
--method servers/import
# Into a non-default catalog (UC4)
mcp-inspector --cli --catalog ./project/.mcp-inspector/mcp.json \
--config ./vendor.json --method servers/import
Not UC2: mcp-inspector --cli node ./server.js --method tools/list without servers/import is UC3/UC1 (use now), not import.
| Today | Target | |
|---|---|---|
| CLI | Not implemented | servers/import for A and B via importServersIntoCatalog in core |
| Web | Import menu items stubbed | Interactive import per v2_ux.md; #1348, #1435 |
| TUI | N/A | No requirement |
See Import (UC2) for behavior rules.
UC3 — Launch with a specific config (session list, not the catalog)
Intent: Run against another server set for this session only — ad-hoc (one server from command/URL) or file (all servers in TUI; one selected by --server for CLI). User sees those servers in list UI; Add/Edit/Remove must not mutate the file (writable: false).
| Today | Target | |
|---|---|---|
| TUI | Works for --config <file> and ad-hoc via loadTuiServers |
Same + explicit writable: false for non-catalog paths |
| Web | UI always shows default catalog; runWeb --config only sets legacy initialMcpConfig |
Session mcpConfigPath → /api/servers serves that file; writable: false; disable CRUD |
| CLI | Single resolved server (file + --server, ad-hoc, or default catalog) |
Same; shared normalizer + settings lift |
mcp-inspector --tui --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
mcp-inspector --cli --config ./project/mcp.json --server local --method tools/list
mcp-inspector --cli node ./server.js --method tools/list
Launching with --config does not import unless the user separately runs UC2.
UC4 — Choose which catalog (grouping), not only the default
Intent: Catalogs group MCP configs for reuse. Most users only use the default (UC1). Power users switch catalogs — per-project files, work vs personal, repo-local .mcp-inspector/mcp.json.
--catalog <path> selects the active catalog for this run. Omitting it → default (UC1).
mcp-inspector --web
mcp-inspector --web --catalog ./.mcp-inspector/mcp.json
mcp-inspector --tui --catalog ~/work/mcp.json
mcp-inspector --cli --catalog ~/work/mcp.json --server api --method tools/list
Distinction from UC3: UC3 uses --config (or ad-hoc) for a foreign/session file — show servers, do not write. UC4 uses --catalog — this file is the Inspector catalog for this run.
| Today | Target | |
|---|---|---|
| Web | Only default catalog — backend always getDefaultMcpConfigPath() |
runWeb --catalog <path> → mcpConfigPath; CRUD on active catalog |
| TUI | --config loads a file; no catalog vs session distinction |
--catalog = writable active catalog; --config without --catalog → UC3 |
| CLI | --config for one-shot reads; default path when only --server |
--catalog scopes UC1/UC2/UC5; --config remains session-only |
Env alternative: MCP_CATALOG_PATH — same semantics as --catalog for scripts/CI.
UC5 — Default selection within any list
Intent: Among loaded servers (catalog or session), focus one entry — required for CLI (one server), useful for web/TUI.
| Today | Target | |
|---|---|---|
| CLI | --server with catalog (UC1) or --config file (UC3) |
Same |
| TUI | All servers shown; no --server flag |
Optional highlight / initial selection |
| Web | Ignores --server for list/selection |
defaultServerId from launch + optional auto-connect (#1183) |
Use-case map (quick reference)
| UC | User goal | How selected | Writable? | Primary clients | Import? |
|---|---|---|---|---|---|
| 1 | Manage default catalog | (no --catalog) |
Yes | Web (now), TUI (CRUD planned), CLI | No |
| 2 | Add servers to a catalog | --method servers/import |
Writes active catalog only | CLI; web UI | Yes |
| 3 | Session with external config | --config or ad-hoc |
No | TUI (now), Web (gap), CLI | No |
| 4 | Switch / use another catalog | --catalog <path> |
Yes (that path) | Web/TUI (gaps) | Via UC2 |
| 5 | Pick one server by name | --server |
— | CLI (required), Web/TUI (hint) | — |
Design principle: UC1 is UC4 with the default path. Catalog modes (UC1/UC4): writable: true. Session mode (UC3): writable: false. UC2 mutates the active catalog only — not a launch side-effect on all clients.
Flag reference
Shared launch flags (full detail in v1.5 docs/mcp-server-configuration.md, port pending):
| Flag | UC1/UC4 (catalog) | UC3 (session) | UC2 (import) |
|---|---|---|---|
--catalog <path> |
Active writable catalog | — | Target catalog for write |
--config <path> |
— | Read-only session file | Source file (A only) |
--server <name> |
Catalog entry id (UC5) | Entry in session file | Source name (A) or new catalog id (B) |
[target...], -e, --cwd, --transport, --server-url, --header |
Ad-hoc overrides on connect | Ad-hoc session server | Same argv as launch (B) |
--server overload (by context — no new flags):
| Context | --server means |
|---|---|
servers/import + --config |
Entry to copy from source file |
servers/import + ad-hoc (no --config) |
Catalog id for new entry |
tools/list (etc.) + no --config |
Lookup from active catalog |
tools/list + --config |
Lookup from session file |
Optional import flags (target): --on-conflict skip|error|replace.
Background (v1.5)
v1.5 (docs/mcp-server-configuration.md) had no default catalog: omitting --config and positional args failed resolution. Config-file headers lived on MCPServerConfig. Web had no file-backed list — launcher argv became initialMcpConfig for form pre-fill only. v2 adds the persistent catalog (Server List File), flat on-disk settings (post-#1358), and resolveLaunchServerConfigs() for CLI/TUI launch resolution.
Normalization
External config files (Claude Desktop, Cursor, hand-edited JSON) are not reliably conformant. Read path: always parse → normalize → lift; never connect from raw file bytes.
| Stage | Where | What |
|---|---|---|
| Parse | loadMcpServersConfig / backend JSON parse |
Structural check; tolerate unknown keys |
| Normalize transport | normalizeServerType (core/mcp/serverList.ts) |
Missing type → stdio; http → streamable-http |
| Normalize catalog entries | normalizeMcpServers (core/mcp/remote/node/server.ts) |
Legacy settings hard-cutover; secret migration |
| Lift to runtime | mcpConfigToServerEntries |
Map key → id; flat disk fields → InspectorServerSettings |
Write path (catalog only): serverEntriesToMcpConfig / buildStoredEntry / writeStoreFile per Server List File.
UC3 session files: normalize on read; no silent writes. Persistence into a catalog requires UC2, web CRUD, or manual edit outside Inspector.
Ad-hoc inline config: ephemeral; never written by the runner.
Cross-client list contract
Web and TUI are list clients: display servers, pick/connect, edit only when writable. They need not distinguish catalog path from session path in the UI — only the resolved list and flags.
Required inputs:
- Canonical list —
ServerEntry[]after normalize +mcpConfigToServerEntries. writable— if false, hide/disable Add, Edit, Remove, settings-save-to-disk.defaultServerId— from--server(UC5).- Optional
autoConnect— #1183.
Centralization belongs in core, not the launcher. Today:
| Piece | Today | Target |
|---|---|---|
| Canonical conversion | mcpConfigToServerEntries |
Same |
| TUI | loadTuiServers — full settings; no writable |
resolveServerList; gate UI on writable |
| Web | useServers → always default catalog; CRUD always on |
/api/servers returns session or catalog list + writable |
| CLI | Single server via resolveLaunchServerConfigs |
resolveSingleServer from same normalizer |
| Launcher | Forwards argv | Unchanged |
Import is not a list-client concern — catalog mutation via UC2 or web CRUD when writable.
Launch behavior by client
Web
| Launch | Catalog (/api/servers) |
initialMcpConfig → /api/config |
Connect |
|---|---|---|---|
| No server args | Default catalog via useServers |
Empty | User selects catalog server |
| Ad-hoc server flags | Still full default catalog | Populated (transport fields) | Launch config not consumed — no auto-connect, no form pre-fill (G2) |
--config + --server |
Still default catalog | Populated from resolved config (settings stripped) | User sees catalog, not launch file |
Target: /api/servers serves the resolved list for the session (catalog or UC3 file) with writable and defaultServerId. See Web launch behavior.
CLI and TUI
CLI connects to exactly one resolved config per invocation; TUI shows all entries from the resolved list. Resolution paths, settings lift, and port gaps are documented in v2_cli_tui_launcher.md. Key catalog-specific gaps: G1 (CLI drops disk settings on catalog/file lookup), G4 (web runWeb --header warns only).
Shared resolvers (resolveLaunchServerConfigs / resolveServerConfigs)
CLI and TUI call resolveLaunchServerConfigs() at launch; it applies withDefaultConfigPath() then delegates to resolveServerConfigs():
withDefaultConfigPath(options)
→ if no --config and no ad-hoc target: configPath = ~/.mcp-inspector/mcp.json
resolveServerConfigs(options, mode) — explicit options only; no default injection
single mode (CLI):
configPath + serverName → loadServerFromConfig (MCPServerConfig only — G1)
configPath, no serverName → sole entry or error if multiple
ad-hoc → buildConfigFromOptions
multi mode (TUI):
configPath → all entries via mcpConfigToServerEntries (settings lifted) in loadTuiServers
ad-hoc → resolveServerConfigs(..., "multi") in loadTuiServers
Web runWeb uses resolveServerConfigs() directly (explicit server argv only); catalog path is backend-owned until launch-config unification.
loadServerFromConfig returns bare MCPServerConfig. Only mcpConfigToServerEntries() lifts flat disk fields into InspectorServerSettings.
Import (UC2)
servers/import on CLI only (plus web Import menu for interactive use). Targets the active catalog (default or --catalog). No launcher --import flag — mcp-inspector --cli … --method servers/import is sufficient.
| Approach | Rationale |
|---|---|
| CLI method | Explicit, scriptable, reuses --config / ad-hoc argv parsing; same normalization as POST /api/servers |
| Not launcher flag | Launcher routes only; catalog mutation blurs responsibilities |
| Not all clients at startup | TUI/web import is UI concern (paste/upload) |
Behavior:
- UC2-A: read
--configsource → canonicalize → one or all entries (--serveroptional). - UC2-B: build one entry from ad-hoc argv (same resolver as UC3 launch) →
--server <id>names catalog entry → canonicalize. - Write to active catalog via
importServersIntoCatalog— never to the UC3 source file. - Collision policy per entry: skip, replace, or error (default TBD).
- Exit 0 with summary JSON (imported ids, skipped duplicates).
Web Import config vs Import server.json menu semantics: v2_ux.md and Server List File. Registry conversion: #922, #1435.
Write-back policy
No silent write-back to any file on launch. Catalog mutations go through /api/servers, servers/import, or explicit user CRUD.
| Action | Writes catalog? | Writes session --config file? |
|---|---|---|
| Launch ad-hoc command/URL | No | No |
Launch --server + overrides (-e, --cwd, --header) |
No | No |
Launch --config /other.json |
No | No (read-only) |
| User Add/Edit/Settings (web UI) | Yes (canonical serialize) | No |
| Import config / server.json (web UI, future) | Yes | No |
--cli --method servers/import |
Yes (active catalog) | No |
Future explicit --save-as <id> |
Opt-in only | No |
Runtime overlay model (CLI/TUI):
catalog entry (config + settings)
+ override flags for this process only
→ effective connect payload
→ never persisted unless explicit CRUD or import
Web launch behavior
Target model: catalog-first (B1). The catalog always exists in the UI. Launch args influence selection and connect ergonomics; they do not replace the catalog or auto-persist ad-hoc servers.
| Behavior | Rule |
|---|---|
| Catalog UI | Always show full resolved catalog (or UC3 session list when wired) |
--server <id> |
Set activeServerId / defaultServerId to matching catalog entry |
| Ad-hoc inline config | Temporary “session server” or “Quick connect” — do not auto-POST /api/servers |
| Auto-connect | Optional --connect or ?autoConnect=true (#1183) |
Rejected alternatives: launch-only UI hiding catalog (B2); v1.5 form pre-fill without catalog (B3).
Implementation sketch:
- Extend
GET /api/configwithinitialServerIdand/or settings reference. App.tsxon load: ifinitialServerIdmatches an entry, set selection; optional auto-connect behind flag.- Ad-hoc without catalog id: non-persisted session card or “Save to catalog?” prompt.
Target API (core)
Evolve loadTuiServers / resolveServerConfigs in core (not launcher):
interface ResolvedServerList {
servers: ServerEntry[];
writable: boolean;
source: "catalog" | "alternate-file" | "adhoc";
configPath?: string;
defaultServerId?: string;
}
resolveServerList(options: ServerConfigOptions): ResolvedServerList;
resolveSingleServer(options: ServerConfigOptions): { entry: ServerEntry; ... }; // CLI
importServersIntoCatalog(
entries: ServerEntry[],
opts?: { onConflict: "skip" | "error" | "replace" },
): ImportResult;
Rules:
- Ad-hoc (no
--config, no--catalog) → one entry,writable: false(UC3). --catalogor default → active catalog,writable: true(UC1/UC4).--configwithout catalog intent → canonicalize →ServerEntry[],writable: false(UC3).defaultServerIdfrom--server.- Runtime overlays on connect, not on disk.
- Web
/api/serversand TUI useresolveServerList; CLI usesresolveSingleServer.
Open decisions
| Topic | Status | Notes |
|---|---|---|
| Import collision default | TBD | skip vs error vs replace for duplicate catalog ids |
MCP_CATALOG_PATH env name |
Proposed | Alias for --catalog |
| Ad-hoc web UX detail | Partially decided | B1 catalog-first; exact “Quick connect” vs banner TBD |
TUI --server initial selection |
Open | Optional highlight when UC5 used from launcher |
Resolved (documented above): catalog vs session flag split; no launch write-back; CLI-only servers/import; core-owned resolveServerList; web catalog-first (B1).
Known gaps
| # | Gap | Severity | Clients |
|---|---|---|---|
| G1 | CLI catalog/file lookup drops settings — loadServerFromConfig not mcpConfigToServerEntries |
Functional | CLI |
| G2 | Web ignores launch-time initialMcpConfig for list/selection/connect |
Functional / UX | Web |
| G3 | InitialConfigPayload has no settings — cannot pass headers/oauth at web launch |
Functional | Web + launcher |
| G4 | runWeb --header warns only |
Functional | Web launcher |
| G5 | Two config pipelines — config.ts vs serverList.ts |
Design debt | CLI vs TUI vs web |
| G6 | v1.5 docs/mcp-server-configuration.md not ported to v2 |
Docs | All |
| G7 | CLI test expects --server without --config to fail — predates default catalog |
Tests | CLI |
| G8 | No auto-connect (#1183) | Enhancement | Web |
| G9 | Import config / Import server.json menu stubbed | UX | Web |
| G10 | CLI thin parse path — no full normalizeMcpServers / settings lift on catalog GET parity |
Consistency | CLI |
G1, G4, and launcher details: v2_cli_tui_launcher.md.
Decision matrix
| User launches with… | File read | Canonicalize on read? | Catalog UI (web) | Writable? |
|---|---|---|---|---|
| Nothing (defaults) | Default catalog | Yes | Full list | Catalog only |
--server foo |
Default catalog | Yes | Full list; should select foo |
Catalog only |
--config other.json |
Alternate file (UC3) | Yes | Session list (target); today default catalog | No |
--catalog ./proj/mcp.json |
Project catalog | Yes | That catalog (target) | That path |
Ad-hoc node srv.js |
None | N/A | Full catalog + session hint (target) | Catalog only if user saves |
| Web Add / Edit / Import | N/A | Yes before write | Full list | Catalog only |
servers/import |
Source (A) or ad-hoc (B) | Yes before write | N/A (CLI) | Active catalog |
Engineering follow-ups
- Core:
resolveServerList,resolveSingleServer,importServersIntoCatalog. - CLI: resolve through shared normalizer; implement
--method servers/import(G1, G10). - Web:
/api/serversserves resolved session or catalog list +writable+defaultServerId(G2). - TUI:
resolveServerList; respectwritable(no CRUD on UC3 files). - Launcher / web: extend
InitialConfigPayloadfor settings at launch (G3, G4). - Tests: CLI default-catalog success with tmp catalog; import method tests (G7).
- Docs: port
docs/mcp-server-configuration.mdto v2 (G6); close/update #1347.
Related GitHub issues
| Issue | Status | Relevance |
|---|---|---|
#1347 — default --config |
Open (likely closable) | Implemented via resolveLaunchServerConfigs() |
| #1246 — port CLI/TUI/launcher | Done in worktree | Parent of default-catalog behavior |
| #1183 — auto-connect | Open | UC5 web ergonomics |
| #1348 — import from other clients | Open | UC2 web UI |
| #1435 — registry import | Open | UC2 registry path |
| #1432 — CLI v2 | Open | Session CLI umbrella |
| #1352 / #1358 | Merged | Flat settings on disk |
| #1356 | Merged | Secrets in keychain |
References
- v2_servers_file.md — catalog format, CRUD, settings on disk
- v2_cli_tui_launcher.md — CLI/TUI/launcher architecture and port gaps
- v2_ux.md — Import config vs Import server.json UX
core/mcp/node/config.ts—withDefaultConfigPath,resolveLaunchServerConfigs,resolveServerConfigscore/mcp/serverList.ts—mcpConfigToServerEntriesclients/tui/src/tui-servers.ts— TUI catalog + settings load pathclients/cli/src/cli.ts— single-config resolve; settings gap (G1)clients/web/server/run-web.ts— launcher argv →initialMcpConfigclients/web/src/App.tsx— catalog viauseServersclients/web/src/components/groups/ImportServerJsonPanel/— registry panel (unwired)clients/web/src/components/groups/ServerAddMenu/ServerAddMenu.tsx— Add menu labels