feat(cli): lift config-file headers/timeouts/OAuth into serverSettings (#1482) (#1500)

* feat(cli): lift config-file headers/timeouts/OAuth into serverSettings (#1482)

The CLI resolved file-sourced servers via resolveServerConfigs(), which
returns bare MCPServerConfig and dropped disk-level headers, timeouts, and
OAuth — CLI connections from a --catalog/--config file silently ignored
them. The TUI already lifted these via mcpConfigToServerEntries().

Extract the TUI's catalog/config -> per-server-settings resolution into a
shared core module (core/mcp/node/servers.ts: loadServerEntries +
selectServerEntry) and route both CLI and TUI through it, so the two share
one file->settings path. --header still overrides the file's headers for
the run while preserving the file's timeouts/OAuth.

- core: add loadServerEntries/selectServerEntry/headersToServerSettings
- tui: tui-servers.ts re-exports the shared core helpers (no logic dup)
- cli: resolve via loadServerEntries + selectServerEntry; drop the
  CLI-only conflict check and bare-config path
- tests: core servers.test.ts (settings lifting, header merge/override,
  stdio overrides, seed/error/conflict, selection); CLI integration tests
  asserting a config header is sent over HTTP and --header overrides it
- docs: CLI README note on file settings being applied

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(core): address #1500 review — dedup overrides, drop dead wrapper, clearer error, spec refresh

- Remove the now-unused resolveLaunchServerConfigs() wrapper (both CLI and
  TUI route through loadServerEntries now); migrate its default-catalog
  launch tests to compose withDefaultCatalogPath + resolveServerConfigs.
- Deduplicate applyStdioOverrides against config.ts applyOverrides: export
  the latter (now trims cwd) and reuse it in servers.ts.
- selectServerEntry's unknown-name error is source-agnostic ("not found"
  not "not found in config file") since entries may be a single ad-hoc
  default, where "config file" would mislead.
- Refresh specification/ docs (v2_cli_tui_launcher.md,
  v2_catalog_launch_config.md): CLI/TUI resolution now via
  loadServerEntries/selectServerEntry; mark gap G1 resolved (#1482);
  withDefaultConfigPath → withDefaultCatalogPath.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(cli): extend smoke:cli to cover #1482 lift→serverSettings

Adds end-to-end smoke coverage for the config-file headers/timeouts/OAuth
lift introduced in #1482:

- unknown --server now errors source-agnostically ("not found")
- multi-server catalog selection via --server (and the omit-it error)
- --header merge does not break the stdio connect path
- HTTP transport: a config-file `headers` object is lifted onto the wire,
  and a CLI --header overrides it (verified against an in-process test
  server's recorded requests via a non-blocking spawn)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-06-20 11:33:21 -04:00 • committed by GitHub
commit e72d4307f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,35 +3,36 @@
### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | V2 Tech Stack | [V2 UX](v2_ux.md)
#### [Web Client](v2_web_client.md) | [CLI, TUI, Launcher](v2_cli_tui_launcher.md) | [Server](v2_server.md) | [Storage](v2_storage.md)
##### [Overview](v2_storage.md) | [Server List File](v2_servers_file.md) | Catalog and Launch
## Table of Contents
* [Summary](#summary)
* [Goals](#goals)
* [Non-goals](#non-goals)
* [Related specifications](#related-specifications)
* [Terminology](#terminology)
* [User intents (UC1–UC5)](#user-intents-uc1uc5)
* [Flag reference](#flag-reference)
* [Background (v1.5)](#background-v15)
* [Normalization](#normalization)
* [Cross-client list contract](#cross-client-list-contract)
* [Launch behavior by client](#launch-behavior-by-client)
* [Import (UC2)](#import-uc2)
* [Write-back policy](#write-back-policy)
* [Web launch behavior](#web-launch-behavior)
* [Target API (core)](#target-api-core)
* [Open decisions](#open-decisions)
* [Known gaps](#known-gaps)
* [Decision matrix](#decision-matrix)
* [Engineering follow-ups](#engineering-follow-ups)
* [Related GitHub issues](#related-github-issues)
* [References](#references)
- [Summary](#summary)
- [Goals](#goals)
- [Non-goals](#non-goals)
- [Related specifications](#related-specifications)
- [Terminology](#terminology)
- [User intents (UC1–UC5)](#user-intents-uc1uc5)
- [Flag reference](#flag-reference)
- [Background (v1.5)](#background-v15)
- [Normalization](#normalization)
- [Cross-client list contract](#cross-client-list-contract)
- [Launch behavior by client](#launch-behavior-by-client)
- [Import (UC2)](#import-uc2)
- [Write-back policy](#write-back-policy)
- [Web launch behavior](#web-launch-behavior)
- [Target API (core)](#target-api-core)
- [Open decisions](#open-decisions)
- [Known gaps](#known-gaps)
- [Decision matrix](#decision-matrix)
- [Engineering follow-ups](#engineering-follow-ups)
- [Related GitHub issues](#related-github-issues)
- [References](#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`).
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 `--catalog`/`--config` and ad-hoc targets (the shared `loadServerEntries()` applies `withDefaultCatalogPath()`, in `core/mcp/node/servers.ts` / `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](v2_servers_file.md). CLI/TUI/launcher build and per-client implementation gaps are in [CLI, TUI, and Launcher](v2_cli_tui_launcher.md).
@ -56,26 +57,26 @@ This specification defines **launch-time configuration** across web, CLI, and TU
## Related specifications
| Document | Owns |
|----------|------|
| [v2_servers_file.md](v2_servers_file.md) | Catalog path, on-disk shape, web CRUD, `/api/servers`, per-server settings |
| [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md) | Bins, tsup, tests, `runWeb`, CLI config-file settings gap (G1) |
| [v2_ux.md](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) |
| Document | Owns |
| ------------------------------------------------ | ------------------------------------------------------------------------------- |
| [v2_servers_file.md](v2_servers_file.md) | Catalog path, on-disk shape, web CRUD, `/api/servers`, per-server settings |
| [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md) | Bins, tsup, tests, `runWeb`, CLI config-file settings lift (G1, resolved #1482) |
| [v2_ux.md](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). |
| 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 `--catalog`/`--config` and no ad-hoc target. `loadServerEntries()` injects the catalog path via `withDefaultCatalogPath()`, then `selectServerEntry()` resolves the named entry. |
| **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). |
---
@ -87,11 +88,11 @@ Five distinct user goals. They share flags (`--config`, `--server`, `--catalog`)
**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) |
| | Today | Target |
| ------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| **Web** | Works — `useServers` + `/api/servers` CRUD | Same |
| **TUI** | Loads default catalog via `withDefaultCatalogPath` in `loadServerEntries`; **no CRUD UX yet** | Catalog CRUD in TUI (planned) |
| **CLI** | `--server foo --method …` with no `--config` resolves against default catalog | Same; settings lifted from catalog entries (G1 resolved #1482) |
```bash
mcp-inspector --web
@ -105,10 +106,10 @@ The catalog is both the **data source** and the **write target** (`writable: tru
**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) |
| 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) |
```bash
# A: import all from a foreign mcp.json
@ -148,11 +149,11 @@ mcp-inspector --cli --catalog ./project/.mcp-inspector/mcp.json \
**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 |
| | 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](v2_ux.md); [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348), [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435) |
| **TUI** | N/A | No requirement |
| **TUI** | N/A | No requirement |
See [Import (UC2)](#import-uc2) for behavior rules.
@ -160,11 +161,11 @@ See [Import (UC2)](#import-uc2) for behavior rules.
**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 |
| | 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 |
| **CLI** | Single resolved server (file + `--server`, ad-hoc, or default catalog) | Same; shared normalizer + settings lift |
```bash
mcp-inspector --tui --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
@ -189,11 +190,11 @@ 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 |
| | 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.
@ -201,21 +202,21 @@ mcp-inspector --cli --catalog ~/work/mcp.json --server api --method tools/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](https://github.com/modelcontextprotocol/inspector/issues/1183)) |
| | 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](https://github.com/modelcontextprotocol/inspector/issues/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) | — |
| 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.
@ -225,21 +226,21 @@ mcp-inspector --cli --catalog ~/work/mcp.json --server api --method tools/list
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) |
| 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** |
| 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`.
@ -247,7 +248,7 @@ 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](v2_servers_file.md)), flat on-disk settings (post-#1358), and `resolveLaunchServerConfigs()` for CLI/TUI launch resolution.
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](v2_servers_file.md)), flat on-disk settings (post-#1358), and the shared `loadServerEntries()` for CLI/TUI launch resolution (lifting disk settings into `InspectorServerSettings`).
---
@ -255,12 +256,12 @@ v1.5 (`docs/mcp-server-configuration.md`) had no default catalog: omitting `--co
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` |
| 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](v2_servers_file.md).
@ -283,13 +284,13 @@ Required inputs:
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 |
| 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 `loadServerEntries` + `selectServerEntry` (settings lifted) | `resolveSingleServer` from same normalizer |
| **Launcher** | Forwards argv | Unchanged |
Import is not a list-client concern — catalog mutation via UC2 or web CRUD when `writable`.
@ -303,12 +304,12 @@ Implemented (#1481/#1483): `runWeb` selects the backend's catalog source and a
session-wide `writable` flag (`/api/config`); the web UI hides catalog CRUD when
`writable: false`.
| Launch | Catalog (`/api/servers`) | `writable` | Connect |
|--------|--------------------------|-----------|---------|
| No server args | Default catalog via `useServers` | `true` | User selects catalog server |
| `--catalog <path>` / `MCP_CATALOG_PATH` | That file (seed/CRUD/watch) | `true` | User selects a server |
| `--config <path>` | That file, served read-only (never written/seeded/migrated) | `false` | User selects; CRUD hidden |
| Ad-hoc server flags (`--server-url`/command, `--header`) | One server held **in memory** (no file) | `false` | User connects; `--header` applied; CRUD hidden |
| Launch | Catalog (`/api/servers`) | `writable` | Connect |
| -------------------------------------------------------- | ----------------------------------------------------------- | ---------- | ---------------------------------------------- |
| No server args | Default catalog via `useServers` | `true` | User selects catalog server |
| `--catalog <path>` / `MCP_CATALOG_PATH` | That file (seed/CRUD/watch) | `true` | User selects a server |
| `--config <path>` | That file, served read-only (never written/seeded/migrated) | `false` | User selects; CRUD hidden |
| Ad-hoc server flags (`--server-url`/command, `--header`) | One server held **in memory** (no file) | `false` | User connects; `--header` applied; CRUD hidden |
`--catalog`/`--config` are mutually exclusive, and neither combines with an
ad-hoc target or `--header`; `--header` requires an ad-hoc HTTP/SSE server.
@ -317,37 +318,26 @@ ad-hoc target or `--header`; `--header` requires an ad-hoc HTTP/SSE server.
### 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](v2_cli_tui_launcher.md). Key remaining catalog-specific gap: **G1** (CLI drops disk settings on catalog/file lookup). (Web's **G4** `--header` and **G2** list-seeding are resolved — see above.)
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](v2_cli_tui_launcher.md). (Web's **G4** `--header` and **G2** list-seeding are resolved — see above; **G1**, the CLI dropping disk settings, is resolved by #1482 — see below.)
### Shared resolvers (`resolveLaunchServerConfigs` / `resolveServerConfigs`)
### Shared resolver (`loadServerEntries` / `selectServerEntry`)
CLI and TUI call `resolveLaunchServerConfigs()` at launch; it applies `withDefaultConfigPath()` then delegates to `resolveServerConfigs()`:
CLI and TUI both call the shared `loadServerEntries()` in `core/mcp/node/servers.ts` at launch (the TUI's `loadTuiServers` is now a thin re-export). It applies `withDefaultCatalogPath()`, enforces the conflict matrix, and lifts disk settings via `mcpConfigToServerEntries()`:
```
withDefaultConfigPath(options)
→ if no --config and no ad-hoc target: configPath = ~/.mcp-inspector/mcp.json
resolveServerConfigs(options, mode) — explicit options only; no default injection
loadServerEntries(options) → Record<name, { config, settings }>
withDefaultCatalogPath(options)
→ if no --catalog/--config and no ad-hoc target: catalogPath = ~/.mcp-inspector/mcp.json
source (catalog/config) → mcpConfigToServerEntries (settings lifted), --header merged per entry
ad-hoc → resolveServerConfigs(..., "multi") → single { default } entry, --header as settings
```
**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
```
- **CLI:** `selectServerEntry(entries, --server)` picks one (named, or the sole entry, else errors). Disk `headers`/timeouts/OAuth are now applied (#1482 closes **G1**); `--header` overrides the file's headers.
- **TUI:** uses the full `entries` record (all servers shown).
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`.
`mcpConfigToServerEntries()` is the single place that lifts flat disk fields into `InspectorServerSettings`; both clients now flow through it.
---
@ -355,11 +345,11 @@ Web `runWeb` uses `resolveServerConfigs()` directly (explicit server argv only);
**`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) |
| 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:**
@ -377,15 +367,15 @@ Web **Import config** vs **Import server.json** menu semantics: [v2_ux.md](v2_ux
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 |
| 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):**
@ -402,12 +392,12 @@ catalog entry (config + settings)
**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](https://github.com/modelcontextprotocol/inspector/issues/1183)) |
| 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](https://github.com/modelcontextprotocol/inspector/issues/1183)) |
**Rejected alternatives:** launch-only UI hiding catalog (B2); v1.5 form pre-fill without catalog (B3).
@ -454,12 +444,12 @@ importServersIntoCatalog(
## 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 |
| 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).
@ -467,18 +457,18 @@ Resolved (documented above): catalog vs session flag split; no launch write-back
## Known gaps
| # | Gap | Severity | Clients |
|---|-----|----------|---------|
| G1 | CLI catalog/file lookup drops settings — `loadServerFromConfig` not `mcpConfigToServerEntries` | Functional | CLI |
| G2 | ~~Web ignores launch-time server input for the list~~ — **resolved for web (#1481):** `runWeb` now points the backend catalog at `--catalog` (writable) / `--config` (read-only session), or serves an in-memory ad-hoc server, and `/api/config` carries `writable` so the UI gates CRUD. Selection/auto-connect (`--server` / `defaultServerId`) is still open (G8). | Web |
| G3 | ~~`InitialConfigPayload` has no settings — cannot pass headers at web launch~~ — **resolved for ad-hoc headers (#1483):** ad-hoc `--header` rides on the in-memory session entry (`headers` → `settings.headers`); a general settings channel on `InitialConfigPayload` is still unbuilt. | Web + launcher |
| G4 | ~~`runWeb --header` warns only~~ — **resolved (#1483):** applied to the seeded ad-hoc connection (or a clear error when it can't apply). | 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](https://github.com/modelcontextprotocol/inspector/issues/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 |
| # | Gap | Severity | Clients |
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----------------- |
| G1 | ~~CLI catalog/file lookup drops settings — `loadServerFromConfig` not `mcpConfigToServerEntries`~~ — **resolved (#1482):** CLI now resolves via the shared `loadServerEntries()` → `mcpConfigToServerEntries()`, lifting disk `headers`/timeouts/OAuth; `--header` overrides the file's headers. | Functional | CLI |
| G2 | ~~Web ignores launch-time server input for the list~~ — **resolved for web (#1481):** `runWeb` now points the backend catalog at `--catalog` (writable) / `--config` (read-only session), or serves an in-memory ad-hoc server, and `/api/config` carries `writable` so the UI gates CRUD. Selection/auto-connect (`--server` / `defaultServerId`) is still open (G8). | Web |
| G3 | ~~`InitialConfigPayload` has no settings — cannot pass headers at web launch~~ — **resolved for ad-hoc headers (#1483):** ad-hoc `--header` rides on the in-memory session entry (`headers` → `settings.headers`); a general settings channel on `InitialConfigPayload` is still unbuilt. | Web + launcher |
| G4 | ~~`runWeb --header` warns only~~ — **resolved (#1483):** applied to the seeded ad-hoc connection (or a clear error when it can't apply). | 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](https://github.com/modelcontextprotocol/inspector/issues/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](v2_cli_tui_launcher.md).
@ -486,22 +476,22 @@ G1, G4, and launcher details: [v2_cli_tui_launcher.md](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 |
| 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
1. **Core:** `resolveServerList`, `resolveSingleServer`, `importServersIntoCatalog`.
2. **CLI:** resolve through shared normalizer; implement `--method servers/import` (G1, G10).
2. **CLI:** resolve through shared normalizer; implement `--method servers/import` (G10; G1 resolved #1482).
3. **Web:** `/api/servers` serves resolved session or catalog list + `writable` + `defaultServerId` (G2).
4. **TUI:** `resolveServerList`; respect `writable` (no CRUD on UC3 files).
5. **Launcher / web:** extend `InitialConfigPayload` for settings at launch (G3, G4).
@ -512,18 +502,19 @@ G1, G4, and launcher details: [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md).
## Related GitHub issues
| Issue | Status | Relevance |
|-------|--------|-----------|
| [#1481](https://github.com/modelcontextprotocol/inspector/issues/1481) — launcher `--config` drives the web catalog | Implemented | `--catalog` (writable) / `--config` (read-only session) / ad-hoc in-memory → backend `mcpConfigPath`/`initialServers` + `writable` on `/api/config`; UI gates CRUD |
| [#1483](https://github.com/modelcontextprotocol/inspector/issues/1483) — web `--header` warns only | Implemented | `--header` lifted onto the in-memory ad-hoc entry (`settings.headers`); errors clearly when it can't apply |
| [#1347](https://github.com/modelcontextprotocol/inspector/issues/1347) — default `--config` | Open (likely closable) | Implemented via `resolveLaunchServerConfigs()` |
| [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) — port CLI/TUI/launcher | Done in worktree | Parent of default-catalog behavior |
| [#1183](https://github.com/modelcontextprotocol/inspector/issues/1183) — auto-connect | Open | UC5 web ergonomics |
| [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348) — import from other clients | Open | UC2 web UI |
| [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435) — registry import | Open | UC2 registry path |
| [#1432](https://github.com/modelcontextprotocol/inspector/issues/1432) — CLI v2 | Open | Session CLI umbrella |
| [#1352](https://github.com/modelcontextprotocol/inspector/pull/1352) / [#1358](https://github.com/modelcontextprotocol/inspector/pull/1358) | Merged | Flat settings on disk |
| [#1356](https://github.com/modelcontextprotocol/inspector/pull/1356) | Merged | Secrets in keychain |
| Issue | Status | Relevance |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [#1481](https://github.com/modelcontextprotocol/inspector/issues/1481) — launcher `--config` drives the web catalog | Implemented | `--catalog` (writable) / `--config` (read-only session) / ad-hoc in-memory → backend `mcpConfigPath`/`initialServers` + `writable` on `/api/config`; UI gates CRUD |
| [#1483](https://github.com/modelcontextprotocol/inspector/issues/1483) — web `--header` warns only | Implemented | `--header` lifted onto the in-memory ad-hoc entry (`settings.headers`); errors clearly when it can't apply |
| [#1347](https://github.com/modelcontextprotocol/inspector/issues/1347) — default `--config` + `--catalog`/`--config` normalization | Closed (#1499) | Default catalog + normalized flags; CLI/TUI resolve via the shared `loadServerEntries()` |
| [#1482](https://github.com/modelcontextprotocol/inspector/issues/1482) — CLI lift config-file settings (G1) | Closed (#1500) | CLI routes file-sourced servers through `loadServerEntries()`/`mcpConfigToServerEntries()`; disk headers/timeouts/OAuth applied |
| [#1246](https://github.com/modelcontextprotocol/inspector/issues/1246) — port CLI/TUI/launcher | Done in worktree | Parent of default-catalog behavior |
| [#1183](https://github.com/modelcontextprotocol/inspector/issues/1183) — auto-connect | Open | UC5 web ergonomics |
| [#1348](https://github.com/modelcontextprotocol/inspector/issues/1348) — import from other clients | Open | UC2 web UI |
| [#1435](https://github.com/modelcontextprotocol/inspector/issues/1435) — registry import | Open | UC2 registry path |
| [#1432](https://github.com/modelcontextprotocol/inspector/issues/1432) — CLI v2 | Open | Session CLI umbrella |
| [#1352](https://github.com/modelcontextprotocol/inspector/pull/1352) / [#1358](https://github.com/modelcontextprotocol/inspector/pull/1358) | Merged | Flat settings on disk |
| [#1356](https://github.com/modelcontextprotocol/inspector/pull/1356) | Merged | Secrets in keychain |
---
@ -532,10 +523,11 @@ G1, G4, and launcher details: [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md).
- [v2_servers_file.md](v2_servers_file.md) — catalog format, CRUD, settings on disk
- [v2_cli_tui_launcher.md](v2_cli_tui_launcher.md) — CLI/TUI/launcher architecture and port gaps
- [v2_ux.md](v2_ux.md) — Import config vs Import server.json UX
- `core/mcp/node/config.ts` — `withDefaultConfigPath`, `resolveLaunchServerConfigs`, `resolveServerConfigs`
- `core/mcp/node/servers.ts` — `loadServerEntries`, `selectServerEntry` (shared CLI/TUI catalog + settings load path)
- `core/mcp/node/config.ts` — `withDefaultCatalogPath`, `resolveServerConfigs`, `serverSourceConflict`
- `core/mcp/serverList.ts` — `mcpConfigToServerEntries`
- `clients/tui/src/tui-servers.ts` — TUI catalog + settings load path
- `clients/cli/src/cli.ts` — single-config resolve; settings gap (G1)
- `clients/tui/src/tui-servers.ts` — thin re-export of the shared `loadServerEntries`
- `clients/cli/src/cli.ts` — single-config resolve via `loadServerEntries`/`selectServerEntry` (G1 resolved #1482)
- `clients/web/server/run-web.ts` — launcher argv → `initialMcpConfig`
- `clients/web/src/App.tsx` — catalog via `useServers`
- `clients/web/src/components/groups/ImportServerJsonPanel/` — registry panel (unwired)