一周内331次MCP工具列表变更,只是改了日期,却每次都触发提示缓存写入

Dev.to AI 2026-11-03T03:59:21.245609

我们在一周内发现331次MCP工具列表变更,仅仅是改了个日期,而每一次都会触发一次提示缓存写入

我们上一篇文章的读者 Paul Irolla 指出一个我们之前只当作正确性问题来看待的情况。他正在为编码 agent 开发一款 token 优化器。模型请求的开头是工具列表。只要工具定义改动一个字节,建立在这个请求上的所有提示缓存就全都失效了,哪怕这个改动毫无实质意义。HISTOR 是我们公开记录 MCP 工具定义变化的日志,它记下了看到的每一次变更,于是我们做了一次统计。在 10 月 25 日到 11 月 1 日这一周里,它记录到 4,491 个 MCP 服务器的工具列表共发生 10,712 次变化。其中 331 次只改了日期或时间戳,别无其他。

为什么一个字节如此关键

在 Anthropic 的 API 里,缓存前缀按固定顺序构建:先是工具,然后是系统提示,最后是消息。工具部分只要有改动,后面所有内容都会失效。读取缓存前缀的价格是正常输入价格的十分之一;写入缓存前缀的价格则是输入价格的 1.25 倍,如果用的是一小时缓存,就是两倍。所以同一份工具列表,未命中时的成本大约是命中时的 12 倍,它后面的内容也一样。其他厂商对缓存输入的折扣力度不同,但道理相同:前缀完全一致就便宜,一旦变了就不便宜。

在单个会话内部,这基本不会造成影响。客户端在连接时获取 tools/list,并一直保留到服务器通知列表发生变化。真正出问题的是会话之间。如果你用同一批服务器跑很多 agent 会话,缓存前缀会从一个会话延续到下一个,直到某个字节发生变化。一个每天重写两次定义的服务器,会让所有使用它的用户的缓存都被重置。

我们如何统计

对于那一周里的每一次变更,我们都取出两份存储的工具列表——变更前和变更后——逐个工具进行比对,每一步都忽略越来越多的内容:先是空白字符;然后是日期和时间戳(2026-10-28、2026-10-28T08:24:54Z);再是小数,比如 1.6.12 或 0.009027;最后忽略所有剩下的数字。

两个列表在哪一步变得相同,就说明了变更类型:

变更内容 服务器数 工具数
工具增删 3,032 1,840
措辞或 schema 6,555 3,369
只有数字(如计数、上限) 657 160
只有小数 134 43
只有一个日期或时间戳 331 27
只有空白字符 3 2

HISTOR 的摘要会对工具和键排序,所以只改了顺序的列表根本不会被记作变更,也不会出现在这里。另外,HISTOR 每次读取最多只看到一个变更,读取间隔在 30 分钟到 6 小时之间。一个每分钟重写一次日期的服务器,在每次读取里也只表现为一个变更。因此这些计数只是下限。

日期从哪来

有 331 个日期类变更里,237 个来自同一个服务器。它的 whats_new 工具会描述最近一次改进,以及服务器上次读取自身信息源的时间:“Live now (feed read 2026-10-28T08:24:54Z)”。每读一次信息源,工具列表就被重写一次。

其余 94 个纯日期变更来自 26 个服务器,共有四种模式。

告诉模型今天是几号。 例如 “Today is 2026-10-28 (UTC) — never infer the date from the…”、“Hoy es 2026-10-28”、“Current date is 2026-10-28.”、“Current server time: 2026-10-25T02:36:38Z (UTC). Use this as your…”。这个思路本身没问题:模型不知道日期,而预订或排期工具需要日期。但把日期写进描述里,工具列表就每天都会变。

新鲜度时间戳。 例如 “measured 2026-10-28”、“indexed on 2026-10-26”、“list prices verified 2026-10-27”、“as of 2026-10-29”、“Rules version 2026-10-28.1”。它们告诉读者数据有多新。数据一变它们就变,可能每天都会变。

随日历变化的示例。 有个预订服务器的输入 schema 里,示例开始时间每天往后推一天:先是 2026-10-29T10:00:00-07:00,然后变成 2026-10-30T10:00:00-07:00。同一家发布商有三个服务器都这么做。

启动时设置的默认值。 比如标题默认值写成 “Conversation 2026-10-26T21:47:21.670Z”,或者 start_date、end_date 的默认值用了服务器启动的时间。这些是 npm 包,每次启动日期都会变。

这两类里共有 2,119 个字段存在差异,其中 1,963 个是工具描述,156 个在输入 schema 里:默认值、示例和属性描述。

十进制数字是另一回事。那 134 处只动了小数的改动,看上去是同一类噪音,其实不是。其中约 126 处是写进描述里的价格:“sold per call … at 0.009027 USDC”,接着变成 0.008588;“Price $0.01/call”,然后 $0.05;还有个工具从“$1.00”跳到“$222.00”。另有 5 处是版本号字符串,比如某个规则引擎从 1.6.12 升到 1.7.1。

对按次付费的 agent 来说,价格变了就是实质变化,理应记进工具列表的历史里。但如果价格每隔几小时就动一次,把价格放在描述里代价太高:每动一次,所有客户端的缓存都要失效,连那些永远不会调用这个工具的客户端也跟着遭殃。会浮动的价格,更适合在调用时现报。走 x402 的付费 MCP 工具,本来就会在 HTTP 402 响应里带上报价。

日期该放在哪

如果你维护一个 MCP 服务,又想告诉模型当前日期、或者数据有多新,有些地方不会碰到工具列表:

这些做法都不会减少模型能知道的信息,只是把会变的部分,从每个请求都要共享的那一块里挪了出去。

给客户端和优化器的建议

如果你在做客户端或 token 优化器,每个服务端的变更历史都是公开的。HISTOR 的变更 API(https://histor.modelmarket.dev/api/v1/changes)会列出每一条记录在案的变更,附带两侧的摘要和逐字段的差异对比。一个服务端如果一周内改了七次以上定义,就适合每个会话只拉取一次并固定下来。

本周有 218 个服务器出现了这种改动,合计 3,661 次变更,占总量三分之一。本周的统计数据、脚本以及文中提到的示例都放在 https://histor.modelmarket.dev/datasets/cache-churn-2026-11-01/ 。披露:我们就是 HISTOR 的开发方。分析、代码和写作过程中使用了 AI 辅助。统计数据来自 HISTOR 的生产数据库,只读访问,在一次性容器中完成。

上一篇 文章的读者 Paul Irolla 正在为编码智能体做 token 优化器,他指出了我们此前只当作正确性问题来看待的一件事。一次模型请求是从工具列表开始的。工具定义里改动一个字节,建立在这条请求之上的所有提示词缓存(prompt cache)就全部失效,哪怕这个改动本身毫无意义。HISTOR 是我们公开的 MCP 工具定义日志,它记录的每一次变更都能查到,于是我们做了统计。在 10 月 25 日到 11 月 1 日这一周里,它记录了 10,712 次工具列表变更。

查看原文