缓存

社区译文;来源: MCP 80041ab0

模型上下文协议(MCP)支持对部分结果进行缓存。客户端因此可以缓存响应,减少不必要的重复获取。 缓存与变更通知 相辅相成,这两种机制可以共存。

可缓存的结果

服务器通过以下操作返回 resultType: "complete" 的结果时,MUST 包含缓存提示:

  • server/discover
  • tools/list
  • prompts/list
  • resources/list
  • resources/templates/list
  • resources/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。

TTL 是新鲜度提示,而非保证。服务器 MAY 在 TTL 到期之前更改底层数据。 TTL 告诉客户端在多长时间内可以合理地避免重新获取,而不是保证数据在多长时间内保持不变。

新鲜度计算

客户端记录收到响应时的本地时间(t_received)。在满足以下条件时,响应被视为新鲜:

now < t_received + ttlMs

TTL 到期后,响应即为过期,客户端 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 防止对协议原语的未授权访问。