spring ai mcp tools 发生错误不能自动发送错误给大模型

问题背景

本来想给官方提交PR 但是发现是一个小bug 在issues 上回复下希望官方尽快解决

  1. 版本:spring ai 1.0.0-M7
  1. 最近开发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 导致程序终止 无法继续把错误消息发送给大模型

临时解决方案

  1. 自定义CustomSyncMcpToolCallback 和CustomSyncMcpToolCallbackProvider 重写call和getToolCallbacks方法
  2. 构建的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)));
        }
    }
}


posted @ 2025-04-25 14:09  贺艳峰  阅读(610)  评论(0)    收藏  举报