From c8b5bbaa8ab94dbe98d4040191734dbe8f4db3c7 Mon Sep 17 00:00:00 2001 From: XinLei Date: Wed, 11 Mar 2026 21:08:48 -0700 Subject: [PATCH 1/2] fix: surface input validation errors for task-augmented tool calls (#1385) When a task-augmented tools/call request fails input schema validation, the ProtocolError was being caught and wrapped into a CallToolResult (isError: true), which then failed CreateTaskResultSchema validation in server.ts, producing a confusing "Invalid task creation result" error that masked the actual validation failure. Now mcp.ts re-throws input-validation ProtocolErrors for task-augmented calls so clients receive the real error message (e.g. "Input validation error: Invalid arguments for tool X: Too small: expected number to be >=500"). Fixes #1385 Co-Authored-By: Claude Opus 4.6 --- packages/server/src/server/mcp.ts | 8 ++ test/integration/test/server/mcp.test.ts | 99 ++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 05136f5b6..2b2b65884 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -216,6 +216,14 @@ export class McpServer { if (error instanceof ProtocolError && error.code === ProtocolErrorCode.UrlElicitationRequired) { throw error; // Return the error to the caller without wrapping in CallToolResult } + if ( + request.params.task && + error instanceof ProtocolError && + error.code === ProtocolErrorCode.InvalidParams && + error.message.startsWith(`MCP error ${ProtocolErrorCode.InvalidParams}: Input validation error:`) + ) { + throw error; // Propagate input schema validation failures as protocol errors for task-augmented calls + } return this.createToolError(error instanceof Error ? error.message : String(error)); } }); diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 416f05102..b176b53d0 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -6623,6 +6623,105 @@ describe('Zod v4', () => { taskStore.cleanup(); }); + test('should surface input validation errors for task-augmented tool calls', async () => { + const taskStore = new InMemoryTaskStore(); + + const mcpServer = new McpServer( + { + name: 'test server', + version: '1.0' + }, + { + capabilities: { + tools: {}, + tasks: { + requests: { + tools: { + call: {} + } + } + } + }, + taskStore + } + ); + + const client = new Client( + { + name: 'test client', + version: '1.0' + }, + { + capabilities: { + tasks: { + requests: { + tools: { + call: {} + } + } + } + } + } + ); + + mcpServer.experimental.tasks.registerToolTask( + 'task-tool-validation', + { + description: 'A task tool with validated input', + inputSchema: z.object({ + duration: z.number().min(500) + }), + execution: { + taskSupport: 'required' + } + }, + { + createTask: async ({ duration }, ctx) => { + const task = await ctx.task.store.createTask({ ttl: duration, pollInterval: 100 }); + return { task }; + }, + getTask: async (_args, ctx) => { + const task = await ctx.task.store.getTask(ctx.task.id); + if (!task) { + throw new Error('Task not found'); + } + return task; + }, + getTaskResult: async (_data, ctx) => { + const result = await ctx.task.store.getTaskResult(ctx.task.id); + return result as CallToolResult; + } + } + ); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]); + + try { + await client.request({ + method: 'tools/call', + params: { + name: 'task-tool-validation', + arguments: { duration: 100 }, + task: { ttl: 60_000 } + } + }); + + expect.unreachable('Expected task-augmented validation error to be thrown'); + } catch (error) { + expect(error).toMatchObject({ + code: ProtocolErrorCode.InvalidParams, + message: expect.stringContaining('Too small: expected number to be >=500') + }); + expect(error).not.toMatchObject({ + message: expect.stringContaining('Invalid task creation result') + }); + } finally { + taskStore.cleanup(); + } + }); + test('should handle task failures during automatic polling', async () => { const taskStore = new InMemoryTaskStore(); const { releaseLatch, waitForLatch } = createLatch(); From 1792369d088eeed0fda7fa14161050dcccee8006 Mon Sep 17 00:00:00 2001 From: XinLei Date: Wed, 11 Mar 2026 22:02:49 -0700 Subject: [PATCH 2/2] ci: retrigger CI (infra flake on pnpm/action-setup@v4)