先记住这个答案
服务端在 initialize 响应的 capabilities 中声明可提供的功能。tools 与 prompts 可用 listChanged 表示目录变化通知;resources 除 listChanged 外,还能用 subscribe 表示支持订阅单个资源变化。logging 的空对象表示日志能力存在,没有由这四类字段共同继承的 subscribe 开关。以下按 2025-11-25 讨论这四项,它们不是该版本全部服务端能力。能力声明描述功能范围,实际工具、提示模板和资源条目还需分别发现,并受当前访问策略约束。
- 能力对象存在与内部布尔值分别判断
- listChanged 关注目录,subscribe 关注资源更新
- 能力声明不直接携带完整工具和资源列表
空能力对象并不表示这个功能不可用
tools: {} 表示工具功能存在,只是没有声明工具目录变化通知。客户端可以调用 tools/list 发现工具,不能因为对象里没有 listChanged 就把整个工具面板关闭。resources: {} 也不能被误判为没有资源读取能力。
下面将四个能力放在同一对象中便于比较。它是初始化结果中 capabilities 的值,不是独立的协议请求。实现时应读取每一项自己的结构,不要遍历全部字段后自动拼出并不存在的通用订阅方法。
{
"tools": { "listChanged": true },
"resources": { "listChanged": true, "subscribe": false },
"prompts": {},
"logging": {}
}工具与资源目录变化可产生相应通知,资源订阅未启用;提示模板可被发现,但未声明目录变化通知。日志能力也不代表服务端会自动发送所有级别的全部内部日志。
文件索引场景中两种变化需要不同处理
文件服务新增一个可发现的资源条目,会影响资源目录;原有资源 URI 不变但文件正文更新,则影响该资源内容。前者适合使目录缓存失效并重新列举,后者在已订阅时通过资源更新通知触发重新读取。
通知通常提供变更信号而不是完整新正文,也不能据此假定所有分页缓存已经自动更新。客户端应把同一轮密集通知合并处理,重新读取后确认最新状态。即使目录条目没有变化,用户当前权限仍可能让下一次读取失败。
声明与实现不一致时怎样定位问题
若服务端声明订阅但没有实现 resources/subscribe,客户端会在连接看似正常后才遇到方法错误。应将能力声明纳入集成测试:每个已开启选项都要有处理路径,每个未开启选项都不能成为客户端流程的隐含依赖。
日志功能可以帮助查看这些交互,但应按协议级别选择和数据清理策略发送,不能泄露请求中的凭证。遇到未声明的扩展能力时先保持兼容,是否启用由明确的扩展契约决定;不能因为字段名字看起来熟悉就自行推断语义。
容易答错的地方
- 有 listChanged 就能订阅每个资源的内容
- 目录变化和单项更新是不同信号,单项订阅还需要 resources.subscribe;不能把目录通知当作对每个 URI 都建立了监听。
- capabilities 里没有实际条目就代表列表为空
- 初始化声明与具体目录发现分开,必须调用对应列表接口并处理分页;能力存在不说明条目数量,也不代表当前身份能访问全部内容。
面试官还会怎么问?
资源内容更新后一定要重新读取整个资源目录吗?
不一定,如果只是已知 URI 的内容变化,通常重新读取该资源即可。目录变化通知才说明需要检查条目集合,具体缓存策略应按信号分别设计。
listChanged 没声明就意味着目录永远不变吗?
不能这样推断,它只表示没有承诺相应通知。客户端仍可在重新连接、用户刷新或应用定义的时机重新发现目录,避免长期依赖旧缓存。
同一个资源同时改变内容和展示名称怎么办?
这可能同时影响资源内容与目录元数据,应按服务端提供的信号更新相应缓存。客户端可以合并重复读取,但不能忽略其中一类变化。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。