Skip to content

Commit 22b5946

Browse files
authored
fix(cloudflare): use Worker-safe validation for MCP (#236)
## Summary - configure the Cloudflare MCP client to use the MCP SDK's `CfWorkerJsonSchemaValidator` - add a regression test that disables string-based code generation while validating advertised MCP tool output schemas ## Root cause The MCP SDK client defaults to AJV for JSON Schema validation. When Cloudflare's MCP server advertises tool `outputSchema` values, AJV compiles them with `new Function(...)`. Cloudflare Workers disallow runtime code generation from strings, so credential validation and MCP requests fail with: ```text Cloudflare MCP request failed: Code generation from strings disallowed for this context ``` The SDK's Cloudflare-specific validator uses `@cfworker/json-schema` and performs validation without `eval` or `new Function`. ## Impact Cloudflare-hosted Open Connector deployments can validate Cloudflare MCP credentials and invoke its tools. Node deployments retain the same behavior. Because the provider is currently broken on Cloudflare in v1.3.3, could this be included in a patch release after review? ## Validation - `node scripts/typecheck.ts src scripts-all examples` - `vitest run` (61 files, 590 tests) - dedicated regression test with `Function` disabled and MCP `outputSchema` values present
1 parent a332575 commit 22b5946

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

src/providers/cloudflare_mcp/executors.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { describe, expect, it, vi } from "vitest";
1+
import { afterEach, describe, expect, it, vi } from "vitest";
22
import { cloudflareMcpActionHandlers, credentialValidators } from "./executors.ts";
33

4+
afterEach(() => {
5+
vi.unstubAllGlobals();
6+
});
7+
48
describe("Cloudflare MCP executors", () => {
59
it("sends bearer auth and maps search to the official MCP tool", async () => {
610
const calls: Array<Record<string, unknown>> = [];
@@ -39,6 +43,16 @@ describe("Cloudflare MCP executors", () => {
3943
expect(oauthResult?.metadata?.mcpTools).toEqual(["docs", "execute", "search"]);
4044
});
4145

46+
it("validates MCP tool schemas when string code generation is unavailable", async () => {
47+
vi.stubGlobal("Function", function disabledFunctionConstructor() {
48+
throw new EvalError("Code generation from strings disallowed for this context");
49+
});
50+
51+
await expect(
52+
credentialValidators.apiKey!({ apiKey: "api-token", values: {} }, { fetcher: createMcpFetch([], "api-token") }),
53+
).resolves.toMatchObject({ metadata: { mcpTools: ["docs", "execute", "search"] } });
54+
});
55+
4256
it("does not start MCP traffic after the execution is cancelled", async () => {
4357
const controller = new AbortController();
4458
controller.abort();
@@ -78,6 +92,7 @@ function createMcpFetch(calls: Array<Record<string, unknown>>, expectedToken: st
7892
tools: ["docs", "execute", "search"].map((name) => ({
7993
name,
8094
inputSchema: { type: "object" },
95+
outputSchema: { type: "object" },
8196
})),
8297
}
8398
: { content: [{ type: "text", text: '["workers"]' }] };

src/providers/cloudflare_mcp/executors.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
55
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
66
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
77
import { McpError } from "@modelcontextprotocol/sdk/types.js";
8+
import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
89
import { createHash } from "node:crypto";
910
import { defineBearerProviderExecutors, providerUserAgent, ProviderRequestError } from "../provider-runtime.ts";
1011

1112
const service = "cloudflare_mcp";
1213
const cloudflareMcpEndpoint = "https://mcp.cloudflare.com/mcp";
1314
const cloudflareMcpRequestTimeoutMs = 60_000;
1415
const expectedTools = ["docs", "execute", "search"];
16+
const cloudflareMcpJsonSchemaValidator = new CfWorkerJsonSchemaValidator();
1517

1618
type CloudflareMcpToolResult = Awaited<ReturnType<Client["callTool"]>>;
1719

@@ -102,7 +104,10 @@ async function withCloudflareMcpClient<T>(
102104
fetch: input.fetcher,
103105
requestInit: { headers },
104106
});
105-
const client = new Client({ name: "oomol-connect-cloudflare-mcp", version: "1.0.0" });
107+
const client = new Client(
108+
{ name: "oomol-connect-cloudflare-mcp", version: "1.0.0" },
109+
{ jsonSchemaValidator: cloudflareMcpJsonSchemaValidator },
110+
);
106111

107112
try {
108113
await client.connect(transport, {

0 commit comments

Comments
 (0)