MCP Server多节点滚动升级一致性治理
飞书云文档原链接地址:https://ik3te1knhq.feishu.cn/wiki/W8ctwG2sAiPkrXkpl7ocP0g0njf
[!TIP]
MCP Server 多节点部署时,滚动发布,MCP Client 侧使用的 Client 连接保证使用的是最新的工具配置信息
- 后续推进:按比例使用旧、新实例
给社区贡献代码:https://github.com/alibaba/spring-ai-alibaba/pull/837
example 示例贡献代码:https://github.com/springaialibaba/spring-ai-alibaba-examples/pull/185
Nacos 新增命名空间
- 命名空间名称:nacos-default-mcp
- 命名空格 ID:9ba5f1aa-b37d-493b-9057-72918a40ef35
Mcp server 端 yml
spring:_ _ai:mcp:server:name: mcp-server-providerversion: 1.0.1
_ sse-message-endpoint: /mcp/messages_
_ _type: _SYNC_
_ _alibaba:mcp:nacos:enabled: trueservice-namespace: 9ba5f1aa-b37d-493b-9057-72918a40ef35server-addr: 127.0.0.1:8848username: nacospassword: nacos
上面主要关注两个配置:
- 服务名称:mcp-server-provider
- 服务的命名空间:注意是填写命名空间 ID,9ba5f1aa-b37d-493b-9057-72918a40ef35
Mcp Server 端侧实际工作
- 服务名称修改:MCP Server 服务名称 +“-mcp-service”
- 推送配置管理
Data Id | Group | 配置内容 | |
工具 | 服务名称 + “-mcp-tools.json” | mcp-tools | { "tools" : [ { "name" : "getCiteTimeMethod", "description" : "获取指定时区的时间", "inputSchema" : { "type" : "object", "properties" : { "timeZoneId" : { "type" : "string", "description" : "ime zone id, such as Asia/Shanghai" } }, "required" : [ "timeZoneId" ], "additionalProperties" : false } } ], "toolsMeta" : { "getCiteTimeMethod" : { "enabled" : true } } } |
Mcp Server | 服务名称 + “-mcp-server.json” | mcp-server | { "protocol" : "mcp-sse", "name" : "mcp-server-provider", "description" : "mcp-server-provider", "version" : "1.0.1", "enabled" : true, "remoteServerConfig" : { "serviceRef" : { "namespaceId" : "9ba5f1aa-b37d-493b-9057-72918a40ef35", "groupName" : "mcp-server", "serviceName" : "mcp-server-provider-mcp-service" }, "exportPath" : "/sse" }, "toolsDescriptionRef" : "mcp-server-provider-mcp-tools.json" } |
- 注册 Mcp Server 实例到 Nacos 中,元数据新增字段 server.md5、tools.names
- server.md5:当前实例配置管理的 md5 值
- tools.names:当前实例所配置的工具名称
Mcp Client 侧 yml 文件
spring:
_ _ai:alibaba:mcp:nacos:enabled: trueservice-namespace: 9ba5f1aa-b37d-493b-9057-72918a40ef35server-addr: 127.0.0.1:8848username: nacospassword: nacosclient:sse:connections:server1: mcp-server-provider
上面主要关注配置:
-
命名空间:注意填写 9ba5f1aa-b37d-493b-9057-72918a40ef35,需要在该命名空间中发现 Mcp Server
-
MCP Server 服务名称:mcp-server-provider
-
根据命名规则,自动从 nacos 中获取相关配置
- 服务名称:Mcp Server 服务名称 +“-mcp-service”
- 服务配置管理:Mcp Server 服务名称 + “-mcp-server.json”
- 服务分组:mcp-server
- 工具配置管理:Mcp Server 服务名称 + “-mcp-tool.json”
- 工具分组:mcp-tools
Mcp Client 端侧实际工作
新增三个字段
Map<String, List<String>> md5ToToolsMap
:server.md5 到工具名称列表的映射Map<String, List<McpAsyncClient>> md5ToClientMap
:server.md5 到 Client 到映射Map<String, Integer> client2CountMap
:存储每个 Client 的调用次数
listTools 动作
直接获取 Nacos 中配置文件里的工具信息
nacosConfigService.getConfig(this.serviceName + McpNacosConstant._TOOLS_CONFIG_SUFFIX_, McpNacosConstant._TOOLS_GROUP_, TIME_OUT_MS);
callTool 动作
- 根据工具名称找到对应的 server.md5,判断哪些节点提供该工具
- 由 server.md5 得到对应的 client
- 通过
client2CountMap
选择调用次数最少的 client 发起 callTool 动作
public Mono<McpSchema.CallToolResult> callTool(McpSchema.CallToolRequest callToolRequest) {String toolName = callToolRequest.name();List<McpAsyncClient> aysnClients = new ArrayList<>();md5ToToolsMap.forEach((md5, tools) -> {if (tools.contains(toolName)) {aysnClients.addAll(md5ToClientMap.get(md5));}});Set<String> clientInfos = aysnClients.stream().map(client -> client.getClientInfo().name()).collect(Collectors._toSet_());String minClientInfoName = clientInfos.stream().min(Comparator._comparingInt_(clientInfo ->client2CountMap.getOrDefault(clientInfo, 0))).get();client2CountMap.put(minClientInfoName, client2CountMap.get(minClientInfoName) + 1);McpAsyncClient mcpAsyncClient = aysnClients.stream().filter(aysnClient -> aysnClient.getClientInfo().name().equals(minClientInfoName)).findFirst().get();return mcpAsyncClient.callTool(callToolRequest);
}
动态监听节点上下线
获取其中一个 client 端逻辑
public McpAsyncClient getMcpAsyncClient() {List<McpAsyncClient> aysnClients = getMcpAsyncClientList();if (aysnClients.isEmpty()) {throw new IllegalStateException("No McpAsyncClient available");}// 从client2CountMap中挑选value最小的键是哪个String clientInfoName = client2CountMap.entrySet().stream().min(Map.Entry._comparingByValue_()).map(Map.Entry::getKey).get();client2CountMap.put(clientInfoName, client2CountMap.get(clientInfoName) + 1);// 从clients中找到clientInfoName对应的clientreturn aysnClients.stream().filter(aysnClient -> aysnClient.getClientInfo().name().equals(clientInfoName)).findFirst().get();
}public List<McpAsyncClient> getMcpAsyncClientList() {return md5ToClientMap.values().stream().flatMap(List::stream).toList();
}
效果演示
在 nacos 中注册新的 nacos-default-mcp 命名空间
Mcp Server 端注册注册实例
配置管理的工具、Mcp Server 信息
触发工具
打包 mavn 包