Skip to content

Persist per-server settings (headers, metadata, timeouts, OAuth credentials) to mcp.json #1352

@cliffhall

Description

@cliffhall

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.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.tsmcpConfigToServerEntries / 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Issues and PRs for v2

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions