Skip to content

fix(local): replay MCP auth during config sync#423

Open
aryasaatvik wants to merge 7 commits intoRhysSullivan:mainfrom
aryasaatvik:codex/fix-mcp-config-sync-auth
Open

fix(local): replay MCP auth during config sync#423
aryasaatvik wants to merge 7 commits intoRhysSullivan:mainfrom
aryasaatvik:codex/fix-mcp-config-sync-auth

Conversation

@aryasaatvik
Copy link
Copy Markdown
Contributor

@aryasaatvik aryasaatvik commented Apr 28, 2026

Fixes #416.

Summary

MCP source auth can now round-trip between executor.jsonc and the local runtime without being dropped during config sync or source updates.

This fixes the boot-time regression where authenticated MCP sources were replayed into storage as auth.kind: "none", causing remote MCP servers to fail with 401s after restart.

Changes

  • Adds shared config/runtime transforms for MCP auth and plugin headers in @executor/config.
  • Replays MCP source auth during local config sync.
  • Persists MCP source auth updates back to the config file when a config sink is available.
  • Adds regression coverage for header auth, OAuth2 auth, none auth, and transform roundtrips.

Tests

  • bun --cwd packages/core/config test
  • bun --cwd packages/core/config typecheck
  • bun --cwd packages/plugins/mcp test
  • bun --cwd packages/plugins/mcp typecheck
  • bun --cwd apps/local test src/server/config-sync.test.ts
  • bun --cwd apps/local typecheck

The runtime-shape auth type lived in @executor/plugin-mcp while its
file-shape counterpart (McpAuthConfig) lived in @executor/config.
Splitting the pair across packages made the plugin import
SECRET_REF_PREFIX back from @executor/config just to serialize auth —
an inversion that made the forward and inverse translators hard to
keep in sync.

Co-locate both shapes next to each other in @executor/config/schema.ts.
The plugin re-exports McpConnectionAuth from its types barrel so
existing downstream consumers (apps/local migrate-connections,
apps/cloud migrate-mcp-connections) are unchanged.

No behavior change.
Consolidates every file↔runtime conversion into a single module
(`packages/core/config/src/transform.ts`) holding four paired functions:

  headerToConfigValue   ↔ headerFromConfigValue
  headersToConfigValues ↔ headersFromConfigValues
  mcpAuthToConfig       ↔ mcpAuthFromConfig

`mcpAuthToConfig` moves out of the MCP plugin (where it duplicated the
file format and had to import SECRET_REF_PREFIX back from the config
package) and joins its inverse here. The forward header translators
move out of `sink.ts`; `sink.ts` re-exports them for existing
consumers.

A roundtrip test suite exercises every discriminant of each union and
asserts `toConfig ∘ fromConfig === identity` and vice versa. A new
auth kind added on one side without its pair now fails the roundtrip
at test time rather than at a user's boot.
Boot-time sync in apps/local silently dropped `auth` when replaying
remote MCP sources from executor.jsonc. A file with
`auth.kind = "header"` (or any auth other than "none") round-tripped
through syncFromConfig would land in the DB with no auth, because the
mcp.addSource call in `addSourceFromConfig` omitted the field. The
plugin itself serializes auth correctly on write-back, so the drop
was asymmetric: UI/updateSource writes reached the file, but next
boot wiped the runtime state.

Wires the file→runtime transform (`mcpAuthFromConfig`) added in the
preceding commit into the remote MCP branch, and swaps the local
header-translation helper for the shared `headersFromConfigValues`.
The executor parameter is narrowed from `LocalExecutor` to
`Pick<LocalExecutor, "scopes" | "openapi" | "graphql" | "mcp">` so
tests can construct a minimal executor without pulling the full
plugin set.

Integration test covers the regression end-to-end: write an
executor.jsonc with header auth, run syncFromConfig against an
in-memory executor, assert `mcp.getSource(...)` returns the stored
row with the runtime `secretId` (bare, no `secret-public-ref:`
prefix). `kind: "none"` is covered alongside for parity; oauth2
replay is covered at the transform level in @executor/config where
its runtime shape is identity, and the integration path would require
seeding a Connection fixture that adds more setup than the coverage
warrants.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP source loses configured auth on next boot

1 participant