Description
RegisteredTool.update() crashes when paramsSchema is a ZodObject instance (e.g. z.object({...}).passthrough()) instead of a raw shape (Record<string, ZodType>).
The error:
TypeError: Cannot read properties of null (reading '_zod')
Root cause
The create path in _createRegisteredTool uses getZodSchemaObject(inputSchema) which correctly handles both raw shapes and ZodObject instances:
function getZodSchemaObject(schema) {
if (!schema) return undefined;
if (isZodRawShapeCompat(schema)) return objectFromShape(schema); // raw shape
return schema; // ZodObject — pass through
}
But the update path calls objectFromShape() directly:
update: updates => {
if (typeof updates.paramsSchema !== 'undefined')
registeredTool.inputSchema = objectFromShape(updates.paramsSchema);
}
objectFromShape expects a raw shape and calls Object.values(shape). When given a Zod v3 ZodObject, Object.values() iterates all own enumerable properties — including internal ones like _cached which is null. This leads to isZ4Schema(null) which accesses null._zod and throws.
Type inconsistency
The types reflect the same inconsistency:
registerTool accepts inputSchema?: InputArgs where InputArgs extends undefined | ZodRawShapeCompat | AnySchema — ZodObject is supported
RegisteredTool.update() types paramsSchema?: InputArgs where InputArgs extends ZodRawShapeCompat — only raw shapes
Reproduction
const { z } = require('zod');
const schema = z.object({
id: z.string(),
property: z.string().optional(),
}).passthrough();
// Register works fine (create path handles ZodObject)
const tool = server.registerTool('my_tool', {
description: 'test',
inputSchema: schema,
}, async (args) => ({ content: [{ type: 'text', text: 'ok' }] }));
// Update crashes (update path does not handle ZodObject)
tool.update({ paramsSchema: schema });
// TypeError: Cannot read properties of null (reading '_zod')
Suggested fix
The update path should use getZodSchemaObject() (or equivalent) instead of calling objectFromShape() directly, matching the create path behavior.
Environment
@modelcontextprotocol/sdk: 1.26.0
zod: 3.25.76
- Node.js: v20.x
Description
RegisteredTool.update()crashes whenparamsSchemais a ZodObject instance (e.g.z.object({...}).passthrough()) instead of a raw shape (Record<string, ZodType>).The error:
Root cause
The create path in
_createRegisteredToolusesgetZodSchemaObject(inputSchema)which correctly handles both raw shapes and ZodObject instances:But the update path calls
objectFromShape()directly:objectFromShapeexpects a raw shape and callsObject.values(shape). When given a Zod v3 ZodObject,Object.values()iterates all own enumerable properties — including internal ones like_cachedwhich isnull. This leads toisZ4Schema(null)which accessesnull._zodand throws.Type inconsistency
The types reflect the same inconsistency:
registerToolacceptsinputSchema?: InputArgswhereInputArgs extends undefined | ZodRawShapeCompat | AnySchema— ZodObject is supportedRegisteredTool.update()typesparamsSchema?: InputArgswhereInputArgs extends ZodRawShapeCompat— only raw shapesReproduction
Suggested fix
The update path should use
getZodSchemaObject()(or equivalent) instead of callingobjectFromShape()directly, matching the create path behavior.Environment
@modelcontextprotocol/sdk: 1.26.0zod: 3.25.76