fix(plugin-mcp): support Cloudflare Workers and web-standard runtimes#16253
Open
sjkey wants to merge 1 commit intopayloadcms:mainfrom
Open
fix(plugin-mcp): support Cloudflare Workers and web-standard runtimes#16253sjkey wants to merge 1 commit intopayloadcms:mainfrom
sjkey wants to merge 1 commit intopayloadcms:mainfrom
Conversation
…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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #14716 — MCP plugin crashes on Cloudflare Workers with error 1101.
This PR addresses three issues that prevent
@payloadcms/plugin-mcpfrom working on Cloudflare Workers (and other web-standard runtimes that restricteval/new Function()):Bug 1:
mcp-handleruses Node.js-only APIsThe
mcp-handlerdependency importshttp.ServerResponse,net.Socket, andstream.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-handlerwithWebStandardStreamableHTTPServerTransportfrom@modelcontextprotocol/sdk, which uses pure Web StandardRequest/ResponseAPIs. 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 generationThe default
AjvJsonSchemaValidatorinMcpServeruses code generation (new Function()), which Cloudflare Workers blocks with "Code generation from strings disallowed for this context".Fix: Pass a permissive inline
jsonSchemaValidatortoMcpServerthat bypasses Ajv. Also wrappedconfigToJSONSchema(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:
convertCollectionSchemaToZodfallback returns wrong Zod typeWhen
convertCollectionSchemaToZodfails (it also usesnew Function()viajsonSchemaToZod+ TypeScript transpilation), its catch block returnsz.record(z.any()). However, calling code increateResourceToolandupdateResourceToolspreadsconvertedFields.shape— andz.record()has no.shapeproperty, causingTypeError: ls2(...).partial is not a function.Fix: Changed fallback to
z.object({}).passthrough()which has.shapeand accepts any input properties. This bug would also manifest on any platform wherenew Function()is restricted, not just Cloudflare Workers.Files Changed
packages/plugin-mcp/src/mcp/getMcpHandler.tsmcp-handlerwithWebStandardStreamableHTTPServerTransport, add permissive validator, wrapconfigToJSONSchemain try/catchpackages/plugin-mcp/src/endpoints/mcp.tsglobalThis.Request/Responsesave/restore workaround (no longer needed withoutmcp-handler)packages/plugin-mcp/src/utils/schemaConversion/convertCollectionSchemaToZod.tsz.record(z.any())toz.object({}).passthrough()Test Plan
Notes
@modelcontextprotocol/sdk/validation/cfworker(which shipsCfWorkerJsonSchemaValidator) for proper validation without Ajv, but that requires@cfworker/json-schemaas an additional dependency.mcp-handlerdependency can potentially be removed frompackage.jsonafter this change, as it's no longer imported. Left as-is to minimize the diff.