最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
MCP Server 的工具和资源发生变化时,客户端如何重新发现能力?
时间:2026-09-16 10:16:01 编辑:袖梨 来源:一聚教程网
MCP Server 的工具、资源或提示模板发生变化时,客户端当前应把对应的 `ListChangedNotification` 视为缓存失效提示,重新调用 `tools/list`、`resources/list` 或 `prompts/list` 获取权威快照。通知本身不是增量数据,也不保证可靠送达,因此客户端还要在重连、手动刷新和必要的定期校验时重新发现能力。
初始化能力与运行时目录不是同一层
连接初始化时,Server 返回 Tools、Resources、Prompts 等顶层 capability,并可通过 `listChanged: true` 声明对应目录会在运行期间变化。这个能力对象回答的是“是否支持这一类功能与变化通知”,而具体有哪些工具、资源和模板,要通过各自 list 方法获取。
因此,客户端至少维护两类状态:连接级的协商能力,以及运行时的目录快照。Server 新增一个工具通常不需要重新执行整个 initialize;它发送工具列表变化通知,客户端只需让工具目录缓存失效并重新 list。
当前重新发现的标准流程
连接建立后,客户端先检查 Server 是否声明相应 capability,再获取初始目录。若该能力的 `listChanged` 为 true,客户端安装对应通知处理器。收到通知时不从本地列表猜测差异,而是重新请求完整或分页后的最新目录。
onConnected(serverCapabilities):
if tools exists:
toolCache = listAllTools()
if tools.listChanged:
listen ToolListChanged
on ToolListChanged:
mark toolCache stale
latest = listAllTools()
atomicallyReplace(toolCache, latest)
Resources 与 Prompts 使用相同模式。资源还存在两种不同变化:资源目录变化触发重新 `resources/list`;某个已订阅资源的内容变化则触发读取该 URI 或更新对应内容。不要因为都叫通知就共用错误的刷新逻辑。
为什么通知只是一条失效提示
列表变化通知通常不携带新增、删除和修改的完整差异。它告诉客户端旧缓存可能过期,权威状态仍在新的 list 响应中。这种设计避免客户端按一串可能重复、乱序或合并的增量事件重建状态。
正确处理方式是幂等刷新。连续收到三次通知,可以合并成一次 list;刷新过程中又收到通知,则在当前刷新结束后再执行一次,确保没有错过后续变化。客户端不应假设一条通知严格对应一个版本。
分页目录必须获取到一致快照
大型目录会通过游标分页返回。客户端收到变化通知后,应从第一页开始重新遍历,直到没有 next cursor,再原子替换旧缓存。不能只刷新第一页,也不能把新第一页直接拼到旧的后续页。
如果目录在分页过程中再次改变,客户端可能得到混合快照。当前没有通用版本标识时,可在刷新期间记录是否又收到变化通知;若收到,则本轮完成后立即重新抓取。Server 也应尽量让一次分页遍历使用稳定视图或稳定游标。
更新 UI 与模型工具集要保持原子性
目录刷新成功前,可以继续展示旧数据并标记为正在更新。新快照完整获取后一次替换,避免 UI 暂时只显示第一页。对于工具,宿主还要同步更新提供给模型的 tools 字段,而不仅是开发者面板中的目录。
若某个已删除工具仍出现在对话历史中,模型可能继续尝试调用。客户端应从下一轮可用工具中移除它,并在调用入口再次检查当前目录和权限。需要保持会话连续性时,可以向模型状态提供简短的能力变更说明,但不能伪造已经失效的工具。
断线重连后必须重新发现
通知不是持久消息队列。连接中断期间发生的变化可能完全不可见,所以恢复连接后不能继续信任旧目录。客户端应重新协商能力、重新获取所需目录,并按新 capability 重新注册通知处理器。
对于 Streamable HTTP,会话恢复与传输重连的具体行为取决于协议版本和实现。应用层最稳妥的边界是:只要建立了新的逻辑连接,旧连接的 capability 与目录缓存就不得未经验证直接继承。
不支持 listChanged 时怎样处理
Server 可能提供 Tools、Resources 或 Prompts,却没有把 `listChanged` 设为 true。这时客户端仍可正常 list 和使用现有项目,只是不能依赖推送发现变化。产品可以提供手动刷新,在任务边界或连接重建时重新 list,必要时使用带退避和抖动的低频轮询。
是否轮询取决于目录变化频率与陈旧风险。静态工具集合无需每轮刷新;权限或部署会频繁变化的远程 Server 则应缩短缓存寿命。客户端不应擅自制造高频流量,也不应把“未声明通知能力”解释为“目录永远不会变化”。
刷新失败时保留旧快照但标记陈旧
收到通知后重新 list 可能因网络、鉴权或 Server 负载失败。直接清空 UI 会造成不必要的抖动;继续把旧数据当成最新也会误导。更好的状态模型包含 fresh、refreshing、stale 和 unavailable。
刷新失败时保留旧快照供查看,但调用前必须重新验证。客户端用指数退避重试,并展示最近成功刷新时间。鉴权失败或 capability 消失时,应停止普通重试,转入重新认证或连接重建流程。
Server 端如何正确发出变化信号
Server 只有在声明相应 `listChanged` 后,才应让客户端依赖通知。目录的权威数据完成提交后再发送通知,避免客户端立即重新 list 却读到旧状态。短时间批量注册多个工具时,可以合并为一次通知。
变化信号不应替代调用阶段校验。某个工具从目录移除后,Server 仍要拒绝旧客户端发来的调用;资源权限收紧后,也不能因为客户端尚未刷新就继续返回数据。目录用于发现,授权与存在性由每次请求的当前状态决定。
观测指标应覆盖整个重新发现链路
排障时至少记录通知类型、接收时间、当时的连接标识、刷新开始与结束、分页数量、目录项数量、缓存版本以及失败原因。仅证明 Server 发出了通知,不能证明客户端收到并采用了新目录。
工具场景还要观测刷新后注入模型的工具数量。如果开发者面板已显示新工具,而模型请求仍沿用旧 schema,问题位于宿主编排层而不是 MCP 发现层。资源和 Prompts 也应分别追踪目录缓存与业务 UI 的采用状态。
未来方向:显式订阅流
MCP Transport Working Group 在路线图中提出,用针对特定项目或更新类型的显式订阅流替代通用 GET 事件流。客户端只为真正关心的内容打开流,并可同时维护多个订阅。流中断后重新启动,而不是维护复杂的恢复状态。
这项设计目标适合大规模远程部署:通用长连接带来粘性路由和状态存储压力,显式、可重启的订阅更容易水平扩展。但它属于演进方向,具体行为要以最终规范版本和所用 SDK 的实现为准,不能在现有客户端中当成已经普遍可用。
未来方向:TTL 与版本标识
路线图还讨论给数据增加 Time-To-Live 和类似 ETag 的版本标识。这样客户端即使漏掉通知,也能根据过期时间主动校验;若版本未变,可以避免重复传输完整目录。通知将成为低延迟优化,而不再是保持正确性的唯一渠道。
理想流程是:缓存未过期时直接使用;收到通知立即再验证;缓存到期后做条件请求;断线重连时按版本判断是否需要下载新快照。版本标识还能帮助分页读取确认是否来自同一目录版本。
未来方向:无握手发现与 Server Card
当前客户端通常必须完成 initialize 才能知道 Server 的基本能力。路线图探索独立 discovery 机制以及标准化 Server Card,让客户端在建立完整连接前了解能力、认证要求和可用 primitive,可用于自动配置、静态安全检查和 UI 预加载。
这不会取消运行时重新发现的需求。预连接元数据可能陈旧,真正执行时仍需依据会话协商和权威目录。Server Card 更适合回答“是否值得连接、需要什么认证”,列表通知与版本化缓存回答“连接后目录如何保持新鲜”。
客户端实现的推荐状态机
DISCONNECTED
-> INITIALIZING
-> DISCOVERING
-> FRESH
FRESH + listChanged
-> STALE
-> REFRESHING
-> FRESH
REFRESHING + failure
-> STALE + retry
connection replaced
-> INITIALIZING
每一种目录分别维护状态,避免 Tools 刷新失败拖累 Resources。通知处理只负责置 stale 和调度刷新,网络请求由单飞机制执行,保证同一目录最多有一个刷新任务。新连接建立后取消旧连接的刷新与通知回调。
端到端测试重点
测试 Server 在连接后增加、删除和修改工具,确认客户端收到通知、完整重新 list 并更新模型请求。对 Resources 和 Prompts 重复同样测试,并验证资源目录变化与资源内容变化不会混淆。
随后模拟通知重复、通知风暴、分页期间再次变化、刷新超时、断线期间目录变化和新连接 capability 改变。对于不声明 listChanged 的 Server,确认手动刷新可用且客户端不会等待永远不会到来的通知。
结论
现阶段,MCP 客户端重新发现变化能力的可靠做法是:检查 `listChanged` 子能力,接收列表变化通知,将缓存标为过期,并重新调用对应 list 方法获取权威快照。通知需要去重,分页要完整读取,缓存要原子替换,断线重连后必须重新发现。
未来的显式订阅流、TTL、版本标识与 Server Card,目标是让发现和缓存更适合无状态、大规模远程部署。它们应被视为路线图而非现有保证;当前实现仍应以协商能力、重新 list 和请求时校验构成正确性基础。