zhou zhou
8 小时以前 82624affb0251b75b62b35567d3eb260c06efe78
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
@@ -2,7 +2,9 @@
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto;
import com.vincent.rsf.server.ai.entity.AiMcpMount;
import com.vincent.rsf.server.ai.service.impl.mcp.BuiltinMcpToolCatalogProvider;
import com.vincent.rsf.server.ai.service.BuiltinMcpToolRegistry;
import com.vincent.rsf.server.ai.tool.RsfWmsBaseTools;
import com.vincent.rsf.server.ai.tool.RsfWmsStockTools;
@@ -15,7 +17,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
@@ -24,46 +28,72 @@
    private final RsfWmsStockTools rsfWmsStockTools;
    private final RsfWmsTaskTools rsfWmsTaskTools;
    private final RsfWmsBaseTools rsfWmsBaseTools;
    private final BuiltinMcpToolCatalogProvider builtinMcpToolCatalogProvider;
    /**
     * 校验内置 MCP 编码是否合法。
     * 当前版本只允许使用显式登记在注册表中的编码,未知编码直接拒绝,
     * 这样可以确保“页面可选项”和“运行时可挂载项”始终一致。
     */
    @Override
    public void validateBuiltinCode(String builtinCode) {
        if (!StringUtils.hasText(builtinCode)) {
            throw new CoolException("内置 MCP 编码不能为空");
        }
        if (!supportedBuiltinCodes().contains(builtinCode)) {
        if (!builtinMcpToolCatalogProvider.supportedBuiltinCodes().contains(builtinCode)) {
            throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
        }
    }
    /**
     * 根据挂载记录创建内置工具回调。
     * 这里不会做任何动态发现,所有工具都必须经过显式注册和治理目录校验后才能暴露给模型。
     */
    @Override
    public List<ToolCallback> createToolCallbacks(AiMcpMount mount, Long userId) {
        String builtinCode = mount.getBuiltinCode();
        validateBuiltinCode(builtinCode);
        if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) {
            List<ToolCallback> callbacks = new ArrayList<>();
            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsStockTools)));
            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools)));
            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools)));
            callbacks.addAll(createValidatedCallbacks(rsfWmsStockTools, builtinCode));
            callbacks.addAll(createValidatedCallbacks(rsfWmsTaskTools, builtinCode));
            callbacks.addAll(createValidatedCallbacks(rsfWmsBaseTools, builtinCode));
            return callbacks;
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)) {
            return Arrays.asList(ToolCallbacks.from(rsfWmsStockTools));
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) {
            return Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools));
        }
        if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) {
            return Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools));
        }
        throw new CoolException("不支持的内置 MCP 编码: " + builtinCode);
    }
    private List<String> supportedBuiltinCodes() {
        return List.of(
                AiDefaults.MCP_BUILTIN_RSF_WMS,
                AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK,
                AiDefaults.MCP_BUILTIN_RSF_WMS_TASK,
                AiDefaults.MCP_BUILTIN_RSF_WMS_BASE
        );
    /**
     * 返回某个内置编码下可预览的工具目录信息。
     * 该目录比运行时回调多了工具用途、查询边界和示例提问,供管理页展示。
     */
    @Override
    public List<AiMcpToolPreviewDto> listBuiltinToolCatalog(String builtinCode) {
        validateBuiltinCode(builtinCode);
        return new ArrayList<>(builtinMcpToolCatalogProvider.getCatalog(builtinCode).values());
    }
    private List<ToolCallback> createValidatedCallbacks(Object toolBean, String builtinCode) {
        /**
         * 把 `@Tool` Bean 转成 Spring AI ToolCallback,并强制校验:
         * 1. 工具名必须符合命名规范
         * 2. 每个工具都必须出现在治理目录里
         */
        List<ToolCallback> callbacks = Arrays.asList(ToolCallbacks.from(toolBean));
        Map<String, AiMcpToolPreviewDto> catalog = builtinMcpToolCatalogProvider.getCatalog(builtinCode);
        for (ToolCallback callback : callbacks) {
            if (callback == null || callback.getToolDefinition() == null) {
                continue;
            }
            String toolName = callback.getToolDefinition().name();
            if (!StringUtils.hasText(toolName) || !toolName.startsWith("rsf_query_")) {
                throw new CoolException("内置工具命名不符合规范,必须以 rsf_query_ 开头: " + toolName);
            }
            if (!catalog.containsKey(toolName)) {
                throw new CoolException("内置工具缺少治理目录配置: " + toolName);
            }
        }
        return callbacks;
    }
}