一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

MCP Server 的 Tools、Prompts 和 Resources 能否随时间动态变化?

时间:2026-09-16 08:32:01 编辑:袖梨 来源:一聚教程网

可以。MCP Server 暴露的 Tools、Prompts 和 Resources 集合都可以随时间变化。客户端不应把初始化时拿到的列表永久固定,而应根据服务端声明的能力订阅列表变更通知,在收到通知后重新调用对应的 list 方法。同时要注意,动态变化并不意味着服务端可以针对同一连接随意漂移列表:规范要求列表不能按连接变化,也不能因为连接上的其他请求产生隐式副作用;但列表可以依据每个请求携带的授权信息而不同。

三类能力都支持动态列表

Tools 是模型可发现并调用的动作,Prompts 是供用户选择的提示模板,Resources 是由应用决定如何纳入上下文的数据。三者的控制主体不同:Tools 偏模型控制,Prompts 偏用户控制,Resources 偏应用控制,但它们在发现机制上采用相似结构。

服务端若支持某一类能力,必须在初始化阶段声明相应 capability,并响应 `tools/list`、`prompts/list` 或 `resources/list`。规范明确允许这些调用返回空集合,也允许返回集合随时间变化。动态加载插件、管理员启停功能、后端数据源上线、授权范围变化,都是合理的变化来源。

这并不是让客户端定时盲目刷新。每类 capability 都可以声明 `listChanged: true`,表示服务端会在可用列表改变时发送通知。客户端订阅对应通知后,收到事件再重新拉取列表,形成“通知只表示可能已变,list 响应才是最新事实”的同步模型。

Tools 如何通知变化

支持工具的服务端在 capabilities 中声明 `tools`。若还声明 `listChanged: true`,客户端可以在订阅流中请求 `toolsListChanged`。工具集合改变时,服务端发送 `notifications/tools/list_changed`;客户端收到后再次调用 `tools/list`,替换本地快照。

{
  "capabilities": {
    "tools": {
      "listChanged": true
    }
  }
}

工具列表响应可以分页,也可以携带缓存信息。当前草案示例包含 `ttlMs` 与 `cacheScope`,客户端应把它们当成缓存约束,而不是把 TTL 到期解释为工具必然发生变化。通知负责降低刷新延迟,TTL 负责限定缓存新鲜度,两者可以同时存在。

工具顺序也值得稳定处理。规范建议在底层集合未变化时返回确定性顺序,因为工具定义常被放入模型上下文。若服务端每次随机排序,即使内容相同也会破坏提示缓存命中,增加 token 和推理成本。可以按稳定的内部键排序,但不能让排序改变工具名或覆盖来自不同服务端的重名工具。

Prompts 的更新机制

Prompts 使用相同的能力协商模式。服务端声明 `prompts.listChanged`,客户端订阅 `promptsListChanged`,列表变化后收到 `notifications/prompts/list_changed`,随后重新调用 `prompts/list`。列表返回的是模板描述、参数和展示元数据,真正使用某个模板时再调用 `prompts/get` 获取内容。

因此,更新一个提示模板存在两种情况。新增、删除模板,或改变名称、参数、描述等列表元数据时,应触发列表变更通知。若服务端只改变 `prompts/get` 返回的内部内容,客户端是否需要刷新取决于缓存策略;最稳妥的实现是让模板版本或相关元数据也变化,并触发通知,避免客户端继续使用过期缓存。

Prompt 是用户控制的能力,客户端通常会把它展示为命令、菜单或表单。收到变更通知后不宜直接打断用户正在编辑的输入,而应原子替换可选列表;如果当前选中的 Prompt 已删除,则在执行前提示用户重新选择。Prompt 内容仍属于服务端输入,客户端必须进行注入和资源访问方面的安全处理。

Resources 有两种不同的变化

Resources 比另外两类多一层:资源列表会变化,某个既有资源的内容也会变化。服务端 capability 中的 `listChanged` 用于表示资源集合变化,`subscribe` 用于表示支持针对资源的更新订阅。两项可以独立声明,也可以同时声明。

{
  "capabilities": {
    "resources": {
      "listChanged": true,
      "subscribe": true
    }
  }
}

例如仓库中新建或删除文件,会改变 `resources/list` 的结果,应触发资源列表变化;一个已经列出的配置文件内容被修改,则 URI 仍然存在,通常属于资源内容更新。客户端不要因为内容改变就必然重新拉取整个列表,也不要把列表通知误当成某个资源的新内容。

Resources 以 URI 唯一标识。客户端重新获取列表时应基于 URI 做增删改合并,并处理 MIME 类型、标题、注解或修改时间的变化。读取内容使用 `resources/read`,返回值也可能带 TTL 和私有缓存范围。对于敏感资源,缓存键必须包含授权上下文,不能把一个调用者读取到的私有内容复用给另一个调用者。

动态不等于按连接个性化

当前草案对三类列表都给出相同约束:列表可以随时间改变,但不能针对不同连接返回不同集合,也不能因为同一连接上的其他请求产生隐式副作用。换句话说,不能让客户端调用某个工具后,悄悄把另一个工具加入该连接的列表,却不形成可观察、可同步的服务端状态变化。

这一限制能避免断线重连、负载均衡和多实例部署导致行为不一致。MCP 的传输连接不应承担隐藏会话语义。若服务端需要购物车、浏览器页面或数据库事务等跨调用状态,应返回显式 handle,并要求后续调用把 handle 作为参数传回,而不是通过修改连接专属工具列表表达状态。

授权是允许的差异来源。草案允许列表依据请求携带的授权而变化,例如只有拥有写入 scope 的请求才能看到写工具。原因是凭证属于每次请求的输入,而不是连接内部状态。实现时,服务端应把授权主体和 scope 纳入缓存键,并确保列表变更通知不会泄露调用者无权知道的能力名称。

客户端应如何维护快照

客户端可以为 Tools、Prompts 和 Resources 分别维护不可变快照,记录协议版本、服务端身份、授权摘要、分页游标结果、获取时间、TTL 和列表指纹。初始化后完整处理所有分页,再一次性发布新快照,避免模型或界面看到半页新数据与半页旧数据混合的状态。

收到 list changed 通知时,不要假设事件中包含差异。正确动作是标记对应快照过期、合并短时间内的重复通知、重新拉取所有分页、验证数据结构,然后原子替换。若刷新失败,保留上一份快照但标记为陈旧,按退避策略重试;对于高风险工具,可在快照过期期间暂停新调用。

async function refreshTools(client, cacheKey) {
  const tools = [];
  let cursor

  do {
    const page = await client.listTools({ cursor })
    tools.push(...page.tools)
    cursor = page.nextCursor
  } while (cursor)

  validateUniqueNames(tools)
  toolCache.set(cacheKey, Object.freeze(tools))
}

分页期间再次收到通知时,可以在本轮完成后再刷新一次,或者使用版本号确认快照一致性。客户端还应对名称冲突做显式消歧:工具名只保证在单个服务端内唯一,聚合多个 MCP Server 时需要添加稳定的服务端前缀,不能依赖 serverInfo 中的 name 天然全局唯一。

服务端应在什么时机发通知

服务端应在外部可观察列表实际改变后发送通知,包括插件启停、功能开关改变、公开资源新增删除、模板部署以及影响可见集合的授权策略更新。仅仅内部重建索引但最终列表相同,不必发送通知。频繁变化应做合并和防抖,否则多个客户端会同时重拉列表,形成放大流量。

通知不能替代 capability 声明。未声明 `listChanged` 的服务端不应假设客户端会;客户端也不能因为协议存在通知名称,就默认所有服务端都会发送。对于不支持通知的服务端,客户端只能依据 TTL、用户手动刷新或重连后的重新发现来更新列表。

部署为多个实例时,变化事件需要广播到所有实例,保证连接到不同节点的客户端得到一致信号。列表生成函数应基于共享配置和请求授权计算,不应读取进程内偶然状态。通知可以重复但不能要求严格只到达一次,因此客户端刷新逻辑必须幂等。

调用进行中遇到能力变化怎么办

列表变化不应自动取消已经发送的调用。工具在调用前存在、执行中被下线时,服务端应返回正常结果或明确的 JSON-RPC 错误;客户端随后刷新列表并停止发起新调用。若工具 schema 改变,旧请求可能不再合法,因此版本升级最好采用先新增新工具、迁移调用方、再删除旧工具的兼容流程。

Prompt 或 Resource 在用户选择后被删除,也需要在 get 或 read 时返回明确错误。客户端收到错误后可以强制刷新相应列表,但不能把任意调用失败都解释为列表改变。网络错误、权限错误和参数错误需要分别处理,否则会造成无休止刷新。

安全与缓存的关键检查

动态工具定义属于来自服务端的不可信元数据。客户端必须验证工具输入输出 schema、名称格式和注解,向用户清楚展示即将调用的工具,并为有副作用操作保留确认机制。列表刷新不应让新出现的高风险工具自动继承旧工具的批准状态。

缓存必须至少按服务端、协议版本和授权上下文隔离。公共列表可以共享,私有资源内容不能跨用户共享。服务端降低某个调用者权限后,应使旧缓存尽快失效;客户端在真正执行敏感操作时仍需由服务端重新鉴权,不能把“工具曾出现在列表里”当作永久授权凭证。

结论

MCP 的 Tools、Prompts 和 Resources 都不是只能在握手时确定的静态清单。三者都能通过 capability 中的 `listChanged` 与对应通知实现动态发现;Resources 还可借助 `subscribe` 处理单个资源内容更新。客户端收到通知后重新 list,服务端保持确定性顺序和无连接隐式状态,双方再用请求级授权与正确缓存键隔离可见集合。

实现时最重要的是把“列表变化”“内容变化”“授权差异”和“连接状态”分开建模。这样无论服务端热插拔插件、更新提示模板还是新增资源,客户端都能得到可预测、可缓存且安全的最新能力视图。

热门栏目