fix(mcp): remove cf-portal catalog entry — loopback-only redirect URI policy - #89
Merged
Merged
Conversation
…policy) After end-to-end debugging via chrome-devtools and worker tail, then cross-referencing OpenCode's source, the root cause is clear: cf-portal's OAuth authorize endpoint (cf-mcp.cloudflareaccess.com) only accepts redirect URIs that point at loopback (http://127.0.0.1:PORT/...) or pre-allowlisted Cloudflare-managed domains (e.g. seal-nightly.cloudflare.dev). DCR succeeds for any host, but the /authorize endpoint then rejects non-allowlisted redirect URIs with: error=invalid_request error_description=Redirect URI not allowed by application configuration This is by design — treating third-party hosted apps as untrusted clients is a sensible default. OpenCode/Cursor/Claude Desktop work because they run locally and spin up an http://127.0.0.1:19876 callback server. Dodo runs on a Worker; we can't bind 127.0.0.1 from the user's perspective. Remove the catalog entry to avoid the UX footgun of users clicking "Connect with OAuth" only to land on an unrecoverable error. The comment in mcp-catalog.ts documents the reasoning so the next person to come looking doesn't re-walk the same path. To use cf-portal tools in Dodo, the workaround is to run a local `mcp-remote` proxy and configure it as a static-headers MCP integration. (Not in scope here.) The supporting infrastructure (OAuth start/poll routes, popup UX, stale-registration cleanup, getAgentByName fix, /agents/oauth/callback path, integrations layout fix) remains in place and is still useful for any future OAuth-MCP entry that DOES allow remote redirect URIs. Tests: 817/817 pass. Typecheck clean. beep-boop-🤖
This was referenced May 26, 2026
jonnyparris
added a commit
that referenced
this pull request
May 26, 2026
…#93) Two cleanup fixes for the OAuth integrations UI: 1. Remove GitHub from CORE_MCP_CATALOG (api.githubcopilot.com/mcp/) GitHub Copilot's MCP server returns 'Incompatible auth server: does not support dynamic client registration' when the SDK-managed OAuth path attempts DCR. The catalog entry was advertising a Connect with OAuth button that could never succeed. Users who want GitHub MCP should add a github_token secret instead — the existing static- headers path already handles it and the UI hides any GitHub catalog suggestion when a github_token is present. The fallback github_token-hides-suggestion code path in dodo-settings.js (`if(cat.id==="github"&&hasGithubToken)return`) becomes dead with this change but is harmless to leave in place. 2. Always render orphaned OAuth servers Before this commit, renderOAuthCard was only called for catalog entries with auth_type='oauth' that had a matching SDK-managed hub-DO record. When a catalog entry was removed (e.g. cf-portal in PR #89) but the user already had a failed/stuck server record from a previous attempt, the record was invisible in the UI → no way to clear it without poking /api/mcp/delete-auth manually. Fix: track which oauthServers got rendered alongside a catalog entry; render any unmatched ones via the new renderOrphanedOAuthCard with a 'Clear' button (calls existing /api/mcp/delete-auth). Also improve renderOAuthCard for non-ready states: - 'Refresh' button only shows for state=ready (it was retrying against a broken config in other states, which was useless) - The remove button reads 'Disconnect' when ready, 'Clear' otherwise. Tests: 832/832 pass. Catalog test count updated to reflect the removed github entry; new assertions confirm github and cf-portal are NOT in the catalog so the next person doesn't accidentally re-add them without reading the history. Stale UI rendering of an orphaned cf-portal 'authenticating' entry that surfaced this issue was already wiped manually via /api/mcp/delete-auth. beep-boop-🤖
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What I learned
After 6 PRs of trying to make Dodo connect to cf-portal via OAuth, and finally cross-referencing OpenCode's source (
packages/opencode/src/mcp/oauth-provider.ts):OpenCode (and Cursor, Claude Desktop, etc) work with cf-portal because they're local clients using loopback redirect URIs. They spin up an HTTP server on 127.0.0.1:19876 to catch the callback.
cf-portal's authorize endpoint (
cf-mcp.cloudflareaccess.com) enforces an allowlist:http://127.0.0.1:*/...— accepted (loopback)seal-nightly.cloudflare.dev) — accepted (allowlisted)Redirect URI not allowed by application configurationThis is sensible OAuth security: treat third-party hosted apps as untrusted clients, only allow callbacks to locally-controlled URIs or known Cloudflare property.
What this means for Dodo
A hosted Worker can't bind
127.0.0.1from the user's perspective. cf-portal as an OAuth target from Dodo is infeasible by design, not by bug.Fix
Remove the cf-portal catalog entry. The comment in
mcp-catalog.tsdocuments the reasoning. Test updated to assert the entry's absence.Workaround for cf-portal access in Dodo (out of scope)
Run a local
mcp-remoteproxy on the user's machine that does the loopback OAuth, then expose it to Dodo as a static-headers MCP integration. Defeats the point of Dodo being hosted, but it's how Cursor users do it. Not implementing this here.What stays in place
All the supporting infrastructure from PRs #82–#88 remains useful for any future OAuth-MCP entry that allows remote redirect URIs:
getAgentByNamefix (workerd#2240)The browser-rendering and github OAuth entries are still in the catalog. github has its own issue (
Incompatible auth server: does not support dynamic client registration— needs a pre-registered client_id which we don't expose yet). browser-rendering has a separate dedicated UI section that wasn't testable in this scope.Verification
npm run typecheckcleannpx vitest run817/817 passbeep-boop-🤖