Skip to content

Incorrect Error handling when calling MCP tools #2857

@Ga-Ol-St

Description

@Ga-Ol-St

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:

String toolResult;
try {
toolResult = toolCallback.call(toolInputArguments, toolContext);
}
catch (ToolExecutionException ex) {
toolResult = this.toolExecutionExceptionProcessor.process(ex);
}

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());
}

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();
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions