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
- Add an entry to
executor.jsonc:
- Provision the
posthog-api-key secret (e.g. via keychain).
- Restart the local executor.
- 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.
Problem
When
executor.jsoncdeclares a remote MCP source withauth.kind: "header"orauth.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 withauth.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
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 " } }posthog-api-keysecret (e.g. via keychain).config.auth.kindis"none"instead of"header".Root cause
apps/local/src/server/config-sync.ts:123-132— the remote-MCP branch ofaddSourceFromConfigbuilds theexecutor.mcp.addSourceinput without anauthfield. Dates to807df1de 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(headersecret: "secret-public-ref:<id>") into the runtimeMcpConnectionAuth(headersecretId: "<id>") and forward it.Worth pairing with a small consolidation:
authToConfigout of the MCP plugin, alongside its new inversemcpAuthFromConfig, into@executor/confignext to the schema and the existingheaderToConfigValuehelper. Thesecret-public-ref:prefix stops leaking into plugin code.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
executor.jsoncsurvives restart with the sameauthshape in the DB.