From ee477213e0e596fb7eb6e700f9b708b3183b9098 Mon Sep 17 00:00:00 2001 From: evo <446796145@qq.com> Date: Mon, 23 Feb 2026 18:21:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96mcp=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 13 +++++ .../main/java/org/ruoyi/agent/McpAgent.java | 45 +++++++-------- .../LangChain4jMcpToolProviderService.java | 57 ++++++++++--------- 3 files changed, 63 insertions(+), 52 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 2a17ad30..ca371e3d 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -216,6 +216,8 @@ springdoc: packages-to-scan: org.ruoyi.generator - group: 5.工作流模块 packages-to-scan: org.ruoyi.workflow + - group: 6.MCP模块 + packages-to-scan: org.ruoyi.mcp # 防止XSS攻击 xss: @@ -357,3 +359,14 @@ knowledge: cache-enabled: true # 缓存过期时间(分钟) cache-expire-minutes: 60 + +--- # MCP 模块配置 +app: + mcp: + client: + # 请求超时时间(秒) + request-timeout: 30 + # 连接超时时间(秒) + connection-timeout: 10 + # 最大重试次数 + max-retries: 3 diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java index 1f5d0c6a..6fc613f2 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/McpAgent.java @@ -7,40 +7,35 @@ import dev.langchain4j.service.V; public interface McpAgent { /** - * 系统提示词:定义智能体身份、核心职责、强制遵守的规则 - * 适配SSE流式特性,明确工具全来自远端MCP服务,仅做代理调用和结果整理 + * 系统提示词:通用工具调用智能体 + * 不限定具体工具类型,让 LangChain4j 自动传递工具描述给 LLM */ @SystemMessage(""" - 你是专业的MCP服务工具代理智能体,核心能力是通过HTTP SSE流式传输协议,调用本地http://localhost:8085/sse地址上MCP服务端注册的所有工具。 - 你的核心工作职责: - 1. 准确理解用户的自然语言请求,判断需要调用MCP服务端的哪一个/哪些工具; - 2. 通过绑定的工具提供者,向MCP服务端发起工具调用请求,传递完整的工具执行参数; - 3. 实时接收MCP服务端通过SSE流式返回的工具执行结果,保证结果片段的完整性; - 4. 将流式结果按原始顺序整理为清晰、易懂的自然语言答案,返回给用户。 + 你是一个AI助手,可以通过调用各种工具来帮助用户完成不同的任务。 - 【强制遵守的核心规则 - 无例外】 - 1. 所有工具调用必须通过远端MCP服务执行,严禁尝试本地执行任何业务逻辑; - 2. 处理SSE流式结果时,严格保留结果片段的返回顺序,不得打乱或遗漏; - 3. 若MCP服务返回错误(如工具未找到、参数错误、执行失败),直接将错误信息友好反馈给用户,无需额外推理; - 4. 工具执行结果若为结构化数据(如JSON、表格),需格式化后返回,提升可读性。 + 【工具使用规则】 + 1. 根据用户的请求,判断需要使用哪些工具 + 2. 仔细阅读每个工具的描述,确保理解工具的功能和参数要求 + 3. 使用正确的参数调用工具 + 4. 如果工具执行失败,向用户友好地说明错误原因,并尝试提供替代方案 + 5. 对于复杂任务,可以分步骤使用多个工具完成 + 6. 将工具执行结果以清晰易懂的方式呈现给用户 + + 【响应格式】 + - 直接回答用户的问题 + - 如果使用了工具,说明使用了什么工具以及结果 + - 如果遇到错误,提供友好的错误信息和解决建议 """) - /** - * 用户消息模板:{{query}}为参数占位符,与方法入参的@V("query")绑定 - */ @UserMessage(""" - 请通过调用MCP服务端的工具,处理用户的以下请求: {{query}} """) + + @Agent("通用工具调用智能体") /** - * 智能体标识:用于日志打印、监控追踪、多智能体协作时的身份识别 - */ - @Agent("MCP服务SSE流式代理智能体-连接本地8085端口") - /** - * 智能体对外调用入口方法 - * @param query 用户的自然语言请求(如:生成订单数据柱状图、查询今日天气) - * @V("query") 将方法入参值绑定到@UserMessage的{{query}}占位符中 - * @return 整理后的MCP工具执行结果(流式结果会自动拼接为完整字符串) + * 智能体对外调用入口 + * @param query 用户的自然语言请求 + * @return 处理结果 */ String callMcpTool(@V("query") String query); } diff --git a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java b/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java index 7abc9fd9..71a853f3 100644 --- a/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java +++ b/ruoyi-modules/ruoyi-mcp/src/main/java/org/ruoyi/mcp/service/core/LangChain4jMcpToolProviderService.java @@ -100,6 +100,7 @@ public class LangChain4jMcpToolProviderService { public ToolProvider getAllEnabledToolsProvider() { List enabledTools = mcpToolMapper.selectList( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .ne(McpTool::getType,"BUILTIN") .eq(McpTool::getStatus, McpToolStatus.ENABLED.getValue()) ); @@ -144,47 +145,49 @@ public class LangChain4jMcpToolProviderService { /** * 获取或创建 MCP Client - * 包含健康检查和失败重试逻辑 */ private McpClient getOrCreateClient(Long toolId) { // 检查工具是否被禁用 if (isToolDisabled(toolId)) { - log.warn("Tool {} is temporarily disabled due to previous failures", toolId); + log.warn("Tool {} is temporarily disabled", toolId); return null; } - // 尝试从缓存获取 + // 返回缓存的客户端 McpClient cachedClient = activeClients.get(toolId); if (cachedClient != null && isToolHealthy(toolId)) { return cachedClient; } - // 创建新的客户端 - return activeClients.compute(toolId, (id, existingClient) -> { - McpTool tool = mcpToolMapper.selectById(id); - if (tool == null || !McpToolStatus.isEnabled(tool.getStatus())) { - return null; - } + // 查询工具配置 + McpTool tool = mcpToolMapper.selectById(toolId); + if (tool == null || !McpToolStatus.isEnabled(tool.getStatus())) { + return null; + } - // 跳过内置工具(BUILTIN 类型) - if ("BUILTIN".equals(tool.getType())) { - log.debug("Skipping builtin tool: {}", tool.getName()); - return null; - } + String toolType = tool.getType(); + // 只支持 LOCAL 和 REMOTE 类型 + if (!ToolProviderFactory.TYPE_LOCAL.equals(toolType) && !ToolProviderFactory.TYPE_REMOTE.equals(toolType)) { + log.warn("Unsupported tool type: {} for tool: {}", toolType, tool.getName()); + return null; + } - try { - McpClient client = createMcpClient(tool); - // 标记工具为健康状态 - markToolHealthy(id); - log.info("Successfully created LangChain4j MCP client for tool: {}", tool.getName()); - return client; - } catch (Exception e) { - log.error("Failed to create MCP client for tool {}: {}", tool.getName(), e.getMessage()); - // 记录失败并可能禁用工具 - handleToolFailure(id); + // 创建并缓存新客户端 + try { + McpClient client = createMcpClient(tool); + if (client == null) { + log.warn("Failed to create MCP client for tool: {}", tool.getName()); return null; } - }); + activeClients.put(toolId, client); + markToolHealthy(toolId); + log.info("Created MCP client for tool: {}", tool.getName()); + return client; + } catch (Exception e) { + log.error("Failed to create MCP client for tool {}: {}", tool.getName(), e.getMessage(), e); + handleToolFailure(toolId); + return null; + } } /** @@ -360,11 +363,11 @@ public class LangChain4jMcpToolProviderService { JsonNode configNode = objectMapper.readTree(configJson); - if (!configNode.has("baseUrl")) { + if (!configNode.has("url")) { throw new IllegalArgumentException("baseUrl is required in config JSON for REMOTE type tool"); } - String baseUrl = configNode.get("baseUrl").asText(); + String baseUrl = configNode.get("url").asText(); log.info("Creating HTTP/SSE MCP client for tool: {}, baseUrl: {}", tool.getName(), baseUrl); // 创建 HTTP/SSE 传输层