Skip to content

Commit 6184b0c

Browse files
fix(plugin-mcp): add error handling to convertCollectionSchemaToZod (#16114)
## Summary Wraps `convertCollectionSchemaToZod` in a try/catch so that a single collection's schema conversion failure doesn't crash the entire `tools/list` MCP response. ## Problem The schema conversion pipeline (`JSON Schema → json-schema-to-zod → TypeScript transpile → eval`) can fail for collections with complex nested schemas. When it does, the error propagates up to the MCP SDK's `tools/list` handler, crashing the entire response and making **all** MCP tools inaccessible — not just the one with the problematic schema. The most common trigger is a Zod v4 bug where `toJSONSchema` crashes with `TypeError: Cannot read properties of undefined (reading '_zod')` on record schemas with undefined `valueType` (see [colinhacks/zod#5821](colinhacks/zod#5821), PR [colinhacks/zod#5822](colinhacks/zod#5822)). ## Fix ```typescript try { // existing conversion pipeline... return new Function('z', `return ${transpileResult.outputText}`)(z) } catch (error) { console.warn(`[plugin-mcp] Schema conversion failed, using permissive fallback:`, error.message) return z.record(z.any()) } ``` When conversion fails: - The tool is still listed in `tools/list` (with a permissive `z.record(z.any())` schema) - Other tools with valid schemas are completely unaffected - A warning is logged to help diagnose which collections have problematic schemas ## Impact Without this fix, any project with 20+ collections (where at least one has a complex nested block schema) will have a completely non-functional MCP server — `tools/list` returns an error instead of the tool list. With this fix, only the affected collection loses strict parameter validation; everything else works normally. ## Testing Verified in production with a 22-collection Payload CMS deployment on Vercel. Before the fix, `tools/list` crashed entirely. After, all tools are listed and functional.
1 parent 0981cdc commit 6184b0c

1 file changed

Lines changed: 37 additions & 24 deletions

File tree

packages/plugin-mcp/src/utils/schemaConversion/convertCollectionSchemaToZod.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,44 @@ import { simplifyRelationshipFields } from './simplifyRelationshipFields.js'
99
import { transformPointFieldsForMCP } from './transformPointFields.js'
1010

1111
export const convertCollectionSchemaToZod = (schema: JSONSchema4) => {
12-
// Clone to avoid mutating the original schema (used elsewhere for tool listing)
13-
const schemaClone = JSON.parse(JSON.stringify(schema)) as JSONSchema4
12+
try {
13+
// Clone to avoid mutating the original schema (used elsewhere for tool listing)
14+
const schemaClone = JSON.parse(JSON.stringify(schema)) as JSONSchema4
1415

15-
const sanitized = sanitizeJsonSchema(schemaClone)
16-
const pointTransformed = transformPointFieldsForMCP(sanitized)
17-
const zodSchemaAsString = jsonSchemaToZod(simplifyRelationshipFields(pointTransformed))
16+
const sanitized = sanitizeJsonSchema(schemaClone)
17+
const pointTransformed = transformPointFieldsForMCP(sanitized)
18+
const zodSchemaAsString = jsonSchemaToZod(simplifyRelationshipFields(pointTransformed))
1819

19-
// Transpile TypeScript to JavaScript
20-
const transpileResult = ts.transpileModule(zodSchemaAsString, {
21-
compilerOptions: {
22-
module: ts.ModuleKind.CommonJS,
23-
removeComments: true,
24-
strict: false,
25-
target: ts.ScriptTarget.ES2018,
26-
},
27-
})
20+
// Transpile TypeScript to JavaScript
21+
const transpileResult = ts.transpileModule(zodSchemaAsString, {
22+
compilerOptions: {
23+
module: ts.ModuleKind.CommonJS,
24+
removeComments: true,
25+
strict: false,
26+
target: ts.ScriptTarget.ES2018,
27+
},
28+
})
2829

29-
/**
30-
* This Function evaluation is safe because:
31-
* 1. The input schema comes from Payload's collection configuration, which is controlled by the application developer
32-
* 2. The jsonSchemaToZod library converts JSON Schema to Zod schema definitions, producing only type validation code
33-
* 3. The transpiled output contains only Zod schema definitions (z.string(), z.number(), etc.) - no executable logic
34-
* 4. The resulting Zod schema is used only for parameter validation in MCP tools, not for data processing
35-
* 5. No user input or external data is involved in the schema generation process
36-
*/
37-
// eslint-disable-next-line @typescript-eslint/no-implied-eval
38-
return new Function('z', `return ${transpileResult.outputText}`)(z)
30+
/**
31+
* This Function evaluation is safe because:
32+
* 1. The input schema comes from Payload's collection configuration, which is controlled by the application developer
33+
* 2. The jsonSchemaToZod library converts JSON Schema to Zod schema definitions, producing only type validation code
34+
* 3. The transpiled output contains only Zod schema definitions (z.string(), z.number(), etc.) - no executable logic
35+
* 4. The resulting Zod schema is used only for parameter validation in MCP tools, not for data processing
36+
* 5. No user input or external data is involved in the schema generation process
37+
*/
38+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
39+
return new Function('z', `return ${transpileResult.outputText}`)(z)
40+
} catch (error) {
41+
// If schema conversion fails (e.g., due to Zod v4 toJSONSchema null-check bug
42+
// with record schemas that have undefined valueType, or bundler transforms
43+
// stripping Zod internals), return a permissive schema so tools/list doesn't
44+
// crash entirely. The tool will still be listed but without strict validation.
45+
// See: https://github.com/colinhacks/zod/issues/5821
46+
console.warn(
47+
`[plugin-mcp] Schema conversion failed, using permissive fallback:`,
48+
error instanceof Error ? error.message : error,
49+
)
50+
return z.record(z.any())
51+
}
3952
}

0 commit comments

Comments
 (0)