spring ai mcp tools 发生错误不能自动发送错误给大模型
问题背景
本来想给官方提交PR 但是发现是一个小bug 在issues 上回复下希望官方尽快解决
- 版本:spring ai 1.0.0-M7
- issues 链接
- gihub账号
- down-to-earth1994
- 最近开发mcp tools 时候返现spring-ai/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java 捕获的是ToolExecutionException
spring-ai/mcp/common/src/main/java/org/springframework/ai/mcp/SyncMcpToolCallback.java call 方法抛出的是IllegalStateException 导致程序终止 无法继续把错误消息发送给大模型
临时解决方案
- 自定义CustomSyncMcpToolCallback 和CustomSyncMcpToolCallbackProvider 重写call和getToolCallbacks方法
- 构建的chatModel的时候使用CustomSyncMcpToolCallbackProvider提供tools
package com.pig4cloud.pig.mcp.client.chat;
import cn.hutool.json.JSONUtil;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.SyncMcpToolCallback;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.tool.definition.DefaultToolDefinition;
import org.springframework.ai.tool.execution.ToolExecutionException;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomSyncMcpToolCallback extends SyncMcpToolCallback {
private final McpSyncClient mcpClient;
private final McpSchema.Tool tool;
/**
* Creates a new {@code SyncMcpToolCallback} instance.
*
* @param mcpClient the MCP client to use for tool execution
* @param tool the MCP tool definition to adapt
*/
public CustomSyncMcpToolCallback(McpSyncClient mcpClient, McpSchema.Tool tool) {
super(mcpClient, tool);
this.mcpClient = mcpClient;
this.tool = tool;
}
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
McpSchema.CallToolResult response = this.mcpClient.callTool(new McpSchema.CallToolRequest(this.tool.name(), arguments));
if (response.isError() != null && response.isError()) {
log.warn("tools exec response error: {}", response);
throw new ToolExecutionException(new DefaultToolDefinition(tool.name(),tool.description(), JSONUtil.toJsonStr(tool.inputSchema())),new IllegalStateException("Error calling tool: " + response.content()));
}
return ModelOptionsUtils.toJsonString(response.content());
}
}
package com.pig4cloud.pig.mcp.client.chat;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.util.ToolUtils;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
public class CustomSyncMcpToolCallbackProvider extends SyncMcpToolCallbackProvider {
private final List<McpSyncClient> mcpClients;
private final BiPredicate<McpSyncClient, McpSchema.Tool> toolFilter;
/**
* Constructor for CustomSyncMcpToolCallbackProvider with tool filter.
*
* @param toolFilter The filter to apply to tools.
* @param mcpClients The list of MCP clients.
*/
public CustomSyncMcpToolCallbackProvider(BiPredicate<McpSyncClient, McpSchema.Tool> toolFilter, List<McpSyncClient> mcpClients) {
Assert.notNull(mcpClients, "MCP clients must not be null");
Assert.notNull(toolFilter, "Tool filter must not be null");
this.mcpClients = mcpClients;
this.toolFilter = toolFilter;
}
/**
* Constructor for CustomSyncMcpToolCallbackProvider without tool filter.
*
* @param mcpClients The list of MCP clients.
*/
public CustomSyncMcpToolCallbackProvider(List<McpSyncClient> mcpClients) {
this((mcpClient, tool) -> true, mcpClients);
}
/**
* Get the tool callbacks.
*
* @return An array of ToolCallback objects.
*/
@Override
public ToolCallback[] getToolCallbacks() {
var toolCallbacks = new ArrayList<>();
this.mcpClients.stream().forEach(mcpClient -> {
toolCallbacks.addAll(mcpClient.listTools()
.tools()
.stream()
.filter(tool -> toolFilter.test(mcpClient, tool))
.map(tool -> new CustomSyncMcpToolCallback(mcpClient, tool))
.toList());
});
var array = toolCallbacks.toArray(new ToolCallback[0]);
validateToolCallbacks(array);
return array;
}
/**
* Validate the tool callbacks to ensure there are no duplicate tool names.
*
* @param toolCallbacks An array of ToolCallback objects.
* @throws IllegalStateException if there are duplicate tool names.
*/
private void validateToolCallbacks(ToolCallback[] toolCallbacks) {
List<String> duplicateToolNames = ToolUtils.getDuplicateToolNames(toolCallbacks);
if (!duplicateToolNames.isEmpty()) {
throw new IllegalStateException(
"Multiple tools with the same name (%s)".formatted(String.join(", ", duplicateToolNames)));
}
}
}

浙公网安备 33010602011771号