diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java new file mode 100644 index 00000000..e4fac002 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcRequest.java @@ -0,0 +1,32 @@ +package xiaozhi.modules.agent.dto; + +import lombok.Data; + +/** + * MCP JSON-RPC 请求 DTO + */ +@Data +public class McpJsonRpcRequest { + private String jsonrpc = "2.0"; + private String method; + private Object params; + private Integer id; + + public McpJsonRpcRequest() { + } + + public McpJsonRpcRequest(String method) { + this.method = method; + } + + public McpJsonRpcRequest(String method, Object params, Integer id) { + this.method = method; + this.params = params; + this.id = id; + } + + public McpJsonRpcRequest(String method, Object params) { + this.method = method; + this.params = params; + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java new file mode 100644 index 00000000..1bd667e6 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/McpJsonRpcResponse.java @@ -0,0 +1,48 @@ +package xiaozhi.modules.agent.dto; + +import lombok.Data; + +/** + * MCP JSON-RPC 响应 DTO + */ +@Data +public class McpJsonRpcResponse { + private String jsonrpc = "2.0"; + private Integer id; + private McpResult result; + private McpError error; + + public McpJsonRpcResponse() { + } + + @Data + public static class McpResult { + private String type; + private String message; + private String agent_id; + private McpTool[] tools; + + public McpResult() { + } + } + + @Data + public static class McpTool { + private String name; + private String description; + private Object inputSchema; + + public McpTool() { + } + } + + @Data + public static class McpError { + private Integer code; + private String message; + private Object data; + + public McpError() { + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java index 13d003a5..e45d2201 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentMcpAccessPointServiceImpl.java @@ -2,7 +2,11 @@ package xiaozhi.modules.agent.service.impl; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -12,8 +16,12 @@ import lombok.extern.slf4j.Slf4j; import xiaozhi.common.constant.Constant; import xiaozhi.common.utils.AESUtils; import xiaozhi.common.utils.HashEncryptionUtil; +import xiaozhi.common.utils.JsonUtils; +import xiaozhi.modules.agent.dto.McpJsonRpcRequest; +import xiaozhi.modules.agent.dto.McpJsonRpcResponse; import xiaozhi.modules.agent.service.AgentMcpAccessPointService; import xiaozhi.modules.sys.service.SysParamsService; +import xiaozhi.modules.sys.utils.WebSocketClientManager; @AllArgsConstructor @Service @@ -35,8 +43,10 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic String key = getSecretKey(uri); // 获取加密的token String encryptToken = encryptToken(id, key); + // 对token进行URL编码 + String encodedToken = URLEncoder.encode(encryptToken, StandardCharsets.UTF_8); // 返回智能体Mcp路径的格式 - agentMcpUrl = "%s/mcp?token=%s".formatted(agentMcpUrl, encryptToken); + agentMcpUrl = "%s/mcp/?token=%s".formatted(agentMcpUrl, encodedToken); return agentMcpUrl; } @@ -46,7 +56,65 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic if (StringUtils.isBlank(wsUrl)) { return List.of(); } - return List.of(); + + // 将 /mcp 替换为 /call + wsUrl = wsUrl.replace("/mcp/", "/call/"); + + try { + // 创建 WebSocket 连接 + try (WebSocketClientManager client = WebSocketClientManager.build( + new WebSocketClientManager.Builder() + .uri(wsUrl) + .connectTimeout(5, TimeUnit.SECONDS) + .maxSessionDuration(20, TimeUnit.SECONDS))) { + + // 发送初始化通知 + McpJsonRpcRequest initRequest = new McpJsonRpcRequest("notifications/initialized"); + client.sendJson(initRequest); + + // 等待 0.2 秒 + Thread.sleep(200); + + // 发送工具列表请求 + McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 1); + client.sendJson(toolsRequest); + + // 监听响应,直到收到包含 id=1 的响应 + List responses = client.listener(response -> { + try { + McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class); + return jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId()); + } catch (Exception e) { + log.warn("解析响应失败: {}", response, e); + return false; + } + }); + + // 处理响应 + for (String response : responses) { + try { + McpJsonRpcResponse jsonResponse = JsonUtils.parseObject(response, McpJsonRpcResponse.class); + if (jsonResponse != null && Integer.valueOf(1).equals(jsonResponse.getId()) + && jsonResponse.getResult() != null && jsonResponse.getResult().getTools() != null) { + + // 提取工具名称列表 + return java.util.Arrays.stream(jsonResponse.getResult().getTools()) + .map(McpJsonRpcResponse.McpTool::getName) + .collect(Collectors.toList()); + } + } catch (Exception e) { + log.warn("处理工具列表响应失败: {}", response, e); + } + } + + log.warn("未找到有效的工具列表响应"); + return List.of(); + + } + } catch (Exception e) { + log.error("获取智能体 MCP 工具列表失败,智能体ID: {}", id, e); + return List.of(); + } } /**