MCP 工具开始算钱了:给每个工具标注成本,给每次对话设预算

月底对账的时候你才发现:某个 MCP 工具被 agent 调了四十几次,每次都真金白银地扣额度。回看对话记录,它当时的表现和调用一个免费的读文件工具没什么区别——同样的 JSON 输入、同样的一行“完成”,你根本无从判断哪一次是在花钱。
这就是 MCP 生态现在最尴尬的地方:图片、视频、3D 生成、付费 API 都被包成了 MCP 服务器,而 agent 看待它们的方式和看待 read_file 一模一样。PR #142470(2026 年 10 月 21 日合并)给这个问题补了两块拼图:声明成本,以及每次对话的预算上限。
三个新配置项
在 config.yaml 的 MCP 服务器条目里:
mcp_servers:
renders:
command: "render-mcp"
trust: untrusted
tool_costs:
create_image: 20 # 每次大约 20 额度
animate: {credits: [5, 20], note: "per animation"} # 区间 + 一句说明
get_status: 0 # 免费,与不写等价
spend_limit: 100 # 每次对话的上限;0 或省略 = 不限制
cost_unit: credits # 弹窗里显示的单位,默认就是 credits
tool_costs 的值支持三种形态:
- 一个非负数,表示固定成本;
- 一个
[最小值, 最大值]区间,适合“按分辨率/时长计价”的工具; - 一个对象,用
credits字段放数字或区间,再用note补一句人话说明。
spend_limit 是每次对话的额度上限,写 0 或者不写就是不限制。cost_unit 只影响显示文案,默认 credits——你的服务按“次”“点”“token”计价,把它改成对应的词即可。
审批弹窗变成了账单
没声明成本之前,一个 trust: untrusted 的服务器在写入类调用前会弹一句“允许执行 create_image 吗”。声明之后,同一句话会变成:
Costs about 20 credits.
区间成本会显示成 Costs 5-20 credits (per animation).;而这个对话已经花过钱之后,还会多一行:
This chat has used about 40 credits on 'renders' (limit 100).
这两句话的价值在于把“要不要批准”从一个安全问题变成了一个预算问题。你批准的不再是一个动作,而是“再花 20 额度”。
累计到底记在谁头上
这块规则比配置项重要,因为它决定你会不会算错账:
- 按对话 × 按服务器记账,两个维度都是独立的。同一个服务器在你另一个聊天里的花费不算进来。
- 只统计真正发出去的调用。被拒绝的、被拦下的调用不计数。
- 按声明的估算值计,区间取上限。也就是说它宁可让你早点被问,也不会让你事后才发现超支。
- 一个调用在服务器还没收到就挂掉的情况,仍然计数——这是有意的,往“多问一次”的方向偏。
- 累计只存在内存里,它是“这次对话的预算”,不是厂商的账单。重启 Hermes 会清零。
- 上下文压缩、
/branch换会话 id 之后,累计跟着对话走;CLI 里/new会开新账,桌面版新开聊天同理;但消息平台上账挂在聊天本身,/new之后仍然延续。每个 cron 运行有自己独立的一份。
超过预算会发生什么
这是整套机制里最值得记住的一节:
- 在
trust: full的服务器上也会问。 平时这种服务器上花钱工具是直接跑的,一旦会越过spend_limit,它会先停下来问你。 - 每次都要问一次。 选“本会话内允许”也压不住它——后续每一次带成本的调用仍然会弹。
- 无人值守时直接拒绝,且不执行。 cron 任务、
hermes chat -q、没有审批人的网关,都属于这一类。模型收到的是一个明确说明“已花多少、上限多少、不要再重试”的工具错误,而不是静默失败。 - 没超预算时一切照旧。
trust: full照样不问;带readOnlyHint: true标注的只读工具不会被这条规则叫醒,除非预算要求。
换句话说,spend_limit 不是限速器,而是“越过这条线就必须有人点头”的闸门。
插件包里也能声明
可移植插件包可以在 plugin.json 的 extensions 里给它的服务器带同一张表:
"extensions": {
"com.nousresearch.hermes": {
"servers": {
"meshy": {
"trust": "untrusted",
"costs": { "meshy_remesh": 5, "meshy_animate": { "credits": [5, 20], "note": "per animation" } },
"spend_limit": 100,
"cost_unit": "credits"
}
}
}
}
这解决了一个现实问题:官方或第三方插件的作者最清楚自己每个工具花多少钱,而用户通常不清楚。包里的 costs / spend_limit / cost_unit 会自动翻译成原生配置项。
五个容易踩的坑
- 写错的成本表会让整个服务器被跳过,不是静默忽略。三个键里任何一个格式不对,那个服务器都不会加载,并报出具体是哪个键——这是有意为之:你不能因为写错一个缩进就把预算丢了。
tool_costs的键必须是服务器自己的工具名,不要写 Hermes 展示层加的前缀mcp_<server>_。写错不会报错,只在连接时给一条警告,很容易漏看。- 同名覆盖时的继承规则:你自己写一个同名的
mcp_servers条目(比如为了把它升到trust: full),包里的成本配置仍然生效,除非你在自己的条目里也写了这几个键。想彻底关掉包里的上限,写spend_limit: 0;想丢掉它的成本表,写tool_costs: {}。 - 它是预算,不是账单。厂商侧的实际计费可能和声明不一致,重启之后累计也会清零。别拿它当财务报表。
hermes plugins validate会列出server costs: <name>,装机前用它确认成本表被正确读到了,比事后查对话记录便宜。
现在能用吗
这个特性目前只在 main 上,不在已经发布的 v0.22.4 里(我对比了 v0.22.3 到 v0.22.4 的 14 个提交,没有它)。所以生产环境要等下一个版本;如果你跑的是源码安装并跟主干,现在就可以试。
来源上,这个设计的灵感来自社区项目 Adolanium/hermes-tripo——它用原生的 pre_tool_call 钩子实现了同样的“每次额度都要问一声、每次对话有个上限”。可移植包不能带钩子,所以这次做成了核心特性,靠声明式数据驱动,官方插件(比如 Meshy 那个)也能直接用上。
如果你还没决定哪些服务器该给 trust: full,可以先读我们之前的 MCP 目录安全指南,成本声明和信任分级解决的是两个不同的问题,但搭配起来才完整。