每次 MCP 工具调用应该记录什么

在客户端眼里,远程 MCP 服务器只是一个看不到内部的 span,目录仪表板上只有汇总数字。只有服务器看到了这次调用。本文逐个字段介绍 Tajo 的 MCP 服务器为每次调用写下的那一行,其中清除了什么,以及还有什么没有构建。

MCP 服务器 日志

MCP 服务器应该为每次工具调用记录什么?

每次工具调用记录一行,记在服务器上,因为只有服务器看到了它。Tajo 的 MCP 服务器记录谁发起了调用(密钥的 ID,绝不记录其密钥值),哪个服务器和连接器,工具存在时的工具,它的影响类别,ok 或简短的错误代码,耗时,结果大小,追踪 ID,调用涉及的记录,以及清除了密钥和个人数据的参数与响应。这些行通过 logs.list 和 logs.get 读取,30 天后删除。它们与带哈希链的审计日志分开存放,审批及其输入摘要保存在审计日志中。尚未构建:健康评分、错误率告警、按工具或追踪 ID 搜索,以及导出到你自己的追踪系统。

了解更多
在本页面

2026年6月8日,Anthropic 为 Claude 目录中连接器的所有者提供了一个仪表板。它显示活跃用户数、工具调用总数和目录排名。它还提供“健康评分、错误率和延迟一目了然,并按工具细分错误,精准定位哪里出了问题”。

一年前,Weights & Biases 的 Alex Volkov 从另一端描述过同一个问题。当他的代理开始通过 MCP 调用工具后,“我习惯的、自己的代码从头到尾运行时所拥有的那种可观测性,变得有点暗了”。与他共同演讲的 Dylibso 的 Benjamin Eckel 说明了原因。远程 MCP 服务器运行在调用方的控制之外,所以它“在追踪中只表现为一个单一的服务 span”。

这两种视角都在服务器之外。客户端看到的是一个看不到内部的 span。目录看到的是汇总数字。服务器被要求做了什么、拒绝了什么、触及了什么,两者都没有看到。只有服务器看到了,所以也只有服务器能把它记下来。

下面是 Tajo 的 MCP 服务器记下的内容,读自运行它们的代码,文档中也有说明。什么是 MCP 服务器介绍了协议本身。本文讲的是日志里的那一行。

每次调用一行

仪表板是港务长的视角:进港的船、出港的船、哪些晚到了。请求日志则是船自己的航海日志,在当时、在船上写下。

在 Tajo 的工作区服务器和每个连接器服务器上,每一次 tools/call 都以一行记录结束。这一行在响应构建完成之后写入,无论响应是什么。因为密钥没有写入权限或因为端点是只读而被拒绝的调用,也和其他调用一样得到一行。调用一个不存在的工具也是如此。

下表列出这一行保留什么,以及拒绝保留什么。

字段保留不保留
谁发起调用的 API 密钥的 ID。通过 OAuth 登录获得的令牌也算作密钥。密钥的密钥值或其前缀。
在哪里工作区服务器或某个连接器服务器,以及其连接器和配置。
工具当服务器有该名称的工具时,记录其名称。客户端发送的任何其他名称:一律存为 (unknown)。
影响read、write、send 或 destructive:该调用可能产生的最重影响。
结果ok,或简短的 snake_case 错误代码,例如 unknown_tool。错误描述文字:任何不是代码的内容都存为 tool_error。
耗时与大小从到达到响应的毫秒数,以及调用方收到的字节数。
追踪 ID请求的 ID,取自 x-request-id、x-trace-id 或 W3C traceparent 头,没有则新生成一个。既不是十六进制也不是 UUID 的头部值。
记录调用指定或产生的记录 ID,最多 20 个:联系人、消息、审批。记录本身。
正文参数和响应,经过清洗,每个最多 4 KB。密钥、自由文本,以及下文列出的个人数据。

其中有三项拒绝针对的是客户端,而不是数据。工具名、错误代码和追踪 ID 都来自外部。每一项只以服务器能够担保的形式保留。一个照单全收、存下调用方所发任何内容的日志,可以被塞进任何东西,包括在工具名一列里出现客户的邮箱地址。

Benjamin Eckel 在演讲结尾留下的开放问题是:采集器如何知道“这是一个出错的 MCP 工具调用”。在一台服务器上,答案就是一列。ok 为 false,代码说明是哪种错误。

参数:我们推翻的一条规则

反对意见很合理,而且它本来就是我们自己的。记录参数,就等于在所有删除请求之外又建了一份客户数据的副本。在 2026年10月18日之前,这正是我们自己的规则。日志既不保留参数,也不保留结果正文,因为两者都可能包含个人数据。

这是一个错误的取舍。一个无法显示发送了什么、返回了什么的请求日志,就无法回答它存在的唯一问题:我的代理的调用为什么失败了?

所以我们保留正文,并用另一种方式解决当初不保留的理由。每个正文在进入队列之前都要经过一次确定性处理。

  • 任何密钥都留不下来。 字段名表示 token、password、secret、signature、cookie、session 或大约二十个类似词语的字段,只保留 [secret]。符合已知凭据格式的值也一样,无论出现在哪里。这包括 bearer 令牌、JWT、私钥、带密码的 URL,以及任何连续 40 个或更多密钥字符的不透明字符串。
  • 文字内容完全不保留。 名为 html、text、body、markdown 或 content 的字段只保留 [redacted]。文字中含有任何模式都找不到的姓名和街道地址。
  • 个人数据被永久遮盖。 字段名表明是个人数据的字段,例如邮箱、电话、姓名、地址或备注,只保留 [redacted]。其他任何字符串中的邮箱地址、电话号码、银行卡号或 IBAN 都会被遮盖。
  • 正文保持精简。 字符串在 512 个字符处截断,列表在 20 项,对象在 50 个键,嵌套在 6 层。整个正文在 4 KB 处截断,并标记为已截断。

有两类调用保留得更少。用户的聊天从其他应用带进来的联系人,只记录一个标记和数量,绝不记录正文。读取日志的调用不保留响应,因为响应不过是其他日志行。

再说成本。遮盖是永久性的,而不是某人持有密钥的加密。日志无法显示退信的那个邮箱地址。它显示的是联系人的 ID,而联系人本身在其自身规则的约束下,只需一次调用就能取到。日志不在工作区数据清除流程的范围内,所以遮盖不能等到清除时再做。

为什么日志不是审计追踪

Tajo 已经有一个只追加的审计日志。每一行都带有前一行的哈希,所以任何地方的修改都会打断链条。那为什么不把工具调用写进去?

因为链条有代价。每次追加都要获取一个工作区级别的锁,并读取前一行来延长链条。如果每次读取都写进去,工作区的调用就会排在同一把锁后面。审计日志存在的意义是展示状态变更,而这些变更也会被成千上万条读取记录淹没。

所以两份记录按问题分工。请求日志说明代理请求了什么、发生了什么,保留 30 天。审计日志说明什么发生了变化、谁允许了它,以及之后是否有人修改过这条记录。

审批属于审计这一侧。在 Tajo 的连接器配置中,propose 工具准备一个已声明的操作,apply 工具在它获批后执行。在提案获批之前,什么都不会发送到服务提供商,审批可以由人完成,也可以由工作区开启的规则完成。propose 调用的那一行会在其记录中列出它创建的审批。审批本身绑定到确切输入的 SHA-256 摘要上。审批只覆盖你看到的那份确切输入:改动一个字段,它就不再匹配。写入生效时,它的审计记录保留输入摘要和服务提供商响应的摘要,绝不保留响应的值。

一行的成本

一行就是一次数据库写入,而写入就要提交。在 2026年10月8日之前,每次调用都要在响应前等待自己的提交,在我们的主机上大约 2.6 毫秒。对某个客户端来说,这相当于一次小型工具调用的四分之一。

现在,这一行在调用结束时构建并放入队列。刷写操作最多 100 毫秒后写入队列,每个工作区一个事务,因此许多调用共享一次提交。行的内容没有任何变化,包括时间戳。如果有 5,000 行在等待,下一次调用会等待刷写完成,而不是丢弃一行。

记录永远不会让调用失败。写入失败时会逐行重试,然后记录并丢弃。进程被强制终止时,丢失的是队列中的内容:最多是最后 100 毫秒的调用。

读回日志

logs.list 返回工作区的调用,最新的在前,不含正文。它可以按时间、按结果或 HTTP 状态、按来源(MCP 或与之共用这张表的 REST API)以及按 API 密钥筛选。默认每页 50 条,最多 100 条。logs.get 返回单次调用,附带密钥的当前名称、状态、耗时、清洗后的请求和响应,以及它涉及的记录。

两者都只读取调用方所在的工作区。来自其他工作区的行 ID 是找不到的。

工具错误会带上请求 ID。当这个 ID 由服务器生成,或者以十六进制或 UUID 的形式传入时,它就是这一行的追踪 ID。收到错误的代理可以把这个唯一标识该调用的值交给人。

已经构建的和尚未构建的

截至本文写作时已经构建的:

尚未构建的,在依赖它之前值得了解:

  • 没有健康评分,没有按工具的图表,也没有错误率上升时的告警。按工具汇总的调用数、错误数和最慢耗时只提供给 Tajo 的运维人员,不在你能打开的任何界面上。
  • logs.list 不能按工具名或追踪 ID 筛选。要找到某个请求 ID 对应的行,请按时间和密钥缩小范围。
  • 这些行不会导出到你运行的追踪系统。追踪 ID 只从 HTTP 头中读取,不从演讲中用来传递追踪上下文的 MCP _meta 字段中读取。
  • 30 天后,行会被删除。没有归档,也没有导出。
  • 被遮盖的内容无法从日志中恢复。
  • 强制终止可能丢失最后 100 毫秒的行。

演讲最后指出,MCP 中的可观测性“分布并不均匀”。下游的仪表板解决不了这个问题,止步于服务器门口的追踪也解决不了。失败的同步需要自己的记录;每一次提出过请求的调用也是如此。

服务器看到了这次调用。别人都没有看到。就在那里把它记下来。

常见问题

MCP 服务器应该为每次工具调用记录什么?

每次调用一行,由服务器写入:哪个 API 密钥发起了调用,哪个服务器和连接器,工具存在时的工具名,它的影响类别,ok 或简短的错误代码,耗时,结果大小,请求的追踪 ID,调用指定或产生的记录 ID,以及清除了密钥和个人数据的参数与响应。Tajo 的 MCP 服务器为每一次 tools/call 写下这一行,被拒绝的调用也不例外,并保留 30 天。

MCP 请求日志应该保留工具参数吗?

应该,但要先清洗。没有参数的日志无法说明调用为什么失败。Tajo 在 2026年10月18日之前不保留任何正文,现在两者都保留,但要先经过一次确定性处理:密钥变成 [secret],自由文本和个人数据字段变成 [redacted],邮箱、电话、银行卡号和 IBAN 被遮盖,每个正文在 4 KB 处截断。遮盖是永久性的,因此日志通过 ID 指向记录,而不是复制记录。

为什么要把 MCP 工具调用日志与审计日志分开?

因为它们以不同的成本回答不同的问题。Tajo 的审计日志是一条记录状态变更的哈希链,每次追加都要获取一个工作区级别的锁来延长链条。如果每次读取都写进去,工作区的调用就会排在这把锁后面,变更也会淹没在读取记录里。工具调用写入一张单独的、不带链条的表,保留 30 天;调用准备的写入操作仍然会留下各自的审计记录。

申请抢先体验

请输入您的名字和电子邮箱地址。我们会发送确认 Tajo 访问申请的链接。

获取Brevo

本页的 Markdown: what-to-log-for-every-mcp-tool-call.md

正在加载 Markdown…