MCP 服务器若只用工具名+参数哈希做缓存键,多租户场景下会导致 A 租户用户拿到 B 租户的缓存结果。解决方案是缓存键必须包含 tenant、role 等认证属性,并将缓存分为六类隔离,从根本上堵住授权边界被缓存静默穿透的风险。
缓存让你的 PostgreSQL MCP 服务器飞快。但如果你只用「工具名 + JSON 参数」作为缓存键,你其实已经在悄无声息中抹掉了自己的授权边界。
两个用户用完全相同的参数调用同一个工具,却分属不同的租户、角色或环境——带着一个脆弱的缓存键,第二个用户拿到的是第一个用户的缓存结果,包括那些他们根本无权看到的行。
这不是假设场景。这是许多朴素 MCP 服务器实现中的默认行为。
MCP 缓存键必须包含认证属性,如 tenant 和 role。
将六类缓存分离,以防止跨作用域泄漏。
用负向跨租户场景测试。
大多数 MCP 服务器缓存时,只对工具名和序列化后的参数做哈希。这在单用户本地环境下没问题。但只要你把 PostgreSQL MCP 服务器部署在面向多租户的网关后面,这个键就不够用了。
看一个具体例子:Alice 在租户 A,Bob 在租户 B。两人同时调用 query_database,参数都是 {"sql": "SELECT * FROM orders"}。一个朴素的缓存把结果存在 query_database:{"sql":"..."} 下。Bob 发了相同的调用,直接命中缓存,拿到了 Alice 的订单。
更糟的是:工具发现元数据本身就能泄露敏感结构。可用工具列表、它们的 schema、描述都可能因角色不同而不同。如果把这些元数据缓存时没有带上调用者的 role,就会泄漏内部 API 的形状。
原文推荐两个动作:分离缓存类,然后在键中包含每一个授权相关属性。
不要用单一缓存承接所有场景,拆分成:
每个类的敏感度和失效需求各不相同。策略决策随角色变化而变化;数据库结果随数据变化而变化。混在一起,任何变更都迫使你不得不全部失效。
对每个缓存条目,计算键时须包含:
将以上全部纳入哈希。任何一个发生变化,缓存未命中就是正确的行为。

即便只是列出可用工具,也可能暴露敏感的内部 schema。按 role 和 tenant 缓存发现响应,而不是全局缓存。
如果你对并发相同请求做合并(single-flight),合并键必须和上面那个支持授权的键完全一致。否则两个用户的请求会合并成一次执行,第二个用户拿到的是第一个用户被授权的结果。
缓存命中后,仍然要应用新鲜度检查、截断、脱敏和追踪元数据。不要返回一个跳过了原始查询上应用的行级安全或截断规则的缓存结果。
先预热缓存,然后用相同参数重复调用:
测量跨作用域命中是否消失——而不是只看总的命中率。跨租户泄漏下的高命中率是安全事故,不是性能收益。
如果你正在构建或维护 PostgreSQL MCP 服务器,现在就审计你的缓存键:
cache.put 和 cache.get 调用这原则通用,无论你用 Redis、内存 Map 还是直接用 PostgreSQL 本身做缓存层。核心原则不变:缓存键是授权模型的一部分。
带完整实现细节的指南见原文。
Originally published on gentic.news