Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/mcp-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ const DEFAULT_CLOUDFLARE_REMOTE_MCP_CATALOG: McpCatalogEntry[] = [
auth_type: "oauth",
knownHosts: ["browser.mcp.cloudflare.com"],
},
{
id: "cf-portal",
name: "Cloudflare Portal",
description:
"Cloudflare internal MCP portal — Backstage catalog, Jira, GitLab, Sentry, Elasticsearch, Wiki, Prometheus, and more. Requires Cloudflare SSO.",
url: "https://portal.mcp.cfdata.org/mcp",
setupGuide:
"Connect with OAuth via Cloudflare Access SSO. Per-user Dynamic Client Registration; access token refreshes automatically.",
auth_type: "oauth",
// cf-mcp.cloudflareaccess.com is the OAuth dance host
// (authorization_endpoint / token_endpoint / registration_endpoint).
knownHosts: ["portal.mcp.cfdata.org", "cf-mcp.cloudflareaccess.com"],
},
// NOTE: cf-portal (https://portal.mcp.cfdata.org/mcp) was tested and is
// intentionally NOT in this catalog. Its OAuth authorize endpoint
// (cf-mcp.cloudflareaccess.com) only accepts redirect URIs pointing at
// loopback (http://127.0.0.1:*) or Cloudflare-managed domains (e.g.
// seal-nightly.cloudflare.dev). DCR succeeds, but the authorize step
// returns "Redirect URI not allowed by application configuration"
// for any other host. This is by design — cf-portal treats third-party
// hosted apps as untrusted clients. OpenCode/Cursor/Claude Desktop work
// because they run locally on 127.0.0.1; Dodo runs on a Worker, which
// can't bind 127.0.0.1 from the user's POV. See OpenCode's
// `McpOAuthProvider.redirectUrl` for reference. To use cf-portal tools
// in Dodo, run a local mcp-remote proxy and add it as a static-headers
// integration.
];

export const DEPLOY_MCP_CATALOG_CONFIG: McpCatalogConfig = {
Expand Down
14 changes: 5 additions & 9 deletions test/mcp-catalog-unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ import { describe, expect, it } from "vitest";
import { MCP_CATALOG } from "../src/mcp-catalog";

describe("MCP_CATALOG", () => {
it("includes cf-portal as an OAuth catalog entry", () => {
it("does NOT include cf-portal — its OAuth authorize endpoint rejects non-loopback redirect URIs", () => {
// Documented in mcp-catalog.ts. cf-portal works for OpenCode/Cursor/etc
// because those clients use http://127.0.0.1:PORT/... redirect URIs.
// A hosted Worker can't do that, and cf-portal rejects everything else.
const entry = MCP_CATALOG.find((e) => e.id === "cf-portal");
expect(entry).toBeDefined();
expect(entry?.url).toBe("https://portal.mcp.cfdata.org/mcp");
expect(entry?.auth_type).toBe("oauth");
// Both the MCP host AND the Cloudflare Access OAuth dance host must be
// in knownHosts — otherwise `isHostAllowed()` rejects the start-auth
// call and the token endpoint round-trip during the dance.
expect(entry?.knownHosts).toContain("portal.mcp.cfdata.org");
expect(entry?.knownHosts).toContain("cf-mcp.cloudflareaccess.com");
expect(entry).toBeUndefined();
});

it("includes browser-rendering as an OAuth catalog entry", () => {
Expand Down
Loading