From c75f4d9dcc9603082773211cebc560d1066f0c48 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Tue, 3 Nov 2026 16:41:58 -0800 Subject: [PATCH] perf(cloudflare): Drop the legacy MCP SDK transport from chat routes The web chat and metadata routes still imported the v1 `@modelcontextprotocol/sdk` just for `StreamableHTTPClientTransport`, long after the server moved to SDK v2. That pulled a second copy of the MCP spec schemas into the worker and built ~1,000 extra zod schemas on every isolate start. `@ai-sdk/mcp` already ships an http transport that takes a url and headers, so use that and drop the dependency. Also removes the vitest ajv stub, which only existed because the legacy SDK imported ajv. Worker bundle drops ~119 KiB and local startup CPU goes from ~61ms to ~54ms. Co-Authored-By: Claude Opus 5.5 --- packages/mcp-cloudflare/package.json | 1 - .../mcp-cloudflare/src/server/routes/chat.ts | 25 +++++++---------- .../src/server/routes/metadata.ts | 13 ++++----- .../mcp-cloudflare/src/test-utils/ajv-stub.ts | 27 ------------------- packages/mcp-cloudflare/vitest.config.ts | 22 --------------- pnpm-lock.yaml | 3 --- 6 files changed, 15 insertions(+), 76 deletions(-) delete mode 100644 packages/mcp-cloudflare/src/test-utils/ajv-stub.ts diff --git a/packages/mcp-cloudflare/package.json b/packages/mcp-cloudflare/package.json index 6685bfbf1..eca68743c 100644 --- a/packages/mcp-cloudflare/package.json +++ b/packages/mcp-cloudflare/package.json @@ -56,7 +56,6 @@ "@ai-sdk/mcp": "catalog:", "@ai-sdk/react": "catalog:", "@cloudflare/workers-oauth-provider": "catalog:", - "@modelcontextprotocol/sdk": "catalog:", "@modelcontextprotocol/server": "catalog:", "@radix-ui/react-accordion": "catalog:", "@radix-ui/react-slot": "catalog:", diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index 3b99c2271..8e256ffbb 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -1,5 +1,4 @@ import { experimental_createMCPClient } from "@ai-sdk/mcp"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { getOpenRouterModel, getOpenRouterProviderOptions, @@ -245,19 +244,17 @@ export default new Hono<{ Bindings: Env }>().post("/", async (c) => { // Get the current request URL to construct the MCP endpoint URL const requestUrl = new URL(c.req.url); const mcpUrl = new URL(`${requestUrl.protocol}//${requestUrl.host}/mcp`); - const httpTransport = new StreamableHTTPClientTransport(mcpUrl, { - requestInit: { + mcpClient = await experimental_createMCPClient({ + name: "mcp.sentry.dev (web)", + transport: { + type: "http", + url: mcpUrl.href, headers: { Authorization: `Bearer ${currentAccessToken}`, }, }, }); - mcpClient = await experimental_createMCPClient({ - name: "mcp.sentry.dev (web)", - transport: httpTransport, - }); - // Get available tools from MCP server Object.assign(tools, await mcpClient.tools()); logInfo(`Connected to ${mcpUrl}`, { @@ -281,19 +278,17 @@ export default new Hono<{ Bindings: Env }>().post("/", async (c) => { const mcpUrl = new URL( `${requestUrl.protocol}//${requestUrl.host}/mcp`, ); - const httpTransport = new StreamableHTTPClientTransport(mcpUrl, { - requestInit: { + mcpClient = await experimental_createMCPClient({ + name: "mcp.sentry.dev (web)", + transport: { + type: "http", + url: mcpUrl.href, headers: { Authorization: `Bearer ${currentAccessToken}`, }, }, }); - mcpClient = await experimental_createMCPClient({ - name: "mcp.sentry.dev (web)", - transport: httpTransport, - }); - Object.assign(tools, await mcpClient.tools()); logInfo(`Connected to ${mcpUrl} (after refresh)`, { loggerScope: ["cloudflare", "chat", "connection"], diff --git a/packages/mcp-cloudflare/src/server/routes/metadata.ts b/packages/mcp-cloudflare/src/server/routes/metadata.ts index 707360a08..75533d624 100644 --- a/packages/mcp-cloudflare/src/server/routes/metadata.ts +++ b/packages/mcp-cloudflare/src/server/routes/metadata.ts @@ -6,7 +6,6 @@ */ import { Hono } from "hono"; import { experimental_createMCPClient } from "@ai-sdk/mcp"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import type { Env } from "../types"; import { logIssue, logWarn } from "@sentry/mcp-core/telem/logging"; import { AuthDataSchema } from "../types/chat"; @@ -58,19 +57,17 @@ export default new Hono<{ Bindings: Env }>().get("/", async (c) => { const requestUrl = new URL(c.req.url); const mcpUrl = `${requestUrl.protocol}//${requestUrl.host}/mcp`; - const httpTransport = new StreamableHTTPClientTransport(new URL(mcpUrl), { - requestInit: { + mcpClient = await experimental_createMCPClient({ + name: "sentry", + transport: { + type: "http", + url: mcpUrl, headers: { Authorization: `Bearer ${accessToken}`, }, }, }); - mcpClient = await experimental_createMCPClient({ - name: "sentry", - transport: httpTransport, - }); - const mcpTools = await mcpClient.tools(); tools = Object.keys(mcpTools); } catch (error) { diff --git a/packages/mcp-cloudflare/src/test-utils/ajv-stub.ts b/packages/mcp-cloudflare/src/test-utils/ajv-stub.ts deleted file mode 100644 index 78739144c..000000000 --- a/packages/mcp-cloudflare/src/test-utils/ajv-stub.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Stub for ajv module to bypass CJS require() issues in workerd runtime. - * - * The legacy MCP SDK imports ajv at module level even though the hosted v2 path - * uses the SDK's workerd-compatible default validator. ajv uses CJS require() - * for JSON files which fails in workerd. - * See: https://github.com/cloudflare/workers-sdk/issues/9822 - * - * This stub provides the minimal API surface required by that legacy import; - * hosted MCP requests do not invoke it. - */ - -export class Ajv { - compile() { - return () => true; - } - - getSchema() { - return undefined; - } - - errorsText() { - return ""; - } -} - -export default Ajv; diff --git a/packages/mcp-cloudflare/vitest.config.ts b/packages/mcp-cloudflare/vitest.config.ts index af0152549..5b3f3f4c1 100644 --- a/packages/mcp-cloudflare/vitest.config.ts +++ b/packages/mcp-cloudflare/vitest.config.ts @@ -1,4 +1,3 @@ -import path from "node:path"; import { cloudflareTest } from "@cloudflare/vitest-plugin"; import { defineConfig } from "vitest/config"; import { createMockOutboundService } from "./src/test-utils/configure-fetch-mock"; @@ -29,25 +28,4 @@ export default defineConfig({ fileParallelism: false, maxWorkers: 1, }, - /** - * Workaround for ajv CJS compatibility in workerd runtime. - * - * The legacy MCP SDK imports ajv at module level even though hosted requests use - * the v2 SDK's workerd-compatible default validator. ajv uses CJS require() for - * JSON files which fails in workerd. - * See: https://github.com/cloudflare/workers-sdk/issues/9822 - * - * This is TEST-ONLY: hosted MCP requests do not invoke ajv, but importing the - * legacy SDK still triggers the CJS issue. - */ - resolve: { - alias: { - ajv: path.resolve(__dirname, "src/test-utils/ajv-stub.ts"), - "ajv-formats": path.resolve(__dirname, "src/test-utils/ajv-stub.ts"), - }, - }, - // Force bundling to apply the ajv alias during module resolution - ssr: { - noExternal: ["@modelcontextprotocol/sdk", "agents"], - }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54fab4625..9dc1f939e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -679,9 +679,6 @@ importers: '@cloudflare/workers-oauth-provider': specifier: 'catalog:' version: 0.10.3(patch_hash=34910b67929c1888b1492384c514d6baeedc4a439f66e21375dee5af404834f8) - '@modelcontextprotocol/sdk': - specifier: 1.32.2 - version: 1.32.2(@cfworker/json-schema@4.1.1)(zod@4.6.7) '@modelcontextprotocol/server': specifier: 'catalog:' version: 2.4.0