首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MCP Server 上线之后:怎么知道模型都在拿你的工具干什么

MCP Server 上线之后:怎么知道模型都在拿你的工具干什么

原创
作者头像
用户11136834
发布于 2026-10-12 22:42:05
发布于 2026-10-12 22:42:05
110
举报

MCP Server 上线之后:怎么知道模型都在拿你的工具干什么

前言

上一篇聊 MCP Server 的测试,结尾留了个钩子:可观测性。这篇把它补上。

先描述一个上线后最常见的场景:用户反馈"AI 助手答非所问",你怀疑是模型乱调了工具,但手头没有任何记录——不知道那次会话里模型看到了哪些工具、发了什么参数、工具返回了什么。你唯一能做的是在自己电脑上连一遍,复现看看。"感觉修好了",但你其实说不清刚才那次的真实原因。

传统 Web 服务有成熟的可观测性方案,日志、指标、链路追踪三大件。MCP Server 大体也能套用,但有两个本质差异,直接决定了方案怎么做:

  1. 调用方是模型,参数是模型生成的。参数里可能带着用户的敏感数据(文件路径、查询语句、邮件内容),日志里全量打印参数,等于把用户数据抄了一份到日志文件——你自己制造了一个泄露面;
  2. 一次模型决策可能触发一串工具调用。链路的"根"不在 HTTP 入口,而在某轮对话里,传统按请求串 trace 的思路需要调整。

这篇文章讲我落地的做法:协议内日志、结构化调用日志、按工具分维度的指标、可回放的审计记录。代码仍是 TypeScript 官方 SDK。

一、先分清两条通道:协议内日志 vs 本地日志

MCP 协议自带 logging 能力,这是很多人没注意到的。Server 声明 capabilities.logging 之后,可以随时向客户端推日志消息:

代码语言:typescript
复制
const server = new Server(
  { name: "todo-server", version: "1.0.0" },
  { capabilities: { tools: {}, logging: {} } },
);

// 工具执行中向客户端/用户报告进度和状态
await server.sendLoggingMessage({
  level: "info",
  logger: "todo-server",
  data: "已扫描 120 个文件,开始匹配关键词",
});

客户端(如果实现了 logging)会把消息展示给用户。这适合放过程可见的信息:长任务做到哪一步、准备批量处理什么。对用户来说,工具不再是"按下回车后黑箱三分钟"。

但要清楚它的边界:协议内日志是给"这次会话的用户"看的,不可检索、不会持久化(取决于客户端)、你自己的排查也用不上。所以本地结构化日志同样要做,两者不互相替代:

  • 协议内日志:面向用户,报进度、报状态;
  • 本地日志:面向你自己,记录完整调用事实,供排查和审计。

还有一个 stdio 特有的约束,测试篇提过再强调一次:stdio 模式下 stdout 是协议通道,本地日志只能写 stderr 或文件。日志框架配置错目的地,是 stdio server 的头号自毁方式。

二、工具调用日志:记什么、不记什么

核心原则:一次 tools/call 记一行结构化日志,够排查问题,但不复制用户数据。

代码语言:typescript
复制
type ToolCallLog = {
  ts: string;            // ISO 时间
  sessionId: string;     // 会话标识(自增即可)
  tool: string;          // 工具名
  argDigest: string;     // 参数摘要,不是参数全文
  outcome: "ok" | "tool_error" | "thrown";
  durationMs: number;
  resultDigest?: string; // 结果摘要
};

关键设计是 argDigest(参数摘要):

代码语言:typescript
复制
function digestArgs(args: Record<string, unknown>): string {
  return Object.entries(args)
    .map(([k, v]) => {
      const s = String(v);
      const shown = s.length > 24 ? s.slice(0, 24) + `…(${s.length})` : s;
      return `${k}=${shown}`;
    })
    .join("&");
}

记参数的"形状"而不记内容:路径记到文件名、查询记前 24 个字符加长度。排查"模型传了空参数""参数类型不对"这类问题完全够用,同时避免把用户邮件正文、数据库记录写进日志。如果某个工具确实需要完整参数才能排查,做成开关配置 + 默认关闭,并在文档里写明这会记录用户内容。

用装饰器把记录逻辑统一包住所有工具,避免每个 handler 里手写:

代码语言:typescript
复制
const timed = <T>(
  name: string,
  fn: (args: Record<string, unknown>) => Promise<T>
) =>
  async (args: Record<string, unknown>) => {
    const start = Date.now();
    let outcome: ToolCallLog["outcome"] = "ok";
    try {
      return await fn(args);
    } catch (e) {
      outcome = "thrown";
      throw e;
    } finally {
      log.info({
        tool: name,
        argDigest: digestArgs(args),
        outcome,
        durationMs: Date.now() - start,
      });
    }
  };

注意 outcome 只在 catch 里改标记、finally 里统一落日志——isError 形式的"业务失败"不抛异常,会在正常返回路径记录,想区分的话在返回前检查一次 result.isError 把它标成 tool_error。

三、指标:按工具名分维度,别只看平均值

有了结构化日志,指标就是聚合问题。最少要这三个,全部按工具名分维度:

  • 调用次数:哪个工具被用得多,直接指导优化优先级;
  • 错误率:tool_error + thrown / total。某个工具错误率突然抬升,往往是依赖的外部服务在抖动;
  • 耗时分布:P50 和 P99,不要只看平均值。

为什么 P99 特别重要?模型客户端普遍对工具调用有超时,慢工具会触发模型侧重试,重试又把负载打回去,形成恶性循环。平均值 200ms 的工具如果 P99 是 8 秒,用户看到的就是"经常转圈失败",而你的平均值监控一片绿。

实践里我给每个工具设一个软预算(比如 3 秒),接近预算就先推协议内日志("正在查询更多数据…"),让用户和模型都知道工具还活着。

四、审计日志:为"出事之后"留证据

《MCP 权限模型》那篇讲过:工具粒度的授权需要知道"谁在什么上下文里调了什么"。落到实现就是审计日志,和第二节的运行日志相比,它多了两个诉求:

  • 身份:哪个客户端、哪个(如果有的话)终端用户。stdio 本地场景身份意义不大,HTTP/远程部署时这列是命根子;
  • 不可变性:审计记录写独立的 append-only 文件(或直接进日志服务),和应用日志分开,避免"排查时顺手改两行"。

审计事件的最小集:时间、身份、工具名、参数摘要、结果状态、会话 ID。和运行日志几乎同构——所以我直接复用同一套 digest 逻辑,只是输出到不同 sink。每条记录加一行换行符分隔的 JSON(NDJSON),出安全事件时 grep 工具名 就能拉出完整时间线。

给一个务实的边界建议:单机个人工具不必上审计,运行日志足够;凡是"连了公司数据源、有多个使用者"的 server,审计从第一天就要有——它不能事后补。

五、一条端到端的排查路径

最后把工具串起来。收到"AI 回答不对"的反馈时:

  1. 拿会话时间点,在运行日志里按 sessionId 过滤,看模型实际调了哪些工具、参数摘要是什么形状;
  2. 参数明显异常(空参、类型不对)→ 回到工具 schema 和描述,八成是描述有歧义,模型在猜;
  3. 参数正常但结果 tool_error → 工具自身问题,看错误率和下游依赖;
  4. 调用都正常、答案仍错 → 才是模型侧的问题(上下文、prompt),此时你才需要动 prompt。

这条路径的价值在于把"玄学问题"变成分层排查:先工具层,再模型层,而不是对着聊天记录猜。

写在最后

清单版总结,给要上线的 MCP Server:

  • 协议内 logging:长任务报进度,给用户看;
  • 本地结构化日志:一行一次调用,参数记摘要不记全文,stdio 只写 stderr/文件;
  • 指标按工具名分维度,盯错误率和 P99,不只看平均;
  • 多人/连公司数据的 server,第一天就开 append-only 审计;
  • 排查顺序:工具参数 → 工具结果 → 才轮到模型。

到这里,MCP Server 的工程化三篇(测试、可观测性,加上安全四篇)就齐了。下一篇可能聊聊部署形态的选择:stdio 子进程、本地 HTTP 和远程托管各自适合什么场景。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • MCP Server 上线之后:怎么知道模型都在拿你的工具干什么
    • 前言
    • 一、先分清两条通道:协议内日志 vs 本地日志
    • 二、工具调用日志:记什么、不记什么
    • 三、指标:按工具名分维度,别只看平均值
    • 四、审计日志:为"出事之后"留证据
    • 五、一条端到端的排查路径
    • 写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档