fix: reject plain JSON Schema objects passed as inputSchema (#1596)

Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
This commit is contained in:
Tilak Dave 2026-03-23 20:15:17 +05:30 • committed by GitHub
commit 93640d33ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 4 deletions

View file

@ -1024,9 +1024,10 @@ export class McpServer {
annotations = rest.shift() as ToolAnnotations;
}
} else if (typeof firstArg === 'object' && firstArg !== null) {
// Not a ZodRawShapeCompat, so must be annotations in this position
// Case: tool(name, annotations, cb)
// Or: tool(name, description, annotations, cb)
// ToolAnnotations values are primitives. Nested objects indicate a misplaced schema
if (Object.values(firstArg).some(v => typeof v === 'object' && v !== null)) {
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
}
annotations = rest.shift() as ToolAnnotations;
}
}
@ -1386,7 +1387,7 @@ function isZodRawShapeCompat(obj: unknown): obj is ZodRawShapeCompat {
/**
* Converts a provided Zod schema to a Zod object if it is a ZodRawShapeCompat,
* otherwise returns the schema as is.
* otherwise returns the schema as is. Throws if the value is not a valid Zod schema.
*/
function getZodSchemaObject(schema: ZodRawShapeCompat | AnySchema | undefined): AnySchema | undefined {
if (!schema) {
@ -1397,6 +1398,10 @@ function getZodSchemaObject(schema: ZodRawShapeCompat | AnySchema | undefined):
return objectFromShape(schema);
}
if (!isZodSchemaInstance(schema as object)) {
throw new Error('inputSchema must be a Zod schema or raw shape, received an unrecognized object');
}
return schema;
}

View file

@ -2050,6 +2050,37 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
// Clean up spies
warnSpy.mockRestore();
});
test('should reject plain JSON Schema objects passed as inputSchema', () => {
const mcpServer = new McpServer({
name: 'test server',
version: '1.0'
});
const jsonSchema = {
type: 'object',
properties: {
directory_id: {
type: 'string',
format: 'uuid',
description: 'The UUID of the directory'
}
},
required: ['directory_id']
} as any;
const cb = async ({ directory_id }: any) => ({
content: [{ type: 'text' as const, text: `Got: ${directory_id}` }]
});
expect(() => {
mcpServer.tool('test', 'A tool', jsonSchema, cb);
}).toThrow(/unrecognized object/);
expect(() => {
mcpServer.registerTool('test', { description: 'A tool', inputSchema: jsonSchema }, cb);
}).toThrow(/unrecognized object/);
});
});
describe('resource()', () => {