Skip to content

Commit 8016684

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 d5df257 commit 8016684

File tree

2 files changed

+21
-18
lines changed

2 files changed

+21
-18
lines changed

src/server/mcp.test.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4292,11 +4292,11 @@ describe("elicitInput()", () => {
42924292
});
42934293

42944294
/**
4295-
* Test: Tool parameter validation with incorrect capitalization
4296-
* This test demonstrates that tools currently silently ignore unknown parameters,
4297-
* including those with incorrect capitalization. This should fail to show the issue exists.
4295+
* Test: Tool parameter validation with strict mode
4296+
* This test verifies that tools with strict: true reject unknown parameters,
4297+
* including those with incorrect capitalization.
42984298
*/
4299-
test("should fail: tool with incorrect parameter capitalization is not rejected", async () => {
4299+
test("should reject unknown parameters when strict validation is enabled", async () => {
43004300
const mcpServer = new McpServer({
43014301
name: "test server",
43024302
version: "1.0",
@@ -4307,11 +4307,12 @@ describe("elicitInput()", () => {
43074307
version: "1.0",
43084308
});
43094309

4310-
// Register a tool that expects optional 'userName' and 'itemCount'
4310+
// Register a tool with strict validation enabled
43114311
mcpServer.registerTool(
43124312
"test-strict",
43134313
{
43144314
inputSchema: { userName: z.string().optional(), itemCount: z.number().optional() },
4315+
strict: true,
43154316
},
43164317
async ({ userName, itemCount }) => ({
43174318
content: [{ type: "text", text: `${userName || 'none'}: ${itemCount || 0}` }],
@@ -4326,22 +4327,17 @@ describe("elicitInput()", () => {
43264327
mcpServer.server.connect(serverTransport),
43274328
]);
43284329

4329-
// Call the tool with unknown parameters (incorrect capitalization)
4330-
// These should be rejected but currently aren't - they're silently ignored
4331-
const result = await client.request(
4330+
// Call the tool with unknown parameters (incorrect capitalization)
4331+
// With strict: true, these should now be rejected
4332+
await expect(client.request(
43324333
{
43334334
method: "tools/call",
43344335
params: {
43354336
name: "test-strict",
4336-
arguments: { username: "test", itemcount: 42 }, // Should be rejected as unknown parameters
4337+
arguments: { username: "test", itemcount: 42 }, // Unknown parameters should cause error
43374338
},
43384339
},
43394340
CallToolResultSchema,
4340-
);
4341-
4342-
// This expectation should fail because the tool currently accepts the call
4343-
// and silently ignores unknown parameters, returning success instead of error
4344-
expect(result.isError).toBe(true);
4345-
expect(result.content[0].text).toContain("Invalid arguments");
4341+
)).rejects.toThrow("Invalid arguments");
43464342
});
43474343
});

src/server/mcp.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -772,13 +772,18 @@ export class McpServer {
772772
inputSchema: ZodRawShape | undefined,
773773
outputSchema: ZodRawShape | undefined,
774774
annotations: ToolAnnotations | undefined,
775+
strict: boolean | undefined,
775776
callback: ToolCallback<ZodRawShape | undefined>
776777
): RegisteredTool {
777778
const registeredTool: RegisteredTool = {
778779
title,
779780
description,
780781
inputSchema:
781-
inputSchema === undefined ? undefined : z.object(inputSchema),
782+
inputSchema === undefined
783+
? undefined
784+
: strict === true
785+
? z.object(inputSchema).strict()
786+
: z.object(inputSchema),
782787
outputSchema:
783788
outputSchema === undefined ? undefined : z.object(outputSchema),
784789
annotations,
@@ -914,7 +919,7 @@ export class McpServer {
914919
}
915920
const callback = rest[0] as ToolCallback<ZodRawShape | undefined>;
916921

917-
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, callback)
922+
return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, false, callback)
918923
}
919924

920925
/**
@@ -928,14 +933,15 @@ export class McpServer {
928933
inputSchema?: InputArgs;
929934
outputSchema?: OutputArgs;
930935
annotations?: ToolAnnotations;
936+
strict?: boolean;
931937
},
932938
cb: ToolCallback<InputArgs>
933939
): RegisteredTool {
934940
if (this._registeredTools[name]) {
935941
throw new Error(`Tool ${name} is already registered`);
936942
}
937943

938-
const { title, description, inputSchema, outputSchema, annotations } = config;
944+
const { title, description, inputSchema, outputSchema, annotations, strict } = config;
939945

940946
return this._createRegisteredTool(
941947
name,
@@ -944,6 +950,7 @@ export class McpServer {
944950
inputSchema,
945951
outputSchema,
946952
annotations,
953+
strict,
947954
cb as ToolCallback<ZodRawShape | undefined>
948955
);
949956
}

0 commit comments

Comments
 (0)