Repository navigation
Description
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:
{
"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.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.tsxlines 399–411, plus theheadersTextform field, theparseHeadershelper, and the references toc.headers/config.headersinconfigToFormStateandbuildConfig). - Keep
ServerSettingsForm's "Custom Headers" section as the sole UI for entering HTTP headers; itsonHeaderChange/onAddHeader/onRemoveHeadercallbacks now drive persistence intosettings.headers. - Update transport construction to source headers from the settings node:
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).
- Deprecate
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
oauthClientSecret is sensitive. Options to consider in the PR:
- Persist in
mcp.jsonwith documented file-permission expectations (matches how stdioenvvalues 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 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.- Integration tests under
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
- Settings entered in
ServerSettingsFormsurvive a page reload and a process restart viamcp.json. - A server loaded from
mcp.jsonapplies itssettings.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 inServerSettingsForm. - Existing
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. - Round-trip and first-connect integration tests cover the new behavior.
Activity
docs(spec): reflect settings persistence + patch-PUT contract in v2_s…
feat(servers): persist per-server settings to mcp.json (#1352) (#1353)
feat(servers): flatten mcp.json settings to top-level fields (#1358) (#…