Skip to content

MCP source loses configured auth on next boot #416

@aryasaatvik

Description

@aryasaatvik

Problem

When executor.jsonc declares a remote MCP source with auth.kind: "header" or auth.kind: "oauth2", boot-time replay persists the source row to the DB without the auth field. Every tool call against that source then goes out unauthenticated.

After #364 (5dc4aa8f mcp: allow saving oauth2 source before sign-in), the failure mode got harder to spot: the row still lands in the catalog with auth.kind: "none" (defaulted from undefined input), so the source appears fine in the UI but is silently misauthenticated.

Related but distinct: #229 covers the UI-side flow where users can't supply a header auth at probe time. This issue is the inverse — auth that was successfully configured (file or UI) is lost across the next restart.

Reproduction

  1. Add an entry to executor.jsonc:
    {
      "kind": "mcp",
      "transport": "remote",
      "name": "PostHog",
      "endpoint": "https://mcp.posthog.com/mcp",
      "namespace": "posthog",
      "auth": {
        "kind": "header",
        "headerName": "Authorization",
        "secret": "secret-public-ref:posthog-api-key",
        "prefix": "Bearer "
      }
    }
  2. Provision the posthog-api-key secret (e.g. via keychain).
  3. Restart the local executor.
  4. Inspect the row:
    sqlite3 ~/.executor/data.db "SELECT name, config FROM mcp_source WHERE id = 'posthog';"
    config.auth.kind is "none" instead of "header".

Root cause

apps/local/src/server/config-sync.ts:123-132 — the remote-MCP branch of addSourceFromConfig builds the executor.mcp.addSource input without an auth field. Dates to 807df1de chore: fix data storage (#262), where boot-time file→DB replay was introduced and the field was missed.

The MCP plugin's write-back (packages/plugins/mcp/src/sdk/plugin.ts authToConfig) correctly serializes auth into the file when a source is added through the UI, so the file ends up right — but the inverse path on boot drops it. The forward and inverse translators live in different packages, which is why drift went unnoticed.

Fix

Translate the file-shape McpAuthConfig (header secret: "secret-public-ref:<id>") into the runtime McpConnectionAuth (header secretId: "<id>") and forward it.

Worth pairing with a small consolidation:

  • Move authToConfig out of the MCP plugin, alongside its new inverse mcpAuthFromConfig, into @executor/config next to the schema and the existing headerToConfigValue helper. The secret-public-ref: prefix stops leaking into plugin code.
  • Roundtrip test (toConfig ∘ fromConfig === identity, both ways) for every auth discriminant — adding a new kind without paired updates fails at test time, not at a user's boot.

PR with fix + consolidation incoming.

Acceptance

  • A header-auth or oauth2 entry in executor.jsonc survives restart with the same auth shape in the DB.
  • Roundtrip transform tests cover every auth discriminant.
  • UI add/remove flows unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions