mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 22:53:56 +08:00
Update:优化mcp工具读取顺序 (#1775)
* update:发布0.6.3版本 * update:升级sherpa_onnx版本 * update:补充自动更新教程 * update:优化mcp工具读取顺序 * update:优化
This commit is contained in:
@@ -174,3 +174,4 @@ main/xiaozhi-server/mysql
|
||||
uploadfile
|
||||
*.json
|
||||
.vscode
|
||||
.cursor
|
||||
|
||||
+69
-30
@@ -61,14 +61,15 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
wsUrl = wsUrl.replace("/mcp/", "/call/");
|
||||
|
||||
try {
|
||||
// 创建 WebSocket 连接
|
||||
// 创建 WebSocket 连接,增加超时时间到15秒
|
||||
try (WebSocketClientManager client = WebSocketClientManager.build(
|
||||
new WebSocketClientManager.Builder()
|
||||
.uri(wsUrl)
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.maxSessionDuration(9, TimeUnit.SECONDS))) {
|
||||
.connectTimeout(8, TimeUnit.SECONDS)
|
||||
.maxSessionDuration(10, TimeUnit.SECONDS))) {
|
||||
|
||||
// 发送初始化消息
|
||||
// 步骤1: 发送初始化消息并等待响应
|
||||
log.info("发送MCP初始化消息,智能体ID: {}", id);
|
||||
McpJsonRpcRequest initializeRequest = new McpJsonRpcRequest("initialize",
|
||||
Map.of(
|
||||
"protocolVersion", "2024-11-05",
|
||||
@@ -81,37 +82,70 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
1);
|
||||
client.sendJson(initializeRequest);
|
||||
|
||||
// 等待初始化响应
|
||||
Thread.sleep(200);
|
||||
|
||||
// 发送初始化完成通知
|
||||
// 对于通知类型的消息,手动构建JSON以避免包含null字段
|
||||
String notificationJson = "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}";
|
||||
client.sendText(notificationJson);
|
||||
|
||||
// 等待 0.2 秒
|
||||
Thread.sleep(200);
|
||||
|
||||
// 发送工具列表请求
|
||||
McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 2);
|
||||
client.sendJson(toolsRequest);
|
||||
|
||||
// 监听响应,直到收到包含 id=2 的响应(tools/list响应)
|
||||
List<String> responses = client.listener(response -> {
|
||||
// 等待初始化响应 (id=1) - 移除固定延迟,改为响应驱动
|
||||
List<String> initResponses = client.listenerWithoutClose(response -> {
|
||||
try {
|
||||
// 先尝试解析为通用JSON对象来获取id
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"));
|
||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||
// 检查是否有result字段,表示初始化成功
|
||||
return jsonMap.containsKey("result") && !jsonMap.containsKey("error");
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.warn("解析响应失败: {}", response, e);
|
||||
log.warn("解析初始化响应失败: {}", response, e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理响应
|
||||
for (String response : responses) {
|
||||
// 验证初始化响应
|
||||
boolean initSucceeded = false;
|
||||
for (String response : initResponses) {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||
if (jsonMap.containsKey("result")) {
|
||||
log.info("MCP初始化成功,智能体ID: {}", id);
|
||||
initSucceeded = true;
|
||||
break;
|
||||
} else if (jsonMap.containsKey("error")) {
|
||||
log.error("MCP初始化失败,智能体ID: {}, 错误: {}", id, jsonMap.get("error"));
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("处理初始化响应失败: {}", response, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!initSucceeded) {
|
||||
log.error("未收到有效的MCP初始化响应,智能体ID: {}", id);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 步骤2: 发送初始化完成通知 - 只有在收到initialize响应后才发送
|
||||
log.info("发送MCP初始化完成通知,智能体ID: {}", id);
|
||||
String notificationJson = "{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}";
|
||||
client.sendText(notificationJson);
|
||||
|
||||
// 步骤3: 发送工具列表请求 - 立即发送,无需额外延迟
|
||||
log.info("发送MCP工具列表请求,智能体ID: {}", id);
|
||||
McpJsonRpcRequest toolsRequest = new McpJsonRpcRequest("tools/list", null, 2);
|
||||
client.sendJson(toolsRequest);
|
||||
|
||||
// 等待工具列表响应 (id=2)
|
||||
List<String> toolsResponses = client.listener(response -> {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"));
|
||||
} catch (Exception e) {
|
||||
log.warn("解析工具列表响应失败: {}", response, e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理工具列表响应
|
||||
for (String response : toolsResponses) {
|
||||
try {
|
||||
// 先解析为通用JSON对象
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
if (jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"))) {
|
||||
// 检查是否有result字段
|
||||
@@ -122,11 +156,16 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
if (toolsObj instanceof List) {
|
||||
List<Map<String, Object>> toolsList = (List<Map<String, Object>>) toolsObj;
|
||||
// 提取工具名称列表
|
||||
return toolsList.stream()
|
||||
List<String> result = toolsList.stream()
|
||||
.map(tool -> (String) tool.get("name"))
|
||||
.filter(name -> name != null)
|
||||
.collect(Collectors.toList());
|
||||
log.info("成功获取MCP工具列表,智能体ID: {}, 工具数量: {}", id, result.size());
|
||||
return result;
|
||||
}
|
||||
} else if (jsonMap.containsKey("error")) {
|
||||
log.error("获取工具列表失败,智能体ID: {}, 错误: {}", id, jsonMap.get("error"));
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -134,7 +173,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("未找到有效的工具列表响应");
|
||||
log.warn("未找到有效的工具列表响应,智能体ID: {}", id);
|
||||
return List.of();
|
||||
|
||||
}
|
||||
@@ -204,4 +243,4 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
// 加密后成token值
|
||||
return AESUtils.encrypt(key, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,37 @@ public class WebSocketClientManager implements Closeable {
|
||||
return collected;
|
||||
}
|
||||
|
||||
private <T> List<T> listenerCustomWithoutClose(
|
||||
BlockingQueue<T> queue,
|
||||
Predicate<T> predicate)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
List<T> collected = new ArrayList<>();
|
||||
long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration);
|
||||
|
||||
while (true) {
|
||||
if (errorFuture.isDone()) {
|
||||
errorFuture.get();
|
||||
}
|
||||
|
||||
long remaining = deadline - System.currentTimeMillis();
|
||||
if (remaining <= 0) {
|
||||
throw new TimeoutException("等待批量消息超时");
|
||||
}
|
||||
|
||||
T msg = queue.poll(remaining, TimeUnit.MILLISECONDS);
|
||||
if (msg == null) {
|
||||
throw new TimeoutException("等待批量消息超时");
|
||||
}
|
||||
|
||||
collected.add(msg);
|
||||
if (predicate.test(msg)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 不调用 close(),保持连接开放
|
||||
return collected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步接收多条消息,直到 predicate 为 true 或超时抛异常;
|
||||
*
|
||||
@@ -147,6 +178,17 @@ public class WebSocketClientManager implements Closeable {
|
||||
return listenerCustom(textMessageQueue, predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步接收多条消息,直到 predicate 为 true 或超时抛异常;
|
||||
* 不自动关闭连接,适用于需要在同一连接上发送多个消息的场景
|
||||
*
|
||||
* @return 返回监听期间的所有消息列表
|
||||
*/
|
||||
public List<String> listenerWithoutClose(Predicate<String> predicate)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
return listenerCustomWithoutClose(textMessageQueue, predicate);
|
||||
}
|
||||
|
||||
public List<byte[]> listenerBinary(Predicate<byte[]> predicate)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
return listenerCustom(binaryMessageQueue, predicate);
|
||||
|
||||
Reference in New Issue
Block a user