-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Summary
When a task-augmented tools/call request fails (e.g., due to input validation), the error is caught and converted to a tool error result ({content: [...], isError: true}), which then fails CreateTaskResultSchema validation. This results in a confusing error message that masks the actual underlying error.
Steps to Reproduce
- Register a task-based tool with input validation:
server.experimental.tasks.registerToolTask(
'batch_process',
{
inputSchema: {
itemCount: z.number().min(1).max(10),
processingTimeMs: z.number().min(500).max(5000).optional(),
},
// ... other config
},
{
createTask: async (args, extra) => {
// ... handler
},
},
);- Call the tool with task augmentation and invalid arguments:
{
"method": "tools/call",
"params": {
"name": "batch_process",
"arguments": { "itemCount": 5, "processingTimeMs": 100 },
"task": { "ttl": 60000 }
}
}Expected Behavior
The client should receive a clear error message indicating the actual problem:
{
"error": {
"code": -32602,
"message": "Input validation error: Invalid arguments for tool batch_process: Too small: expected number to be >=500"
}
}Actual Behavior
The client receives a confusing error that hides the actual cause:
{
"error": {
"code": -32602,
"message": "MCP error -32602: Invalid task creation result: [{\"expected\":\"object\",\"code\":\"invalid_type\",\"path\":[\"task\"],\"message\":\"Invalid input: expected object, received undefined\"}]"
}
}Root Cause
The issue is in the error handling flow in server/mcp.js:
// In setToolRequestHandlers() - CallToolRequestSchema handler
try {
const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
const result = await this.executeToolHandler(tool, args, extra);
// ...
} catch (error) {
// ALL errors get wrapped as tool errors, including validation errors
return this.createToolError(error instanceof Error ? error.message : String(error));
}Then in server/index.js, the wrapped handler validates the result:
if (params.task) {
const taskValidationResult = safeParse(CreateTaskResultSchema, result);
if (!taskValidationResult.success) {
// This fails because createToolError returns {content: [...], isError: true}
// which doesn't have a 'task' property
throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
}
}Suggested Fix
For task-augmented requests, errors thrown before task creation (like input validation errors) should be re-thrown directly instead of being wrapped as tool errors:
// In setToolRequestHandlers() - CallToolRequestSchema handler
try {
// ...
} catch (error) {
if (error instanceof McpError) {
if (error.code === ErrorCode.UrlElicitationRequired) {
throw error;
}
// For task-augmented requests, also re-throw validation errors
// instead of wrapping them as tool errors
if (isTaskRequest && error.code === ErrorCode.InvalidParams) {
throw error;
}
}
return this.createToolError(error instanceof Error ? error.message : String(error));
}Or alternatively, modify server/index.js to not validate non-task results against CreateTaskResultSchema:
if (params.task) {
// Only validate if the result looks like a CreateTaskResult (has task property)
// If it's a tool error, return it as-is or convert to a proper error response
if ('isError' in result && result.isError) {
throw new McpError(ErrorCode.InvalidParams, result.content[0]?.text ?? 'Tool execution failed');
}
// ... existing validation
}Environment
- SDK Version: 1.25.2
- Node.js: v24.1.0
This bug affects the developer experience significantly as the actual error (input validation, missing taskStore, handler exceptions, etc.) is completely hidden behind the generic "Invalid task creation result" message.