Skip to content

feat: re-forward live MCP servers per turn with OAuth mapping#19

Merged
justin-carper merged 1 commit into
feat/auto-session-reusefrom
feat/dynamic-mcp-forwarding
Jun 11, 2026
Merged

feat: re-forward live MCP servers per turn with OAuth mapping#19
justin-carper merged 1 commit into
feat/auto-session-reusefrom
feat/dynamic-mcp-forwarding

Conversation

@justin-carper

Copy link
Copy Markdown
Collaborator

Problem

The plugin's config hook snapshots opencode's MCP server set once at startup, bakes it into the provider options, and never re-reads it. opencode supports enabling/disabling MCP servers live mid-session, but those toggles never reached the Cursor agent — whatever was enabled at launch was frozen until restart.

What changed

Dynamic per-turn re-forwarding (chat.params hook)

  • Fetches the live set via client.mcp.status() (runtime truth: connected/disabled/needs_auth) + client.config.get() (launch specs)
  • Forwards only currently-connected servers, so mid-session enable/disable propagates
  • Best-effort: any failure leaves the static startup snapshot in place
  • Opt-out (forwardMcp: false) and non-cursor models untouched

Pool-aware change detection

  • TranscriptRecord gains mcpHash; mcpServersFingerprint() hashes the forwarded set (order-independent)
  • On a continuation, the pooled agent is resumed only if the MCP set is unchanged — otherwise a fresh Agent.create runs (a resumed agent keeps its original servers)

OAuth mapping

  • Remote oauth client registration (clientId/clientSecret/scope) → Cursor SDK auth { CLIENT_ID, CLIENT_SECRET?, scopes[] }, so the agent runs its own OAuth flow (and refreshes its own token)
  • opencode's access token never lands in config.mcp, so servers needing OAuth with no shareable clientId (dynamic registration / needs_auth) are skipped and surfaced via a one-time warning toast instead of forwarding a spec that 401s
Server Result
plain remote / oauth:false forwarded
OAuth + configured clientId forwarded with auth → agent's own OAuth + refresh
OAuth dynamic-registration (no clientId) skipped + one-time toast
live needs_auth w/o clientId skipped + toast

Tests

+16 tests (174 total, all passing): status-filtered translation, OAuth clientId→auth mapping + scope splitting, dynamic-reg skip, oauth:false passthrough, findUnshareableOAuthServers (config + live status), per-turn injection (connect/disconnect/non-cursor/opt-out), toast emission + once-only dedupe, mcpServersFingerprint stability, pool mcpHash round-trip.

tsc --noEmit clean · vitest run 174/0 · tsup build success.

Note

Stacked on feat/auto-session-reuse (#18) — the MCP change-detection reuses its fingerprint/pool machinery. Targeting that branch as base; rebase onto main once #18 merges.

The config hook only snapshots opencode's MCP set once at startup, so
mid-session enable/disable never reached the Cursor agent. Re-forward the
live set from chat.params using client.mcp.status() (runtime truth) +
client.config.get() (launch specs), and force a fresh Agent.create when the
forwarded set changes between turns (a resumed agent keeps its original
servers).

Map remote OAuth client registration (clientId/clientSecret/scope) onto the
Cursor SDK's auth block so the agent runs its own OAuth flow. opencode's
access token never lands in config.mcp, so servers needing OAuth without a
shareable clientId (dynamic registration / needs_auth) are skipped and the
user is notified via a one-time toast instead of forwarding a spec that 401s.
@justin-carper justin-carper merged commit 6c089c9 into feat/auto-session-reuse Jun 11, 2026
6 checks passed
@justin-carper justin-carper deleted the feat/dynamic-mcp-forwarding branch June 11, 2026 22:10
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