Skip to content

fix(plugin-mcp): support Cloudflare Workers and web-standard runtimes#16253

Open
sjkey wants to merge 1 commit intopayloadcms:mainfrom
sjkey:fix/mcp-cloudflare-workers
Open

fix(plugin-mcp): support Cloudflare Workers and web-standard runtimes#16253
sjkey wants to merge 1 commit intopayloadcms:mainfrom
sjkey:fix/mcp-cloudflare-workers

Conversation

@sjkey
Copy link
Copy Markdown

@sjkey sjkey commented Apr 12, 2026

Summary

Fixes #14716 — MCP plugin crashes on Cloudflare Workers with error 1101.

This PR addresses three issues that prevent @payloadcms/plugin-mcp from working on Cloudflare Workers (and other web-standard runtimes that restrict eval/new Function()):

Bug 1: mcp-handler uses Node.js-only APIs

The mcp-handler dependency imports http.ServerResponse, net.Socket, and stream.Readable — Node.js APIs that don't exist on Cloudflare Workers. This causes error 1101 (Worker threw exception) on any POST request to /api/mcp.

Fix: Replace mcp-handler with WebStandardStreamableHTTPServerTransport from @modelcontextprotocol/sdk, which uses pure Web Standard Request/Response APIs. The transport is created per-request in stateless mode (sessionIdGenerator: undefined), appropriate for serverless runtimes without persistent in-memory state.

Bug 2: Ajv uses new Function() for code generation

The default AjvJsonSchemaValidator in McpServer uses code generation (new Function()), which Cloudflare Workers blocks with "Code generation from strings disallowed for this context".

Fix: Pass a permissive inline jsonSchemaValidator to McpServer that bypasses Ajv. Also wrapped configToJSONSchema (which uses Ajv internally via Payload core) in a try/catch with an empty-schema fallback so tools still register even when schema generation fails.

Bug 3: convertCollectionSchemaToZod fallback returns wrong Zod type

When convertCollectionSchemaToZod fails (it also uses new Function() via jsonSchemaToZod + TypeScript transpilation), its catch block returns z.record(z.any()). However, calling code in createResourceTool and updateResourceTool spreads convertedFields.shape — and z.record() has no .shape property, causing TypeError: ls2(...).partial is not a function.

Fix: Changed fallback to z.object({}).passthrough() which has .shape and accepts any input properties. This bug would also manifest on any platform where new Function() is restricted, not just Cloudflare Workers.

Files Changed

File Change
packages/plugin-mcp/src/mcp/getMcpHandler.ts Replace mcp-handler with WebStandardStreamableHTTPServerTransport, add permissive validator, wrap configToJSONSchema in try/catch
packages/plugin-mcp/src/endpoints/mcp.ts Remove globalThis.Request/Response save/restore workaround (no longer needed without mcp-handler)
packages/plugin-mcp/src/utils/schemaConversion/convertCollectionSchemaToZod.ts Change fallback from z.record(z.any()) to z.object({}).passthrough()

Test Plan

  • Tested on production Cloudflare Workers (Payload 3.82.1, Next.js 15.4.11, @opennextjs/cloudflare 1.16.6)
  • MCP initialize handshake returns correct capabilities
  • 21 tools registered across 14 collections (CRUD operations)
  • Tool calls work end-to-end (findPages returns page data from D1)
  • Admin panel continues to work normally
  • Should be verified on Node.js/Vercel to confirm no regression

Notes

  • The permissive validator means tool input is not schema-validated on Workers. A future improvement could use @modelcontextprotocol/sdk/validation/cfworker (which ships CfWorkerJsonSchemaValidator) for proper validation without Ajv, but that requires @cfworker/json-schema as an additional dependency.
  • The mcp-handler dependency can potentially be removed from package.json after this change, as it's no longer imported. Left as-is to minimize the diff.

…ntimes

Fixes payloadcms#14716

The MCP plugin crashes on Cloudflare Workers due to three issues:

1. **mcp-handler uses Node.js APIs** — imports `http.ServerResponse`,
   `net.Socket`, and `stream.Readable` which don't exist on Workers,
   causing error 1101 on POST requests to `/api/mcp`.

   → Replaced with `WebStandardStreamableHTTPServerTransport` from
   `@modelcontextprotocol/sdk`, which uses pure Web Standard
   `Request`/`Response` APIs compatible with any runtime.

2. **Ajv uses `new Function()`** — the default `AjvJsonSchemaValidator`
   in `McpServer` generates code from strings, which is blocked on
   Workers ("Code generation from strings disallowed").

   → Added a permissive inline validator that bypasses Ajv. Also
   wrapped `configToJSONSchema` in a try/catch since it also uses Ajv
   internally via Payload core.

3. **Schema fallback type mismatch** — when `convertCollectionSchemaToZod`
   fails (also uses `new Function()`), its catch block returns
   `z.record(z.any())` which has no `.shape` property. Calling code in
   `createResourceTool` and `updateResourceTool` spreads
   `convertedFields.shape`, causing `TypeError: .partial is not a function`.

   → Changed fallback to `z.object({}).passthrough()` which has `.shape`
   and accepts any properties. This bug affects any environment where
   `new Function()` is restricted, not just Cloudflare Workers.

Tested with 21 MCP tools on a production Cloudflare Workers deployment
(Payload 3.82.1, Next.js 15.4.11, @opennextjs/cloudflare 1.16.6).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP plugin on Cloudflare workers is not working

1 participant