Skip to content

fix(executor): repair MCP auth round-trip across config-sync#709

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

fix(executor): repair MCP auth round-trip across config-sync#709
aryasaatvik wants to merge 2 commits intoRhysSullivan:mainfrom
aryasaatvik:fix/mcp-config-sync-auth-roundtrip

Conversation

@aryasaatvik
Copy link
Copy Markdown
Contributor

Summary

  • Boot replay: config-sync.ts was forwarding endpoint/headers/queryParams from executor.jsonc into executor.mcp.addSource(...) but silently dropping the auth block — and after the credential-binding cutover, the same call also lacked the new mandatory credentialTargetScope. Result: every authenticated remote MCP source from a config file booted unauthenticated, the SSE handshake fired without Authorization, and upstreams rejected. Adds an inline mcpAuthFromConfig converter (file secret-public-ref:<id> → input secretId) and threads auth + credentialTargetScope through the MCP/OpenAPI/GraphQL add calls.
  • UI write-back: mcp.updateSource updated the DB but never executor.jsonc, so the next boot replayed stale or empty auth and overwrote the DB's good state. Mirrors the existing addSource/removeSource pattern. Naive write-back would silently drop slot-form auth (authToConfig only knows the secretId/connectionId input shape), so adds an inputFormFromStored helper that resolves secretSlot/connectionSlot and slot-bound headers/queryParams back to the input form via the source's credential_binding rows before rendering the file entry.
  • Tests: integration coverage for boot replay across all three auth kinds (header secret-ref, oauth2, none) and a stub-sink unit test for the writeback payload.

Test plan

  • bun run typecheck — 33/33
  • bunx --bun vitest run apps/local/src/server/config-sync.test.ts — 3/3
  • bunx --bun vitest run -t "writes auth changes" packages/plugins/mcp/src/sdk/plugin.test.ts — 1/1
  • Live executor web against a real executor.jsonc with mixed header-bearer and OAuth2 MCP sources: 10/10 sources sync, tool calls return real data through both paths.
  • Reviewer: confirm no regression in existing add/remove flows (those already wrote through to the file; this PR only adds the missing update-source writeback and the boot-replay auth threading).

Boot-time addSourceFromConfig was forwarding endpoint/headers/queryParams
from executor.jsonc into executor.mcp.addSource(...) but silently
dropping the auth block. Keychain-backed remote MCP sources started
unauthenticated on every boot, the SSE handshake fired with no
Authorization header, and upstreams rejected with the
"Failed connecting via sse" error class.

Add an inline mcpAuthFromConfig converter that strips the
secret-public-ref: prefix from file-shape auth into runtime
McpConnectionAuth.secretId, and thread it through the MCP remote
addSource call. none and oauth2 are identity passthroughs (oauth2's
runtime extras are optional, so the file shape is structurally valid as
runtime auth).

New config-sync.test.ts covers all three auth kinds against an
unreachable endpoint, which still persists the source row so the stored
auth shape can be asserted without seeding secrets or running an MCP
server.
updateSource wrote new auth to the DB but never to executor.jsonc, so
the next boot replayed the file's stale auth (or none) and silently
overwrote the DB. The smoking gun: after signing into Sentry via the
UI, the mcp-oauth2-sentry connection survived in keychain with valid
tokens, but the next reboot cleared the source row's auth_connection_id
because the file never reflected the link.

Mirror the existing addSource/removeSource pattern by calling
configFile.upsertSource(toMcpConfigEntry(...)) after putSource. The
existing toMcpConfigEntry/authToConfig path handles the runtime->file
conversion (writes secret-public-ref:<id> back), so the file stays
authoritative for boot replay.

Test exercises updateSource against a stub ConfigFileSink and asserts
the upsert payload uses secret-public-ref:<new-id> in file shape.
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.

1 participant