Follow-up to #2497, split out from my comment there as suggested.
Problem. When an MCP server reports a tool execution error (a CallToolResult with isError: true, which is what the SDK sends when a tool throws Mcp\Exception\ToolCallException), the model never sees the server's message. It only gets:
An error occurred while executing tool "demo_movie_details".
MCP puts tool errors inside the result so the model can see them and fix the call. Here the text is lost.
Cause. McpToolbox::invoke() throws ToolCallException::returnedError(). That exception doesn't implement ToolExecutionExceptionInterface, so AbstractToolbox wraps it in ToolExecutionException::executionFailed(), and FaultTolerantToolbox replaces it with the generic message.
Expected. The server's error text is the tool call result the model receives. Transport and connection failures (callFailed(), listFailed()) should stay hidden from the model, as they are now.
Reproducer. In the demo, give an agent { mcp_server: ... } pointing at a server whose tool throws Mcp\Exception\ToolCallException('No movie with slug "nope".'). The model receives the generic message instead.
A correction to my #2497 comment. The cases I tested there (a missing required argument, a wrong argument type) take a different path. The SDK rejects schema violations as a JSON-RPC -32602 error rather than an isError result, so they arrive as callFailed() and are masked for that reason. Whether invalid-params errors should also reach the model is a separate question, and I've left it out of the fix for this issue.
Follow-up to #2497, split out from my comment there as suggested.
Problem. When an MCP server reports a tool execution error (a
CallToolResultwithisError: true, which is what the SDK sends when a tool throwsMcp\Exception\ToolCallException), the model never sees the server's message. It only gets:MCP puts tool errors inside the result so the model can see them and fix the call. Here the text is lost.
Cause.
McpToolbox::invoke()throwsToolCallException::returnedError(). That exception doesn't implementToolExecutionExceptionInterface, soAbstractToolboxwraps it inToolExecutionException::executionFailed(), andFaultTolerantToolboxreplaces it with the generic message.Expected. The server's error text is the tool call result the model receives. Transport and connection failures (
callFailed(),listFailed()) should stay hidden from the model, as they are now.Reproducer. In the demo, give an agent
{ mcp_server: ... }pointing at a server whose tool throwsMcp\Exception\ToolCallException('No movie with slug "nope".'). The model receives the generic message instead.A correction to my #2497 comment. The cases I tested there (a missing required argument, a wrong argument type) take a different path. The SDK rejects schema violations as a JSON-RPC
-32602error rather than anisErrorresult, so they arrive ascallFailed()and are masked for that reason. Whether invalid-params errors should also reach the model is a separate question, and I've left it out of the fix for this issue.