缓存
社区译文;来源: MCP 80041ab0
模型上下文协议(MCP)支持对部分结果进行缓存。客户端因此可以缓存响应,减少不必要的重复获取。 缓存与变更通知 相辅相成,这两种机制可以共存。
可缓存的结果
服务器通过以下操作返回 resultType: "complete" 的结果时,MUST 包含缓存提示:
server/discovertools/listprompts/listresources/listresources/templates/listresources/read
带有 resultType: "input_required" 的中间结果(参见
多轮往返请求
)不可缓存,
也不携带缓存提示。
缓存键
缓存响应由请求方法以及会影响结果的请求参数共同标识(例如 uri 参数用于
resources/read,或 cursor 参数用于分页列表请求)。如果某个请求的方法或参数与生成缓存响应的
请求不同,客户端 MUST NOT 提供该缓存响应。
通过多轮往返请求
机制重试请求所产生的结果,
即携带 inputResponses 或 requestState 的请求结果,MUST NOT 被缓存,
因为这些结果依赖未包含在缓存键中的输入。
缓存模型
MCP 中可缓存的结果使用两个字段向客户端提供缓存提示:
- 生存时间(TTL)字段
ttlMs是以毫秒为单位的整数值,指定客户端 MAY 将结果视为新鲜结果的时长。 - 缓存范围字段
cacheScope表示缓存响应的预期范围,取值为"public"或"private"。
生存时间(TTL)字段
ttlMs 字段是服务器提供的提示,表示客户端 MAY 将结果视为新鲜结果的毫秒数。
其语义类似于 HTTP Cache-Control: max-age。
- 如果
ttlMs为0,响应 SHOULD 被视为立即过期。客户端 MAY 在每次需要该结果时重新获取。 - 如果
ttlMs为正数,客户端 SHOULD 在收到响应后的相应毫秒数内将结果视为新鲜结果。 - 如果没有
ttlMs,客户端 SHOULD 假定默认值为0(立即过期),并依赖自身的缓存启发式策略或通知。这种情况应当只出现在旧版服务器中。 - 如果
ttlMs为负数,客户端 SHOULD 忽略该值并将其视为0。
服务器 MUST 提供 ttlMs 值,且该值为 >= 0。
新鲜度计算
客户端记录收到响应时的本地时间(t_received)。在满足以下条件时,响应被视为新鲜:
now < t_received + ttlMsTTL 到期后,响应即为过期,客户端 SHOULD 在下次访问时重新获取。
客户端 SHOULD NOT 将 TTL 视为触发自动后台重新获取的轮询间隔。TTL 是新鲜度提示: 客户端在需要数据时检查其新鲜度,并且仅在数据过期时重新获取。选择进行轮询的实现 MUST 应用抖动和退避。
如果客户端有理由认为数据已经变化,客户端 MAY 在 TTL 到期前重新获取(例如,工具调用返回意外错误, 表明未找到方法或参数无效)。
如果重新获取期间发生错误(例如网络问题或服务器停机),客户端 MAY 提供过期响应。
缓存范围字段
cacheScope 字段控制哪些主体可以缓存响应,类似于 HTTP 的
Cache-Control: public 与 Cache-Control: private。
| 值 | 含义 |
|---|---|
"public" | 响应不包含用户特定数据。任何客户端、共享网关或缓存代理 MAY 存储缓存响应,并向任何用户提供该响应。 |
"private" | 响应包含不应在调用方之间共享的私有数据。缓存响应 MAY 在相同授权上下文中复用。缓存 MUST NOT 跨授权上下文共享(例如,不同的访问令牌需要不同的缓存)。 |
选择缓存范围
- 当工具、提示词和资源模板列表对所有用户都相同时,适合使用
"public"。 "private"适用于依赖已认证用户的resources/read结果,或因用户而异的筛选列表结果。
与通知的交互
TTL 与服务器推送通知相辅相成:
- 服务器 MAY 提供
ttlMs,而不在能力声明中公布listChanged: true。此时,客户端完全依赖基于 TTL 的新鲜度判断。 - 服务器 MAY 公布
listChanged: true,并且提供ttlMs。此时,客户端可以使用 TTL 避免在两次通知之间进行不必要的重新获取,而通知则作为立即失效信号。
如果在缓存响应仍然新鲜时收到相关通知,该通知会使缓存响应失效,应立即将其视为过期。
sequenceDiagram
participant Client
participant Server
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
Note over Client: Cache response, fresh for 5 min
Note over Client: 2 minutes later...
Client->>Client: Need tools list → cache still fresh, use cached
Note over Client: 3 minutes later (TTL expired)...
Client->>Client: Need tools list → cache stale
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
Note over Server: Tools change before TTL expires
Server-->>Client: notifications/tools/list_changed
Note over Client: Invalidate cache immediately
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
与分页的交互
当列表结果被分页
时,每一页都是可独立缓存的响应,
这与 HTTP Cache-Control 处理分页资源的方式一致。
- 每个分页响应都携带自己的
ttlMs值。每一页的新鲜度计时从收到该页时开始。 - 服务器 MAY 在不同分页中返回不同的
ttlMs值(例如,为稳定列表的前几页设置较长 TTL,为最后一页设置较短 TTL)。 - 当缓存页过期时,客户端 SHOULD 使用该页的游标重新获取。
- 不保证跨页一致性。如果底层数据在获取不同分页之间发生变化,客户端可能观察到重复项或缺失项。
- 需要完整列表一致快照的客户端 SHOULD 从头开始重新获取(不带游标)。
- 如果游标失效(例如服务器对之前有效的游标返回错误),客户端 SHOULD 丢弃所有缓存页并从头开始重新获取。
对于给定的列表请求,服务器 MUST 对所有响应分页应用相同的 cacheScope。例如,如果
tools/list 响应的第一页使用 cacheScope: "private",则该请求后续所有分页
MUST 也为 "private"。
安全注意事项
cacheScope 为 "public" 表示响应不包含用户特定数据,可以安全共享。服务器 MUST 意识到,
即使结果来自已认证端点,带有 "public" cacheScope 的响应也可能在调用方之间共享。例如,
一次已认证 tools/list 调用返回的结果如果带有 "public" cacheScope,客户端可能缓存该结果,
并将其共享到初始请求的授权上下文之外(即不同访问令牌可以利用同一缓存)。
服务器实现者:
- 应确保
cacheScope正确反映该协议原语的预期可见性。 - MUST 对每个协议原语应用适当的访问控制,并且 MUST NOT 仅依赖
cacheScope防止对协议原语的未授权访问。