|
7 | 7 |
|
8 | 8 | ## Summary |
9 | 9 |
|
10 | | -Replaces the hardcoded `SEED_SERVERS` in `clients/web/src/App.tsx:47` with a file-backed list at `~/.mcp-inspector/mcp.json`, read at startup, mutated via REST endpoints, surfaced through a `useServers` hook. |
| 10 | +Replaces the hardcoded `SEED_SERVERS` in `clients/web/src/App.tsx:47` with a file-backed list at `~/.mcp-inspector/mcp.json`, read at startup, mutated via REST endpoints, surfaced through a `useServers` hook. The file also stores the per-server **settings** (custom headers, request metadata, timeouts, pre-configured OAuth credentials) edited by `ServerSettingsForm` — see [Per-server settings](#per-server-settings-1352) below for the on-disk shape, UI rationale, and write/read invariants. |
11 | 11 |
|
12 | 12 | ## Goals |
13 | 13 |
|
@@ -52,7 +52,8 @@ Replaces the hardcoded `SEED_SERVERS` in `clients/web/src/App.tsx:47` with a fil |
52 | 52 | - Matches `MCPConfig` in `core/mcp/types.ts:68`. |
53 | 53 | - `type` omitted → normalized to `"stdio"`; `type: "http"` → `"streamable-http"` (`normalizeServerType` in `core/mcp/node/config.ts:81`). |
54 | 54 | - The map key is the **server `id`**. `ServerEntry.id` already documents itself this way (`core/mcp/types.ts:89`: "The MCPConfig.mcpServers map key"). |
55 | | -- Display name: derived from the map key. The Inspector adds **no extension fields** to keep `mcp.json` clean and tool-interoperable. The edit dialog therefore treats id and display name as the same field; renaming = key-rotate + carry config across. |
| 55 | +- Display name: derived from the map key. The edit dialog treats id and display name as the same field; renaming = key-rotate + carry config across. |
| 56 | +- Each entry may optionally carry a `settings` node alongside the transport keys (`headers`, `metadata`, timeouts, OAuth credentials — see [Per-server settings](#per-server-settings-1352) below). It is an Inspector-specific extension; other MCP tools (Claude Desktop, Cursor, Cline) treat it as an unknown key and ignore it. |
56 | 57 |
|
57 | 58 | ## First-run behavior |
58 | 59 |
|
@@ -187,6 +188,8 @@ Per `AGENTS.md`'s "test new or modified code" rule plus the UI-changes guidance: |
187 | 188 |
|
188 | 189 | ## Per-server settings (#1352) |
189 | 190 |
|
| 191 | +Our new UI design separates the basic server configuration (transport, URL or command + args + env) from settings (custom headers, connect/request timeout, global request metadata, client id/secret) into two dialogs. The latter is stored in an optional `settings` node of the server config. The reason they're separated in the UI is that custom settings are less likely to be needed than basic config, so a simpler, friendlier form greets most users. |
| 192 | + |
190 | 193 | Each server entry may carry an optional `settings` node alongside the transport config: |
191 | 194 |
|
192 | 195 | ```jsonc |
@@ -217,9 +220,17 @@ Each server entry may carry an optional `settings` node alongside the transport |
217 | 220 | - `settings.connectionTimeout` → `Promise.race` wrapper around `InspectorClient.connect()` in the web client. |
218 | 221 | - `settings.oauthClientId` / `oauthClientSecret` / `oauthScopes` → pre-seeded OAuth client credentials via `InspectorClientOptions.oauth`. |
219 | 222 | - **First-connect contract**: settings apply on the *first* outbound request after the entry loads from disk — no need to open the settings form. The browser sends `settings` to the backend in the `/api/mcp/connect` body; the backend reads it from `RemoteConnectRequest` and threads it into `createTransportNode`. |
220 | | -- **Secret storage**: `oauthClientSecret` is persisted in `mcp.json` alongside stdio `env` values, both protected by the file's `0o600` permission. OS-keychain integration is out of scope; a follow-up may switch the layout if a stronger secret store is needed. |
| 223 | +- **Secret storage**: `oauthClientSecret` is persisted in `mcp.json` alongside stdio `env` values, both protected by the file's `0o600` permission. OS-keychain integration is tracked separately in #1356 — when that lands, the secret will be lifted out of the file and replaced with a keychain reference. |
221 | 224 | - **Removed**: `MCPServerConfig.headers` (previously on `SseServerConfig` / `StreamableHttpServerConfig`) has been deleted. The headers textarea in `ServerConfigModal` is gone; HTTP headers are entered only in `ServerSettingsForm`. v2 has not shipped a stable release with the old shape, so no on-read migration is included. |
222 | | -- **UI**: `ServerSettingsModal` is opened from the server card's settings affordance; saving routes through `useServers.updateServerSettings(id, settings)` which calls `PUT /api/servers/:id` with `{ id, config, settings }`. `useServers.updateServer` re-sends the existing settings whenever it issues a PUT so the config-modal save does not clobber persisted settings. |
| 225 | +- **UI**: `ServerSettingsModal` is opened from the server card's settings affordance. Saving routes through `useServers.updateServerSettings(id, settings)` which issues a settings-only `PUT /api/servers/:id` with `{ id, settings }` — the route preserves the on-disk transport config inside its write lock. Conversely, `useServers.updateServer` (driven by the basic-config modal) issues a config-only PUT with `{ id, config }` and the route preserves the on-disk settings node. Edits in either modal cannot silently wipe the other half. |
| 226 | +- **Save cadence**: the form fires `onSettingsChange` on every keystroke. `App.tsx` debounces 300 ms and flushes on modal close so a burst of edits coalesces into a single PUT. If the close-flush PUT fails (network hiccup, server 500), a red `@mantine/notifications` toast surfaces the failure — the modal has already closed so a silent failure would leave the user thinking the last edits saved. |
| 227 | +- **`PUT /api/servers/:id` patch semantics**: both `config` and `settings` are independent patches. |
| 228 | + - Field omitted → preserve the on-disk value. |
| 229 | + - Explicit `null` on `settings` → clear the settings node. (`config` may not be `null`; a body that wants to update only settings should omit `config` entirely.) |
| 230 | + - Field present and well-formed → validate and apply. |
| 231 | + - A bare `PUT { id: "renamed" }` is a pure rename preserving both halves. |
| 232 | +- **Write-path gates**: `validateSettings` rejects malformed shapes (non-object, wrong-typed `headers` / `metadata`, non-numeric timeouts) with `400` + descriptive message and picks-and-builds the validated value so unknown stowaway keys silently drop. `buildStoredEntry` strips any `settings` key nested inside the incoming `config` (and logs a `warn` with the server id) so `validateSettings` remains the only path settings reach disk on the write side. |
| 233 | +- **Read-path gates**: `normalizeMcpServers` re-runs `validateSettings` on the on-disk settings node when loading; a malformed hand-edited `settings` node is silently dropped (lenient-on-read pattern matching `normalizeServerType`'s unknown→stdio fallback). A subsequent preserve-on-PUT cannot propagate the broken node, and `mcp.json` files written by future versions with unknown stowaway keys won't poison the in-memory shape. |
223 | 234 |
|
224 | 235 | ## Out of scope (follow-ups) |
225 | 236 |
|
|
0 commit comments