Skip to content

feat(mcp): add cf-portal OAuth catalog entry + Connect UI - #83

Merged
jonnyparris merged 1 commit into
mainfrom
oauth/phase-1-user-control
May 26, 2026
Merged

feat(mcp): add cf-portal OAuth catalog entry + Connect UI#83
jonnyparris merged 1 commit into
mainfrom
oauth/phase-1-user-control

Conversation

@jonnyparris

Copy link
Copy Markdown
Owner

What

Adds first-class OAuth-MCP support for cf-portal — and incidentally fixes the UX for github + browser-rendering OAuth too. "Connect with OAuth" button now appears in the integrations panel for any OAuth catalog entry.

Why the 7-phase plan collapsed to one PR

While reading the existing code, found that the Agents SDK's MCPClientManager already handles every "hard" thing the original plan called for:

  • PKCE S256 ✓
  • Dynamic Client Registration ✓
  • Single-use refresh token + in-DO mutex (Kenny Johnson's docs were the inspiration for the SDK fix) ✓
  • Per-user state in the CodingAgent hub DO ✓
  • OAuth callback wiring (/agents/oauth/callback) ✓
  • Tool federation across session DOs (loadOAuthToolsFromHub) ✓

The backend HTTP routes (/api/mcp/start-auth, /api/mcp/delete-auth, /api/mcp/refresh-state, /agents/* callback) already existed. The host allowlist already pre-allowed catalog knownHosts. Even the github catalog entry was wired with auth_type: "oauth" and a knownHost.

What was actually missing:

  1. A cf-portal entry in the catalog
  2. A frontend button to call /api/mcp/start-auth and open the popup
  3. An endpoint to list which OAuth servers the user has connected

That's the whole PR.

Changes

Backend (44 lines):

  • src/mcp-catalog.ts — register cf-portal with knownHosts: [portal.mcp.cfdata.org, cf-mcp.cloudflareaccess.com] so the OAuth dance (auth/token/registration endpoints) and the MCP server itself are both implicitly allowed without admin allowlisting.
  • src/index.ts — new GET /api/mcp/oauth-servers proxies stub.getMcpServers() from the user's hub DO, with tool-count rollup.

Frontend (106 lines):

  • public/js/dodo-settings.jsloadIntegrations() now also fetches /api/mcp/oauth-servers in parallel; new renderOAuthCard() shows state-aware actions ("Connect with OAuth" / "Refresh" / "Disconnect"); connectOAuthCatalog() opens a 560×720 popup and polls for closure before refreshing the list.

Tests (45 lines):

  • test/mcp-catalog-unit.test.ts — catalog shape assertions.
  • test/dodo.test.ts/api/mcp/oauth-servers happy path + auth gate.

Verification

  • npm run typecheck: clean
  • npx vitest run: 817/817 pass (no regressions; +6 new tests)
  • No changes to coding-agent.ts, user-control.ts, or any DO migrations

Manual test plan (post-deploy)

  1. Open Dodo settings → Integrations
  2. Find "Cloudflare Portal" in the suggestions, click "Connect with OAuth"
  3. Complete CF SSO in popup
  4. Popup closes, integrations list refreshes, cf-portal shows "Connected — N tools"
  5. In a new coding session, the cf-portal tools (backstage_*, jira-cli_*, etc.) should appear in tool listing
  6. After ≥20 min, calling a cf-portal tool should still work (SDK handles refresh transparently)
  7. Click "Disconnect" → cf-portal disappears from the connected list

Plan reference

memory/workload/plans/2026-05-26-dodo-cf-portal-oauth.md — original 7-phase plan. Most of phases 1–5 turned out to already exist in coding-agent.ts. Phase 6 (catalog) and Phase 4 (frontend popup) are what this PR delivers.

beep-boop-🤖

Adds end-to-end OAuth-MCP onboarding for cf-portal (and improves the
generic OAuth-MCP UX for github + browser-rendering as a side effect).

The Agents SDK's MCPClientManager already handles PKCE + DCR + token
refresh + the in-DO mutex that prevents Kenny Johnson's documented
re-auth loop — all wired up via `this.mcp` on the per-user CodingAgent
hub DO. What was missing was the UI to trigger `addMcpServer()` from
the integrations panel.

Backend:
- src/mcp-catalog.ts — register `cf-portal` with
  `knownHosts: [portal.mcp.cfdata.org, cf-mcp.cloudflareaccess.com]`
  so the OAuth dance hits authorization/token/registration endpoints
  without admin-allowlisting them.
- src/index.ts — new `GET /api/mcp/oauth-servers` returns the user's
  Agents-SDK-managed MCP servers (id/name/url/state/toolCount) so the UI
  can render "Connected — N tools" vs "Not connected" for OAuth
  catalog entries.

Frontend:
- public/js/dodo-settings.js — `loadIntegrations()` now fans out to
  `/api/mcp/oauth-servers` in parallel; `renderIntegrations()` routes
  OAuth catalog entries through a new `renderOAuthCard()` that shows
  "Connect with OAuth" or "Disconnect / Refresh" based on state.
  `connectOAuthCatalog()` opens the OAuth provider in a popup, polls
  the popup's closed state, and refreshes the list.

Tests:
- test/mcp-catalog-unit.test.ts — catalog entries carry the right
  `auth_type` and `knownHosts`.
- test/dodo.test.ts — two new tests for `/api/mcp/oauth-servers`
  (shape + auth gate).

817/817 pass. No changes to coding-agent.ts or user-control.ts — the
existing per-user OAuth hub pattern is reused as-is.

Plan: memory/workload/plans/2026-05-26-dodo-cf-portal-oauth.md
@jonnyparris
jonnyparris merged commit 0526165 into main May 26, 2026
1 check passed
@jonnyparris
jonnyparris deleted the oauth/phase-1-user-control branch May 26, 2026 10:30
jonnyparris added a commit that referenced this pull request May 26, 2026
…rst UI layout (#84)

Two follow-ups to PR #83:

1. **Catalog seed was first-run-only.** The cf-portal entry I added to
   `mcp-catalog.ts` never appeared in `/api/mcp-catalog` because the
   endpoint reads from UserControl's `approved_mcps` table, which is
   seeded once when the table is empty. Existing DOs (i.e. mine, and
   everyone else's) had been seeded back when the catalog was just
   browser-rendering + github, so the new entry never propagated.

   Fix: use `INSERT OR IGNORE` on every onStart, keyed by `mcp_url`
   (PRIMARY KEY). New catalog entries flow through; admin edits and
   soft-deletes on existing rows are preserved.

2. **Integrations buried below Skills + Tools.** Settings panel order
   was Skills (~50 entries) → Tools (~30 entries) → Integrations.
   Moved Integrations above both. Skills and Tools are now wrapped in
   collapsed `<details>` so they don't dominate the scroll.

No tests added — the seed behaviour is exercised implicitly by every
test that hits `/api/mcp-catalog` (which seeds via onStart). The UI
move is markup-only.

beep-boop-🤖
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