Skip to content

Proxy MCP OAuth callbacks on behalf of connected squadrons#25

Open
mlund01 wants to merge 1 commit into
mainfrom
oauth-proxy-command-center
Open

Proxy MCP OAuth callbacks on behalf of connected squadrons#25
mlund01 wants to merge 1 commit into
mainfrom
oauth-proxy-command-center

Conversation

@mlund01
Copy link
Copy Markdown
Owner

@mlund01 mlund01 commented Apr 18, 2026

Summary

  • New public GET /oauth/callback endpoint lets IdPs redirect to command center. The handler looks up the owning squadron via the OAuth state value and forwards the code/state over the WS bridge.
  • New POST /api/instances/{id}/mcp/{name}/oauth/start for browser-initiated MCP logins. Returns the authorization URL for the browser to window.open.
  • New GET /api/instances/{id}/notifications SSE stream surfaces per-instance ephemeral events. Toasts fire on oauth_completed / oauth_failed via a useInstanceNotifications hook mounted in AppLayout.
  • Tools page gains a Connect button on MCP rows.

Paired with:

Multi-tenancy

The /oauth/callback endpoint is shared across all connected squadrons — routing is by state (cryptographically random, per-flow). No per-squadron URL needed, so DCR-registered redirect URIs stay fixed.

TEMP notice

go.mod has a local replace for squadron-wire until the sibling PR merges and a tag is cut. Will be removed + bumped before merge.

Test plan

  • go test ./... green (new internal/oauth suite + existing suites)
  • Callback endpoint smoke-tested: unknown state returns the failure page
  • End-to-end with a real MCP server: click Connect in UI → IdP → callback → squadron persists token
  • Verify toast fires on completion and callback tab auto-closes

Command center now hosts a public, unauthenticated OAuth callback endpoint
that IdPs can redirect to. When a squadron kicks off an MCP login it
reserves a flow keyed by the OAuth `state` value; the callback handler
looks up the owning squadron and forwards the code/state back over the
WS bridge. Motivation: squadrons on remote hosts can't bind a loopback
callback, and some IdPs require pre-registered HTTPS redirect URIs.

New endpoints:

- GET /oauth/callback (on outerMux, no auth — IdPs don't carry session
  cookies; security comes from the unguessable single-use state value).
- POST /api/instances/{id}/mcp/{name}/oauth/start — browser-initiated
  MCP login. Forwards StartMCPLogin to squadron, returns the authorization
  URL for the browser to window.open.
- GET /api/instances/{id}/notifications — per-instance SSE stream of
  ephemeral events. Currently delivers oauth_completed / oauth_failed
  notifications; structured to accept future event types.

Hub additions:

- PendingFlows (internal/oauth/flows.go) — state→{instanceID, mcpName}
  store with 10-minute TTL and tests.
- Notifications (internal/hub/notifications.go) — per-instance fan-out
  with no buffering (ephemeral toasts only).
- Connection dispatch handles inbound OAuthRegisterFlow from squadrons.

Frontend:

- api.startMcpOauth wraps the start endpoint.
- subscribeInstanceNotifications SSE helper.
- useInstanceNotifications hook mounted in AppLayout surfaces toasts via
  Sonner on oauth_completed / oauth_failed.
- Tools page gains a Connect button on MCP rows.

TEMP: go.mod has a local replace pointing at ../squadron-wire for the
new protocol message types. Revert + bump once squadron-wire tags a new
version (see sibling PR).
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