Skip to content

Commit b2b2257

Browse files
author
Adam Bloomston
committed
feat: add optional strict parameter to registerTool for Zod validation
Add strict parameter to registerTool config that defaults to false for backward compatibility. When strict=true, applies .strict() to Zod schema creation for tool input validation to throw errors on unknown parameters instead of silently ignoring them. - Add strict?: boolean to registerTool config interface - Modify _createRegisteredTool to accept and use strict parameter - Apply z.object(inputSchema).strict() when strict=true - Update legacy tool() method to pass strict=false (backward compatibility) - Update test to verify strict validation rejects unknown parameters - All existing tests continue to pass (no breaking changes) This fixes the issue where parameter name typos are silently dropped, leading to confusing behavior where tools execute with missing data.
1 parent 5e2dbcd commit b2b2257

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

src/server/mcp.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4125,4 +4125,44 @@ describe('elicitInput()', () => {
41254125
expect(result.isError).toBe(true);
41264126
expect(result.content[0].text).toContain('Invalid arguments');
41274127
});
4128+
4129+
test('should reject unknown parameters when strict validation is enabled', async () => {
4130+
const mcpServer = new McpServer({
4131+
name: 'test server',
4132+
version: '1.0'
4133+
});
4134+
4135+
const client = new Client({
4136+
name: 'test client',
4137+
version: '1.0'
4138+
});
4139+
4140+
mcpServer.registerTool(
4141+
'test-strict',
4142+
{
4143+
inputSchema: { userName: z.string().optional(), itemCount: z.number().optional() },
4144+
strict: true
4145+
},
4146+
async ({ userName, itemCount }) => ({
4147+
content: [{ type: 'text', text: `${userName || 'none'}: ${itemCount || 0}` }]
4148+
})
4149+
);
4150+
4151+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
4152+
4153+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
4154+
4155+
await expect(
4156+
client.request(
4157+
{
4158+
method: 'tools/call',
4159+
params: {
4160+
name: 'test-strict',
4161+
arguments: { username: 'test', itemcount: 42 }
4162+
}
4163+
},
4164+
CallToolResultSchema
4165+
)
4166+
).rejects.toThrow('Invalid arguments');
4167+
});
41284168
});

src/server/mcp.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -647,13 +647,14 @@ export class McpServer {
647647
inputSchema: ZodRawShape | undefined,
648648
outputSchema: ZodRawShape | undefined,
649649
annotations: ToolAnnotations | undefined,
650+
strict: boolean | undefined,
650651
_meta: Record<string, unknown> | undefined,
651652
callback: ToolCallback<ZodRawShape | undefined>
652653
): RegisteredTool {
653654
const registeredTool: RegisteredTool = {
654655
title,
655656
description,
656-
inputSchema: inputSchema === undefined ? undefined : z.object(inputSchema),
657+
inputSchema: inputSchema === undefined ? undefined : strict === true ? z.object(inputSchema).strict() : z.object(inputSchema),
657658
outputSchema: outputSchema === undefined ? undefined : z.object(outputSchema),
658659
annotations,
659660
_meta,
@@ -780,7 +781,7 @@ export class McpServer {
780781
}
781782
const callback = rest[0] as ToolCallback<ZodRawShape | undefined>;
782783

783-
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, undefined, callback);
784+
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, false, undefined, callback);
784785
}
785786

786787
/**
@@ -794,6 +795,7 @@ export class McpServer {
794795
inputSchema?: InputArgs;
795796
outputSchema?: OutputArgs;
796797
annotations?: ToolAnnotations;
798+
strict?: boolean;
797799
_meta?: Record<string, unknown>;
798800
},
799801
cb: ToolCallback<InputArgs>
@@ -802,7 +804,7 @@ export class McpServer {
802804
throw new Error(`Tool ${name} is already registered`);
803805
}
804806

805-
const { title, description, inputSchema, outputSchema, annotations, _meta } = config;
807+
const { title, description, inputSchema, outputSchema, annotations, strict, _meta } = config;
806808

807809
return this._createRegisteredTool(
808810
name,
@@ -811,6 +813,7 @@ export class McpServer {
811813
inputSchema,
812814
outputSchema,
813815
annotations,
816+
strict,
814817
_meta,
815818
cb as ToolCallback<ZodRawShape | undefined>
816819
);

0 commit comments

Comments
 (0)