diff --git a/actions/setup/js/mcp_server_core.cjs b/actions/setup/js/mcp_server_core.cjs index 370574379c2..7b707a21c4e 100644 --- a/actions/setup/js/mcp_server_core.cjs +++ b/actions/setup/js/mcp_server_core.cjs @@ -772,9 +772,10 @@ async function handleRequest(server, request, defaultHandler) { if (missing.length) { const hasRequiredFields = tool.inputSchema && Array.isArray(tool.inputSchema.required) && tool.inputSchema.required.length > 0; if (hasRequiredFields && Object.keys(args).length === 0) { + const schemaGuidance = generateEnhancedErrorMessage(tool.inputSchema.required, name, tool.inputSchema); throw { code: -32602, - message: `Empty arguments are not allowed — this tool is write-once, not a discovery probe. To inspect the schema, use the tools/list MCP method. To signal that no action is needed, call \`noop\` with a \`message\`.`, + message: `Empty arguments are not allowed — this tool is write-once, not a discovery probe. To inspect the schema, use the tools/list MCP method. To signal that no action is needed, call \`noop\` with a \`message\`.\n\n${schemaGuidance}`, }; } throw { @@ -941,10 +942,11 @@ async function handleMessage(server, req, defaultHandler) { if (missing.length) { const hasRequiredFields = tool.inputSchema && Array.isArray(tool.inputSchema.required) && tool.inputSchema.required.length > 0; if (hasRequiredFields && Object.keys(args).length === 0) { + const schemaGuidance = generateEnhancedErrorMessage(tool.inputSchema.required, name, tool.inputSchema); server.replyError( id, -32602, - `Empty arguments are not allowed — this tool is write-once, not a discovery probe. To inspect the schema, use the tools/list MCP method. To signal that no action is needed, call \`noop\` with a \`message\`.` + `Empty arguments are not allowed — this tool is write-once, not a discovery probe. To inspect the schema, use the tools/list MCP method. To signal that no action is needed, call \`noop\` with a \`message\`.\n\n${schemaGuidance}` ); return; } diff --git a/actions/setup/js/mcp_server_core.test.cjs b/actions/setup/js/mcp_server_core.test.cjs index 7f86c1529d4..e5819d785fc 100644 --- a/actions/setup/js/mcp_server_core.test.cjs +++ b/actions/setup/js/mcp_server_core.test.cjs @@ -354,6 +354,9 @@ describe("mcp_server_core.cjs", () => { expect(results[0].error.message).toContain("write-once, not a discovery probe"); expect(results[0].error.message).toContain("tools/list"); expect(results[0].error.message).toContain("noop"); + // Schema guidance should be included so the model can retry without calling tools/list + expect(results[0].error.message).toContain("Example:"); + expect(results[0].error.message).toContain("Required parameter"); }); it("should return enhanced error for partially-supplied but invalid required fields", async () => { @@ -568,6 +571,43 @@ describe("mcp_server_core.cjs", () => { expect(response.error.message).toContain("too short"); expect(response.error.message).toContain("20"); }); + + it("should return error for empty arguments object (probe detection) via handleRequest", async () => { + const { createServer, registerTool, handleRequest } = await import("./mcp_server_core.cjs"); + const server = createServer({ name: "test-server", version: "1.0.0" }); + + registerTool(server, { + name: "probe_tool", + description: "A tool to test probe detection", + inputSchema: { + type: "object", + properties: { input: { type: "string", description: "Some input" } }, + required: ["input"], + }, + handler: args => ({ + content: [{ type: "text", text: `received: ${args.input}` }], + }), + }); + + const response = await handleRequest(server, { + jsonrpc: "2.0", + id: 10, + method: "tools/call", + params: { + name: "probe_tool", + arguments: {}, // completely empty — probe attempt + }, + }); + + expect(response.error).toBeDefined(); + expect(response.error.code).toBe(-32602); + expect(response.error.message).toContain("write-once, not a discovery probe"); + expect(response.error.message).toContain("tools/list"); + expect(response.error.message).toContain("noop"); + // Schema guidance should be included so the model can retry without calling tools/list + expect(response.error.message).toContain("Example:"); + expect(response.error.message).toContain("Required parameter"); + }); }); describe("loadToolHandlers", () => {