MCP 协议 cacheScope 中 cacheKey 不完整时,私有结果(如用户专属工具目录)可能被后续用户命中,造成跨用户信息泄露,附安全修复建议。
MCP cacheScope 要解决的是一个微妙的问题:一份响应可能足够"新鲜",但对另一个用户而言却并不安全。
2026-07-28 的稳定版 MCP 规范定义了可复用结果的缓存提示。服务器可以将结果标记为 public 或 private,而 ttlMs 则表示结果可以保持新鲜多长时间。但共享的客户端缓存仍然需要足够的身份信息来隔离私有条目。
如果 Alice 预热了某个缓存,之后 Bob 使用了同一个存储,那么不完整的缓存键就会把 Alice 的结果返回给 Bob。我将这视为一个值得测试的安全边界。
MCP 缓存规范覆盖了以下内容的发现:工具列表、提示列表、资源列表、资源模板和资源读取。
其作用域有不同的含义:
public 结果可以在不同授权上下文之间复用,即使端点需要认证。
private 结果只能在同一授权上下文中复用。
对于用户专属的工具目录,服务器可以这样描述结果:
const server = new McpServer(
{ name: "private-catalog", version: "1.0.0" },
{
cacheHints: {
"tools/list": {
ttlMs: 60_000,
cacheScope: "private",
},
},
},
);
这个提示传达了服务器的缓存意图,但不会告诉共享缓存是哪个调用者发起了请求。
缓存键已经需要包含方法名和每一个可能改变结果的参数。授权身份通常在 tools/list 参数之外传递,因此客户端需要为此建立一个独立的分区。
危险的结构很小:两个授权上下文、一个响应缓存、没有分区。
const sharedCache = new InMemoryResponseCacheStore();
function unpartitionedClient() {
return new Client(
{ name: "shared-gateway", version: "1.0.0" },
{ responseCacheStore: sharedCache },
);
}
我的演示使用两个进程内端点,它们具有相同的 MCP 服务器身份。一个暴露了仅限 Alice 的工具;另一个暴露了仅限 Bob 的工具。这些端点代表了真实认证服务器会产生不同结果的情况。
Alice 先调用 tools/list,将其私有结果放入共享存储。之后 Bob 用相同的参数和服务器身份调用同一方法。
如果没有 cachePartition,查找操作无法区分授权上下文。Bob 会收到 Alice 缓存的工具列表,而他的端点处理零个 tools/list 请求。官方 TypeScript SDK v2 缓存指南警告说,这种配置可能会将一个用户的私有响应体发送给另一个用户。
测试在重现了不安全结果时故意通过。这样可以在无需凭证、网络服务、模型或付费 API 调用的情况下,让失败模式变得可见。
修复方法是为每个授权上下文提供一个稳定的缓存分区:
const sharedCache = new InMemoryResponseCacheStore();
function clientFor(cachePartition: string) {
return new Client(
{ name: "shared-gateway", version: "1.0.0" },
{
responseCacheStore: sharedCache,
cachePartition,
},
);
}
const alice = clientFor("subject:alice");
const bob = clientFor("subject:bob");
现在 Alice 的私有条目与 Bob 的条目分开存储。相同的方法、参数和服务器身份不再解析到同一个私有缓存位置。第二个测试证明两个端点各收到一个请求,每个客户端只看到自己的工具。
我会从一个稳定、不透明的授权身份中导出分区。它必须包含每一个可能改变可见性的维度,如租户、主体、角色或生效范围。原始 bearer token 不是一个好的分区选择:它是秘密材料,且可能在底层主体不变的情况下轮换。
SDK 对 public 条目的处理不同。它们可以在主体分区之间共享,因为服务器明确声明它们可以安全地跨上下文复用。这样既保留了性能优势,又不会削弱私有隔离。
我的审查清单很短:
可运行的 TypeScript 回归示例包含了不安全重现和分区修复。
cacheScope 控制缓存复用,但不授予访问权限。服务器仍必须认证调用者,并对每个非缓存请求进行授权。
TTL 是一个新鲜度提示,而不是即时撤销机制。如果权限发生变化,等待私有条目过期可能太慢。合规的客户端必须在收到相应的 MCP 通知时使受影响的条目失效,而应用程序仍然需要一个策略来处理该流程之外的授权变更。
其他协议边界也很重要。携带 inputResponses 或 requestState 的多轮重试请求不得缓存。input_required 结果是不完整的,不可缓存。分页按页缓存,每页使用相同的作用域,且不承诺快照一致性。
对于使用私有的、非共享存储的单主体进程,分区可能增加很少的价值。对于网关、桌面主机或通过一个缓存复用用户的服务,我会将分区隔离作为回归测试,而不是配置假设。