History

Cloudflare: MCP-only preset with codemode=false, warn on missing opt-out (#1660)

Rhys Sullivan committed 2 months ago

Commit c11bef21 parent 73cbba97 files · +127 −10Browse files

Unified · Split

7 files
  1. .changeset/cloudflare-mcp-codemode-opt-out.md +8 −0
  2. e2e/scenarios/mcp-cloudflare-codemode-warning.test.ts +41 −0
  3. packages/plugins/mcp/src/react/AddMcpIntegration.tsx +16 −0
  4. packages/plugins/mcp/src/sdk/cloudflare-codemode.test.ts +42 −0
  5. packages/plugins/mcp/src/sdk/cloudflare-codemode.ts +15 −0
  6. packages/plugins/mcp/src/sdk/presets.ts +5 −2
  7. packages/plugins/openapi/src/sdk/presets.ts +0 −8
.changeset/cloudflare-mcp-codemode-opt-out.md+8−0
@@ -0,0 +1,8 @@
1+---
2+"@executor-js/plugin-mcp": patch
3+"@executor-js/plugin-openapi": patch
4+---
5+
6+**Cloudflare ships as MCP-only, with code mode opted out**
7+
8+The Cloudflare OpenAPI preset is gone from the default catalog; the MCP preset is the one Cloudflare entry. Its endpoint now pins `?codemode=false` because Cloudflare's MCP server otherwise hides the tool catalog behind a single code-execution tool, and executor already provides the code-execution surface. Hand-entered `mcp.cloudflare.com` URLs missing the opt-out get an inline warning in the add flow telling the user to append `?codemode=false`.
e2e/scenarios/mcp-cloudflare-codemode-warning.test.ts+41−0
@@ -0,0 +1,41 @@
1+// Cloudflare's MCP server defaults to code mode, which hides the tool catalog
2+// behind a single code-execution tool. Executor already provides the
3+// code-execution surface, so the add-MCP flow warns when a Cloudflare URL
4+// misses the `?codemode=false` opt-out. This guards the warning and that it
5+// clears once the opt-out is added; the probe's outcome is irrelevant to it.
6+import { Effect } from "effect";
7+
8+import { scenario } from "../src/scenario";
9+import { Browser, Target } from "../src/services";
10+
11+const urlField = "https://mcp.example.com";
12+const warning = "Cloudflare code mode is on";
13+
14+scenario(
15+ "MCP add flow · a Cloudflare URL without codemode=false gets the opt-out warning",
16+ {},
17+ Effect.gen(function* () {
18+ const target = yield* Target;
19+ const browser = yield* Browser;
20+ const identity = yield* target.newIdentity();
21+
22+ yield* browser.session(identity, async ({ page, step }) => {
23+ await step("Open the add-MCP flow", async () => {
24+ await page.goto("/integrations/add/mcp", { waitUntil: "networkidle" });
25+ await page.getByPlaceholder(urlField).waitFor();
26+ });
27+
28+ await step("Typing the Cloudflare URL without the opt-out shows the warning", async () => {
29+ await page.getByPlaceholder(urlField).fill("https://mcp.cloudflare.com/mcp");
30+ await page.getByText(warning).waitFor();
31+ });
32+
33+ await step("Appending ?codemode=false clears the warning", async () => {
34+ // The same placeholder survives the probe's re-render, so this resolves
35+ // in both the bare-URL and probed layouts.
36+ await page.getByPlaceholder(urlField).fill("https://mcp.cloudflare.com/mcp?codemode=false");
37+ await page.getByText(warning).waitFor({ state: "hidden" });
38+ });
39+ });
40+ }),
41+);
packages/plugins/mcp/src/react/AddMcpIntegration.tsx+16−0
@@ -16,6 +16,7 @@ import { · 15 unmodified lines
1616 CardStackEntryField,
1717 } from "@executor-js/react/components/card-stack";
1818 import { FloatActions } from "@executor-js/react/components/float-actions";
19+import { Info, InfoDescription, InfoTitle } from "@executor-js/react/components/info";
1920 import { Input } from "@executor-js/react/components/input";
2021 import { TagInput } from "@executor-js/react/components/tag-input";
2122 import {
@@ -38,6 +39,7 @@ import type { McpAuthMethodInput } from "../sdk/types"; · 16 unmodified lines
3839 import { probeMcpEndpoint, addMcpServer } from "./atoms";
3940 import { McpRemoteIntegrationFields } from "./McpRemoteIntegrationFields";
4041 import { mcpAuthMethodInputFromEditorValue, mcpWireAuthInput } from "./auth-method-config";
42+import { cloudflareNeedsCodemodeOptOut } from "../sdk/cloudflare-codemode";
4143 import { mcpPresets, type McpPreset } from "../sdk/presets";
4244
4345 // The remote add flow REGISTERS the server's declared auth methods through the
@@ -430,6 +432,20 @@ export default function AddMcpIntegration(props: { · 386 unmodified lines
430432 onRetry={handleProbe}
431433 />
432434
435+ {/* Cloudflare's MCP server defaults to code mode, which collapses the
436+ catalog into one code-execution tool; executor already provides
437+ code execution, so nudge the user toward the opt-out. */}
438+ {cloudflareNeedsCodemodeOptOut(state.url) && (
439+ <Info variant="warning">
440+ <InfoTitle>Cloudflare code mode is on</InfoTitle>
441+ <InfoDescription>
442+ By default Cloudflare&apos;s MCP server hides its tools behind a single
443+ code-execution tool. Add <code className="font-mono">?codemode=false</code> to the
444+ URL to get the full tool catalog.
445+ </InfoDescription>
446+ </Info>
447+ )}
448+
433449 {/* Authentication — declares the auth methods to register through the
434450 shared list editor. The credentials themselves (API key value /
435451 OAuth sign-in) are added from the integration's detail hub after
packages/plugins/mcp/src/sdk/cloudflare-codemode.test.ts+42−0
@@ -0,0 +1,42 @@
1+import { describe, expect, it } from "@effect/vitest";
2+
3+import { cloudflareNeedsCodemodeOptOut } from "./cloudflare-codemode";
4+import { mcpPresets } from "./presets";
5+
6+describe("cloudflareNeedsCodemodeOptOut", () => {
7+ it("flags the Cloudflare MCP endpoint without the opt-out", () => {
8+ expect(cloudflareNeedsCodemodeOptOut("https://mcp.cloudflare.com/mcp")).toBe(true);
9+ });
10+
11+ it("flags codemode set to anything but false", () => {
12+ expect(cloudflareNeedsCodemodeOptOut("https://mcp.cloudflare.com/mcp?codemode=true")).toBe(
13+ true,
14+ );
15+ });
16+
17+ it("accepts the opt-out regardless of other params or whitespace", () => {
18+ expect(cloudflareNeedsCodemodeOptOut("https://mcp.cloudflare.com/mcp?codemode=false")).toBe(
19+ false,
20+ );
21+ expect(
22+ cloudflareNeedsCodemodeOptOut("https://mcp.cloudflare.com/mcp?foo=bar&codemode=false"),
23+ ).toBe(false);
24+ expect(cloudflareNeedsCodemodeOptOut(" https://mcp.cloudflare.com/mcp?codemode=false ")).toBe(
25+ false,
26+ );
27+ });
28+
29+ it("ignores non-Cloudflare and unparseable endpoints", () => {
30+ expect(cloudflareNeedsCodemodeOptOut("https://mcp.linear.app/mcp")).toBe(false);
31+ expect(cloudflareNeedsCodemodeOptOut("https://bindings.mcp.cloudflare.com/sse")).toBe(false);
32+ expect(cloudflareNeedsCodemodeOptOut("mcp.cloudflare.com/mcp")).toBe(false);
33+ expect(cloudflareNeedsCodemodeOptOut("")).toBe(false);
34+ });
35+
36+ it("the shipped Cloudflare preset carries the opt-out", () => {
37+ const cloudflare = mcpPresets.find((preset) => preset.id === "cloudflare");
38+ expect(cloudflare?.transport).toBeUndefined();
39+ if (cloudflare === undefined || cloudflare.transport !== undefined) return;
40+ expect(cloudflareNeedsCodemodeOptOut(cloudflare.endpoint)).toBe(false);
41+ });
42+});
packages/plugins/mcp/src/sdk/cloudflare-codemode.ts+15−0
@@ -0,0 +1,15 @@
1+// Cloudflare's hosted MCP server (mcp.cloudflare.com) defaults to "code
2+// mode": it hides the tool catalog behind a single code-execution tool.
3+// Executor is itself a code-execution surface, so nesting code mode buries
4+// every real Cloudflare tool. The preset pins `?codemode=false`; hand-entered
5+// endpoints are flagged so the user adds the same opt-out.
6+
7+/** Whether `endpoint` targets Cloudflare's MCP server without the
8+ * `codemode=false` opt-out. Unparseable and non-Cloudflare URLs are fine. */
9+export const cloudflareNeedsCodemodeOptOut = (endpoint: string): boolean => {
10+ const trimmed = endpoint.trim();
11+ if (!URL.canParse(trimmed)) return false;
12+ const url = new URL(trimmed);
13+ if (url.hostname !== "mcp.cloudflare.com") return false;
14+ return url.searchParams.get("codemode") !== "false";
15+};
packages/plugins/mcp/src/sdk/presets.ts+5−2
@@ -134,8 +134,11 @@ export const mcpPresets: readonly McpPreset[] = [ · 133 unmodified lines
134134 id: "cloudflare",
135135 name: "Cloudflare",
136136 summary: "Workers, KV, D1, R2, and DNS management via MCP.",
137− url: "https://mcp.cloudflare.com/mcp",
138− endpoint: "https://mcp.cloudflare.com/mcp",
137+ // `codemode=false` opts out of Cloudflare's code mode, which replaces the
138+ // tool catalog with a single code-execution tool. Executor is already a
139+ // code-execution surface, so nesting it would hide every real tool.
140+ url: "https://mcp.cloudflare.com/mcp?codemode=false",
141+ endpoint: "https://mcp.cloudflare.com/mcp?codemode=false",
139142 icon: "https://integrations.sh/logo/cloudflare.com",
140143 },
141144 {
packages/plugins/openapi/src/sdk/presets.ts+0−8
@@ -93,14 +93,6 @@ const openApiOnlyPresets: readonly OpenApiPreset[] = [ · 92 unmodified lines
9393 icon: "https://integrations.sh/logo/vercel.com",
9494 featured: true,
9595 },
96− {
97− id: "cloudflare",
98− name: "Cloudflare",
99− summary: "DNS, workers, pages, R2, and security rules.",
100− url: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json",
101− icon: "https://integrations.sh/logo/cloudflare.com",
102− featured: true,
103− },
10496 {
10597 id: "neon",
10698 name: "Neon",

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t