-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
Codebase: M7
Issue Description:
When we execute MCP Tools it's normal that tool can report errors, for example:
MCP Tool is handling renaming files, we fire this tool and trying to rename a file which don't exist, Tool will report an error.
Current state:
When LLM execute some MCP tool and MCP tool reports error - this will terminate execution flow with following error:
java.lang.IllegalStateException: Error calling tool: [TextContent[audience=null, priority=null, text=file not found]]
at org.springframework.ai.mcp.SyncMcpToolCallback.call(SyncMcpToolCallback.java:117) ~[spring-ai-mcp-1.0.0-M7.jar:1.0.0-M7]
at org.springframework.ai.mcp.SyncMcpToolCallback.call(SyncMcpToolCallback.java:125) ~[spring-ai-mcp-1.0.0-M7.jar:1.0.0-M7]
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCall(DefaultToolCallingManager.java:227) ~[spring-ai-model-1.0.0-M7.jar:1.0.0-M7]
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCalls(DefaultToolCallingManager.java:139) ~[spring-ai-model-1.0.0-M7.jar:1.0.0-M7]
at com.example.demo.MyController.processRequest(MyController.java:234) ~[main/:na]
Expected Behavior:
Error should be processed by ToolExecutionExceptionProcessor and result should be returned to LLM
Issue analysis:
DefaultToolCallingManager should catch this kind of errors but in current code state if MCP Tool calls produce an error they will throw IllegalStateException but DefaultToolCallingManager is expecting ToolExecutionException in case of error in Tool execution.
Reference points:
Lines 204 to 210 in 81b715b
| String toolResult; | |
| try { | |
| toolResult = toolCallback.call(toolInputArguments, toolContext); | |
| } | |
| catch (ToolExecutionException ex) { | |
| toolResult = this.toolExecutionExceptionProcessor.process(ex); | |
| } |
spring-ai/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java
Lines 111 to 120 in 81b715b
| public String call(String functionInput) { | |
| Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput); | |
| // Note that we use the original tool name here, not the adapted one from | |
| // getToolDefinition | |
| CallToolResult response = this.mcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)); | |
| if (response.isError() != null && response.isError()) { | |
| throw new IllegalStateException("Error calling tool: " + response.content()); | |
| } | |
| return ModelOptionsUtils.toJsonString(response.content()); | |
| } |
spring-ai/mcp/common/src/main/java/org/springframework/ai/mcp/AsyncMcpToolCallback.java
Lines 107 to 117 in 81b715b
| public String call(String functionInput) { | |
| Map<String, Object> arguments = ModelOptionsUtils.jsonToMap(functionInput); | |
| // Note that we use the original tool name here, not the adapted one from | |
| // getToolDefinition | |
| return this.asyncMcpClient.callTool(new CallToolRequest(this.tool.name(), arguments)).map(response -> { | |
| if (response.isError() != null && response.isError()) { | |
| throw new IllegalStateException("Error calling tool: " + response.content()); | |
| } | |
| return ModelOptionsUtils.toJsonString(response.content()); | |
| }).block(); | |
| } |