Skip to content

Task-augmented tool call errors are wrapped incorrectly, masking actual error messages #1385

@punkpeye

Description

@punkpeye

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

  1. 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
    },
  },
);
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions