Background
#1343 landed persistence of the server list to ~/.mcp-inspector/mcp.json, but the per-server settings edited by ServerSettingsForm (clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx) are not persisted anywhere. The form is purely presentational — its onSettingsChange callbacks never reach disk, and there is no separate store for them in core/storage/ or elsewhere.
The shape that needs persisting is InspectorServerSettings (core/mcp/types.ts:283):
export interface InspectorServerSettings {
headers: { key: string; value: string }[];
metadata: { key: string; value: string }[];
connectionTimeout: number;
requestTimeout: number;
oauthClientId?: string;
oauthClientSecret?: string;
oauthScopes?: string;
}
These should round-trip through mcp.json and be applied from the first connection to a server — not just after the user opens the settings form.
Note: pre-configured OAuth credentials (clientId / clientSecret / scopes) are distinct from the tokens managed by core/auth/oauth-storage.ts — the existing OAuth store only persists discovery metadata and tokens acquired at runtime, not user-entered client credentials.
Proposal
Add a settings node to each server entry in mcp.json, alongside the existing transport config:
Headers consolidation — single source of truth in settings.headers
MCPServerConfig.headers on the SSE / streamable-http variants (core/mcp/types.ts:48,57) and InspectorServerSettings.headers represent the same concept. Today only MCPServerConfig.headers is wired to the wire (core/mcp/node/transport.ts:69,75,97 and core/mcp/remote/remoteClientTransport.ts:160); InspectorServerSettings.headers is defined but never read.
Decision: collapse to settings.headers so there is one place to edit headers and one place to read them.
- Remove the Headers textarea from
ServerConfigModal (clients/web/src/components/groups/ServerConfigModal/ServerConfigModal.tsx lines 399–411, plus the headersText form field, the parseHeaders helper, and the references to c.headers / config.headers in configToFormState and buildConfig).
- Keep
ServerSettingsForm's "Custom Headers" section as the sole UI for entering HTTP headers; its onHeaderChange / onAddHeader / onRemoveHeader callbacks now drive persistence into settings.headers.
- Update transport construction to source headers from the settings node:
core/mcp/node/transport.ts — replace reads of sseConfig.headers / httpConfig.headers with the merged settings.headers for that entry.
core/mcp/remote/remoteClientTransport.ts — send settings to the remote server in the /api/mcp/connect body (or have the remote server load it from its own copy of mcp.json by id).
- Deprecate
MCPServerConfig.headers on the type — either remove outright, or keep it as a one-shot read path that migrates into settings.headers on first load of a legacy mcp.json. Recommendation goes in the implementation PR.
This keeps the "ServerConfigModal = essentials, ServerSettingsForm = extra settings" split clean: simple servers (most of them) never need to open the settings form at all.
Applying the rest of the settings at connect time
connectionTimeout / requestTimeout — applied via the SDK transport / client options at connect.
metadata — attached to each outgoing MCP request's _meta field (matches the form's hint text on ServerSettingsForm.tsx:161).
oauthClientId / oauthClientSecret / oauthScopes — fed into the OAuth flow as the pre-configured client credentials (separate from the runtime tokens that core/auth/oauth-storage.ts already manages).
Storing the OAuth client secret
oauthClientSecret is sensitive. Options to consider in the PR:
- Persist in
mcp.json with documented file-permission expectations (matches how stdio env values are handled today).
- Persist a reference to an OS keychain entry instead of the literal value.
- Defer secret persistence in this issue and only persist
clientId / scopes.
Files to touch
core/mcp/types.ts — extend the server entry shape with optional settings: InspectorServerSettings; deprecate/remove MCPServerConfig.headers on SSE / streamable-http.
core/mcp/serverList.ts — mcpConfigToServerEntries / serverEntriesToMcpConfig must round-trip the settings node (and handle legacy headers at the transport level if a migration path is chosen).
core/mcp/node/transport.ts and core/mcp/remote/remoteClientTransport.ts — read headers, timeouts, and metadata from settings at connect time.
core/mcp/remote/node/server.ts — /api/servers routes (around lines 630–780) read/write the settings node; /api/mcp/connect receives or resolves settings.
clients/web/src/components/groups/ServerConfigModal/ServerConfigModal.tsx — drop the headers textarea and its supporting code.
clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx and its container — wire onSettingsChange / per-field callbacks through to the persistence path.
- Integration tests under
clients/web/src/test/integration/storage/ and …/mcp/ — round-trip a server with a populated settings node, and assert that settings-defined headers/metadata reach the wire on the first outbound request after load.
Acceptance criteria
- Settings entered in
ServerSettingsForm survive a page reload and a process restart via mcp.json.
- A server loaded from
mcp.json applies its settings.headers, timeouts, and metadata on its first outbound request — no need to open the settings form first.
- The Headers textarea is removed from
ServerConfigModal; headers are entered only in ServerSettingsForm.
- Existing
mcp.json files (no settings node, possibly with legacy transport-level headers) continue to load — via either an on-read migration into settings.headers or a documented compatibility path, decided in the PR.
- Round-trip and first-connect integration tests cover the new behavior.
Background
#1343 landed persistence of the server list to
~/.mcp-inspector/mcp.json, but the per-server settings edited byServerSettingsForm(clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx) are not persisted anywhere. The form is purely presentational — itsonSettingsChangecallbacks never reach disk, and there is no separate store for them incore/storage/or elsewhere.The shape that needs persisting is
InspectorServerSettings(core/mcp/types.ts:283):These should round-trip through
mcp.jsonand be applied from the first connection to a server — not just after the user opens the settings form.Note: pre-configured OAuth credentials (
clientId/clientSecret/scopes) are distinct from the tokens managed bycore/auth/oauth-storage.ts— the existing OAuth store only persists discovery metadata and tokens acquired at runtime, not user-entered client credentials.Proposal
Add a
settingsnode to each server entry inmcp.json, alongside the existing transport config:{ "mcpServers": { "my-server": { "type": "streamable-http", "url": "https://example.com/mcp", "settings": { "headers": [{ "key": "Authorization", "value": "Bearer xxx" }], "metadata": [{ "key": "tenant", "value": "acme" }], "connectionTimeout": 30000, "requestTimeout": 60000, "oauthClientId": "...", "oauthScopes": "read:tools write:tools" } } } }Headers consolidation — single source of truth in
settings.headersMCPServerConfig.headerson the SSE / streamable-http variants (core/mcp/types.ts:48,57) andInspectorServerSettings.headersrepresent the same concept. Today onlyMCPServerConfig.headersis wired to the wire (core/mcp/node/transport.ts:69,75,97andcore/mcp/remote/remoteClientTransport.ts:160);InspectorServerSettings.headersis defined but never read.Decision: collapse to
settings.headersso there is one place to edit headers and one place to read them.ServerConfigModal(clients/web/src/components/groups/ServerConfigModal/ServerConfigModal.tsxlines 399–411, plus theheadersTextform field, theparseHeadershelper, and the references toc.headers/config.headersinconfigToFormStateandbuildConfig).ServerSettingsForm's "Custom Headers" section as the sole UI for entering HTTP headers; itsonHeaderChange/onAddHeader/onRemoveHeadercallbacks now drive persistence intosettings.headers.core/mcp/node/transport.ts— replace reads ofsseConfig.headers/httpConfig.headerswith the mergedsettings.headersfor that entry.core/mcp/remote/remoteClientTransport.ts— sendsettingsto the remote server in the/api/mcp/connectbody (or have the remote server load it from its own copy of mcp.json by id).MCPServerConfig.headerson the type — either remove outright, or keep it as a one-shot read path that migrates intosettings.headerson first load of a legacy mcp.json. Recommendation goes in the implementation PR.This keeps the "ServerConfigModal = essentials, ServerSettingsForm = extra settings" split clean: simple servers (most of them) never need to open the settings form at all.
Applying the rest of the settings at connect time
connectionTimeout/requestTimeout— applied via the SDK transport / client options at connect.metadata— attached to each outgoing MCP request's_metafield (matches the form's hint text onServerSettingsForm.tsx:161).oauthClientId/oauthClientSecret/oauthScopes— fed into the OAuth flow as the pre-configured client credentials (separate from the runtime tokens thatcore/auth/oauth-storage.tsalready manages).Storing the OAuth client secret
oauthClientSecretis sensitive. Options to consider in the PR:mcp.jsonwith documented file-permission expectations (matches how stdioenvvalues are handled today).clientId/scopes.Files to touch
core/mcp/types.ts— extend the server entry shape with optionalsettings: InspectorServerSettings; deprecate/removeMCPServerConfig.headerson SSE / streamable-http.core/mcp/serverList.ts—mcpConfigToServerEntries/serverEntriesToMcpConfigmust round-trip thesettingsnode (and handle legacyheadersat the transport level if a migration path is chosen).core/mcp/node/transport.tsandcore/mcp/remote/remoteClientTransport.ts— read headers, timeouts, and metadata fromsettingsat connect time.core/mcp/remote/node/server.ts—/api/serversroutes (around lines 630–780) read/write the settings node;/api/mcp/connectreceives or resolves settings.clients/web/src/components/groups/ServerConfigModal/ServerConfigModal.tsx— drop the headers textarea and its supporting code.clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsxand its container — wireonSettingsChange/ per-field callbacks through to the persistence path.clients/web/src/test/integration/storage/and…/mcp/— round-trip a server with a populatedsettingsnode, and assert that settings-defined headers/metadata reach the wire on the first outbound request after load.Acceptance criteria
ServerSettingsFormsurvive a page reload and a process restart viamcp.json.mcp.jsonapplies itssettings.headers, timeouts, and metadata on its first outbound request — no need to open the settings form first.ServerConfigModal; headers are entered only inServerSettingsForm.mcp.jsonfiles (nosettingsnode, possibly with legacy transport-levelheaders) continue to load — via either an on-read migration intosettings.headersor a documented compatibility path, decided in the PR.