主题
Nacos 与 AI:MCP 与 Agent 注册
一句话:Nacos 3.x 把「服务注册」扩展成「AI 资产注册」:MCP Server/Tool、A2A Agent、Prompt/Skill 都登记到同一个控制面,解决工具和 Agent 的动态发现与治理;它不托管模型、不执行推理,真正调用仍走网关或工具服务。
适用版本:Nacos 3.2.4。MCP Registry 自 3.0(2025-04)引入,A2A Agent Registry 自 3.1,Skill/Prompt Registry 在 3.2 补齐;3.3 起 MCP 发布流程改为 draft/review/publish,接口可能变化。以下 API 形态以 3.2.x 为例,落地前回查对应版本文档。
为什么需要
Agent 调工具时,最常见的第一步不是「模型不够聪明」,而是「工具地址写死」:
- 工具服务扩缩容、换域名,Agent 的配置要跟着改。
- 同一个工具多个版本,Agent 不知道该调哪个。
- 多租户环境下,A 租户不该看到 B 租户的工具。
- 工具描述和参数 schema 散落在各项目,模型每次都要全量加载,token 浪费且容易选错。
传统注册中心只存「服务名 + ip:port + metadata」,不知道「这个服务暴露了哪些工具、参数是什么、属于哪个版本」。MCP Registry 补的就是这层元数据;A2A Agent Registry 补的是「Agent 之间怎么按名称、描述、能力互相发现」。
核心机制
控制面与数据面
图里 Nacos 只负责「登记与发现」,不转发工具调用;真正的 MCP 请求走 Higress/MCP Router 或直连工具服务。所以 Nacos 挂了,已经拿到地址的 Agent 仍可能调用成功,但发现不了新工具、拿不到新版本;这和注册中心「只发名单」是同一个道理。
注册对象
| 对象 | 关键字段 | 解决什么 | 版本说明 |
|---|---|---|---|
| MCP Server | 名称、协议(sse/streamable)、版本、endpoint 引用 | 工具服务在哪、用什么协议 | 3.0+ |
| Tool | 名称、描述、inputSchema、启用开关 | 模型怎么选、怎么传参 | 3.0+ |
| Prompt | 模板内容、版本、可见性 | Prompt 集中管理与热更新 | 3.2 |
| A2A Agent | AgentCard:名称、描述、能力、端点 | Agent 之间按能力发现 | 3.1+ |
| Skill | 技能描述与绑定 | 把可复用能力登记为资产 | 3.2 |
MCP Server 的 endpoint 通常不是直接写 ip:port,而是引用一个已注册的服务(REF):服务发现负责地址变化,MCP 元数据负责工具语义。这样扩缩容时只动服务注册,不用改工具定义。
命名空间、可见性与鉴权
- Namespace 隔离环境与租户;MCP Server、Agent、Prompt 都挂在 Namespace 下。
- 可见性:新发布的 MCP Server 默认
PUBLIC,可通过 Admin API 改为PRIVATE;跨租户共享要显式设置。 - 鉴权:3.x 的 Admin/Console API 默认开启鉴权,客户端 OpenAPI 的开关独立;不要把 Registry 暴露到公网。
- 私有地址:3.2.4 对从私有/本地地址导入 MCP 增加白名单(
nacos.console.ai.mcp.import.allowed-private-addresses),防止 SSRF 式导入。
版本、灰度与生命周期
MCP Server 和 Tool 都有版本;更新工具描述或 schema 后,客户端下次发现就能拿到新版本,不需要重启 Agent。3.2 支持灰度/版本管理;3.3 改成 draft → review → publish,旧的一步发布接口仍兼容一段时间,但不要把它当长期契约。
这也带来一个新问题:工具 schema 是运行时契约。改一个必填参数,可能让所有已缓存的 Agent 调用失败;发布前要像改 API 一样做兼容性评估,而不是像改文案一样随手发。
AI 带来的新负载
Registry 的读写比例和传统注册中心不同:服务元数据是「写少读多」,工具元数据则是「写少、读更分散」——每个 Agent 启动/刷新都要拉一批工具描述,描述本身又比 ip:port 大得多。它通常不是高 QPS 系统,但单次响应体积大、发现延迟直接影响 Agent 首轮响应;不要把每次工具调用的结果、会话状态写进 Registry,那里只放「有哪些工具、怎么调」的静态契约。
什么时候真的需要它
只有一两个固定工具、地址不变、单租户,直接写在 Agent 配置里更简单;需要动态扩缩容、多版本并存、多租户隔离、工具数量持续增长时,Registry 才开始产生价值。判断标准不是「用了 Nacos 3.x」,而是「工具地址和 schema 是否会独立于 Agent 变化」。
实战场景
一、工具加了个必填参数,所有 Agent 调用失败
现象:某“订单查询”MCP 工具升级,在 inputSchema 里新增必填字段 tenantId。工具发布后 5 分钟内,接入该工具的三个 Agent 全部报参数校验失败;已运行的 Agent 没有重启,模型侧看不到新字段,仍按旧 schema 传参。
排查:工具 schema 是运行时契约,改动影响面等同于改 API。
| 定位层 | 取证据 | 判定 |
|---|---|---|
| schema 变更内容 | 该 Tool 新旧版本的 inputSchema diff | 新增必填 / 改类型 / 改语义均为破坏性变更 |
| 客户端拿到的版本 | Agent 侧发现结果里的版本号与工具当前版本 | 版本落后 → 缓存未刷新;一致 → 契约本身不兼容 |
| 影响范围 | 按工具名统计调用失败率与调用方清单 | 全量失败说明没有灰度隔离 |
| 发布方式 | 是直接覆盖同一版本,还是发新版本 | 覆盖式发布让回滚无处可回 |
| 模型侧可见性 | 工具描述与参数说明是否让模型知道必填 | 描述没写清,模型不会主动补 |
对症做法:
json
// 兼容做法:新增字段用可选 + 默认值,语义在描述里写清,旧调用方不受影响
{
"name": "order.query",
"inputSchema": {
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "订单号,必填" },
"tenantId": { "type": "string", "description": "租户 ID;不传时按调用方凭证推断" }
},
"required": ["orderId"]
}
}text
1. 破坏性变更必须发新版本,旧版本保留一段时间,让 Agent 渐进迁移;
覆盖同一版本号会让“回滚”变成一件做不到的事。
2. 走灰度:3.2 支持版本与灰度管理;3.3 的 draft → review → publish 流程落地前,
不要把一步发布当长期契约。
3. 契约测试进流水线:用旧 schema 的样本参数调用新工具,断言仍然成功。
4. 描述当 API 文档审:输入输出、副作用、幂等性写清楚,
不要把密钥、内网地址写进描述——描述会进入模型上下文。复盘要点:判据是“同一工具的调用失败率在发布瞬间不得上升”,并按工具维度而非整体维度看。工具契约的变更评审要纳入接口变更流程:谁在用、能不能双跑、多久下线旧版本;这三个问题答不出来就不该发。
二、默认 PUBLIC 的工具被别的租户发现
现象:多租户 Agent 平台上线两周后,安全 review 发现两个问题:A 租户的 Agent 能列出 B 租户的内部工具(含描述里的业务流程);Registry 的客户端 OpenAPI 可从办公网直接访问。另外有一个工具在导入时填了 127.0.0.1 的 endpoint,被用来探测内网其他服务。
排查:Registry 存的是工具元数据,被篡改或越权可见的后果不只是“看到不该看的”。
| 定位层 | 取证据 | 判定 |
|---|---|---|
| 默认可见性 | 新发布 MCP Server 的 visibility | 默认 PUBLIC,生产未逐项收紧 |
| 租户隔离 | 工具所在 Namespace 与调用方 Namespace | 同 Namespace 承载多租户即为越权入口 |
| API 暴露面 | 客户端 OpenAPI 与 Admin/Console API 的鉴权开关、访问来源 | 3.x Admin/Console 默认鉴权,客户端 OpenAPI 开关独立 |
| 私有地址导入 | nacos.console.ai.mcp.import.allowed-private-addresses 配置 | 未设白名单时,导入功能可指向内网地址 |
| endpoint 形态 | 是直接写 ip:port 还是 REF 引用已注册服务 | 写死地址既难维护又绕过服务发现的治理 |
| 版本风险 | 服务端版本 | 3.0.0–3.2.3 存在鉴权作用域问题,要锁 3.2.4 及以上补丁 |
对症做法:
properties
# 1. 私有地址导入必须白名单,只允许登记的入口网段
nacos.console.ai.mcp.import.allowed-private-addresses=10.30.0.0/16text
2. 默认私有:新 MCP Server 发布后置为 PRIVATE,逐一授权共享;
跨租户共享用显式可见性设置,不靠约定。
3. Namespace 按租户 + 环境切分:测试工具与生产工具不同 Namespace。
4. endpoint 用 REF 引用服务发现里已注册的服务,
扩缩容只改服务注册,不改工具定义,也不暴露裸地址。
5. Registry 不暴露公网:只在内网/办公网经 VPN 到达,数据面调用统一走 Higress/MCP Router,
鉴权、限流、审计都在网关做。复盘要点:可见性属于“默认值不安全”的配置,判据只能是扫描:定期导出全部 MCP Server 的 visibility 与 Namespace 分布,出现生产 Namespace 下的 PUBLIC 即告警。同时把版本锁进部署模板(3.2.4+),并把“Registry 只做发现、数据面走网关”写进架构约束——Nacos 不代理工具流量,指望它做工具级鉴权会落空。
三、把调用结果写进 Registry:Agent 首轮响应被拖慢
现象:某团队在工具调用链路里把每次调用的返回样例、会话状态和统计计数写回 Registry 的 Tool 元数据。两周后出现两个症状:Agent 启动时拉取工具列表的响应体从 40KB 涨到 3.2MB,首轮响应延迟增加 8 秒(经验值,需校准);模型侧报告“工具太多不知道选哪个”,任务成功率下降。
排查:Registry 的负载形态与传统注册中心不同,读分散、单响应体积大。
| 定位层 | 取证据 | 判定 |
|---|---|---|
| 写入内容与频率 | Registry 的变更记录来源(是否为运行时进程) | 运行时回写 = 把 Registry 当业务库 |
| 单次发现响应体积 | Agent 拉取工具列表的响应字节数分布 | 体积随调用量增长即说明混入了动态数据 |
| 发现延迟 | 从 Agent 启动到拿到工具列表的时间 | 直接影响首轮响应 |
| 工具数量 | 每个 Agent 可见的工具数与描述长度 | 数量膨胀会稀释模型注意力 |
| 是否影响服务发现链路 | Registry 与 Naming 同进程时的资源占用 | 大量元数据读写会争抢同一份 CPU/连接 |
对症做法:
text
1. 边界收紧:Registry 只放“有哪些工具、怎么调”的静态契约。
调用结果、会话状态、用量统计进时序库或对象存储,不进 Registry。
2. 动态统计单独走指标:调用次数、失败率、耗时分位,
用 Prometheus 暴露,不写回 Tool 元数据。
3. 工具集按 Agent 角色分组(Namespace + 可见性),
每个 Agent 只看到自己需要的工具,控制单次发现的体积与模型选择负担。
4. 描述做“可路由”而非“可枚举”:名称、用途、输入输出、副作用四段,
删掉重复的背景介绍;长描述进文档链接,不进上下文。java
// 5. 是否需要 Registry 的判据:工具地址与 schema 是否会独立于 Agent 变化
// 只有一两个固定工具、地址不变、单租户 → 直接写在 Agent 配置里更简单复盘要点:判据是量化的:单次发现响应体积、发现延迟、每 Agent 可见工具数三条都要设预算并告警(例如响应 ≤ 200KB、发现延迟 ≤ 1s、可见工具 ≤ 30 个,经验值,需校准)。注册不等于可用——Registry 里有元数据不代表工具服务健康,健康检查仍要在服务发现和网关两层做,见 监控指标与告警。
最小上手
沿用 01 的 3.2.4 单机实例,先注册一个普通服务,再把 MCP Server 的 endpoint 指向它:
bash
# 1. 业务服务先注册(略,见 02)
# 2. 注册 MCP Server + Tool,endpoint 用 REF 引用服务
curl -s -X POST "http://127.0.0.1:8848/nacos/v3/admin/ai/mcp?mcpName=order-tools&namespaceId=public" \
-H "accessToken: $ACCESS_TOKEN" \
--data-urlencode 'serverSpecification={"name":"order-tools","protocol":"mcp-streamable","frontProtocol":"mcp-streamable","description":"订单查询工具","versionDetail":{"version":"1.0.0"},"remoteServerConfig":{"exportPath":"/mcp"}}' \
--data-urlencode 'toolSpecification={"tools":[{"name":"getOrder","description":"按订单号查询订单","inputSchema":{"type":"object","properties":{"orderId":{"type":"string"}},"required":["orderId"]}}]}' \
--data-urlencode 'endpointSpecification={"type":"REF","data":{"serviceName":"order-service","groupName":"DEFAULT_GROUP","namespaceId":"public"}}'打开 http://127.0.0.1:8080/ 的 MCP Registry 页面,能看到 order-tools 和 getOrder 即注册成功。Agent 侧通过 MCP Router 或 Spring AI Alibaba 发现并调用;具体发现 API 随版本变化,以官方文档为准。3.2.4 起旧的 MCP 导入 API 默认返回 410,需要兼容开关才能恢复。
生产动作
- 锁 3.2.4 或更高补丁:3.0.0–3.2.3 有鉴权作用域问题;Registry 存的是工具元数据,被篡改的后果不只是「看到不该看的」。
- Namespace 按租户/环境切:不要把测试工具和生产线工具放同一个 Namespace;跨租户共享用显式可见性,不靠约定。
- 默认私有:新 MCP Server 默认
PUBLIC,生产要改成PRIVATE并逐一授权。 - 网关做数据面:Nacos 只做 Registry;MCP 调用走 Higress/MCP Router,鉴权、限流、审计在网关做。
- 工具描述当 API 文档审:描述会进入模型上下文,写清楚输入输出、副作用、幂等性;不要把密钥、内网地址写进描述。
- 版本兼容:新增可选参数可以,改必填/改语义要发新版本;保留旧版本一段时间,让 Agent 渐进迁移。
- 监控:MCP Server 注册数、工具调用失败率、发现延迟、私有地址导入拦截数、Registry 的鉴权失败数。
边界
- 不是模型平台:Nacos 不托管模型、不做推理、不做上下文管理;它只登记「有哪些工具/Agent」。
- 不是完整 MCP 网关:协议转换、SSE 会话、限流在 Higress/MCP Router;Nacos 不代理工具流量。
- MCP/A2A 仍在演进:3.3 的 draft/review/publish、A2A 规范版本都会变;接口和字段以对应版本文档为准。
- 注册不等于可用:Registry 里有元数据,不代表工具服务健康;健康检查仍要在服务发现和网关上做。
- 工具多了会稀释模型注意力:Registry 解决「找得到」,不解决「选得对」;工具数量和描述质量仍要治理。
讲清楚
Nacos 3.x 在注册中心之上加了一层 AI 资产注册:MCP Server 和 Tool 登记工具语义,A2A Agent 登记 AgentCard,Prompt/Skill 登记可复用能力;Namespace 做租户隔离,鉴权和可见性控制谁能看到什么。它的角色是控制面,只负责「登记与发现」,真正的 MCP 调用走 Higress/MCP Router 或直连工具服务,所以 Nacos 挂了已拿到地址的 Agent 还能调,但发现不了新工具。它解决的是工具地址写死、版本混乱、多租户可见性和模型每次全量加载工具描述的问题;代价是工具 schema 变成运行时契约,改参数可能让已缓存的 Agent 调用失败,必须像改 API 一样做版本兼容。边界是:它不托管模型、不执行推理、不是完整网关,MCP/A2A 规范仍在演进,落地要锁版本并回查官方文档。