首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MCP 长任务三件事:进度上报、取消与重连后的状态恢复

MCP 长任务三件事:进度上报、取消与重连后的状态恢复

原创
作者头像
用户11136834
发布于 2026-10-16 19:46:06
发布于 2026-10-16 19:46:06
180
举报

前言

《MCP Server 部署形态怎么选》里划过一条分界线:stdio 适合短平快的本地工具,HTTP 适合跨进程的共享服务。但有个问题没展开——不管哪种形态,工具一旦变成"长任务"(建索引、批量转码、跑数据迁移),一次请求一次响应的简单模型就不够用了。

模型发起一次调用,等三分钟没动静,它会怎么办?重试?放弃?用户盯着转圈?这一篇把长任务的三个必答题讲清楚:进度怎么报、怎么取消、断线之后怎么恢复。三个都做对,工具才能从"玩具"变成"生产可用"。

一、进度上报:progressToken 机制

MCP 对进度有原生支持,机制是回调式:客户端发起请求时在 _meta 里带一个 progressToken,服务端在执行过程中用它发送 notifications/progress 通知。

服务端(TypeScript SDK)大致长这样:

代码语言:typescript
复制
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const token = request.params._meta?.progressToken;
  const total = files.length;
  for (let i = 0; i < total; i++) {
    await processFile(files[i]);
    if (token !== undefined) {
      await server.notification({
        method: "notifications/progress",
        params: {
          progressToken: token,
          progress: i + 1,
          total,
          message: `已处理 ${files[i].name}`,
        },
      });
    }
  }
  return { content: [{ type: "text", text: `全部 ${total} 个文件处理完成` }] };
});

三个实践细节,都是踩出来的:

  1. 节流。别每个文件发一条通知——一万个文件就是一万条 JSON-RPC 消息,传输层直接被淹没。按时间(如每 500ms 一条)或比例(每 1%)节流,体验没有区别;
  2. progress 是通知不是请求,服务端发完不等待、不重试。客户端没带 token 就说明它不关心进度,别发;
  3. message 带人话。progress: 4392, total: 10000 不如 正在处理 4392/10000:reports_2024.pdf——这条消息大概率会被展示给最终用户。

二、取消:协作式的,不是强杀

MCP 的取消是客户端发 notifications/cancelled,携带要取消的请求 ID。关键是理解它的语义:取消是协作式的。协议只是"捎话"说别干了,服务端代码里必须有对应的检查点,任务才会真的停下来。

代码语言:typescript
复制
const cancelled = new Set<string>(); // 记录已取消的请求 ID

server.setNotificationHandler(CancelledNotificationSchema, (n) => {
  cancelled.add(n.params.requestId);
});

// 长任务循环里定期检查
for (let i = 0; i < total; i++) {
  if (cancelled.has(requestId)) {
    await cleanup(partialResults);   // 清理半成品
    throw new RequestCancelledError();
  }
  await processFile(files[i]);
}

两个必做的配套:

  • 清理半成品。取消后留下写到一半的索引文件、传到一半的临时目录,下次运行就可能读到脏数据。取消路径要和异常路径一样认真对待;
  • 幂等的取消。取消通知可能重复到达、可能在任务已完成后到达。服务端对"取消一个不存在的请求"应该静默忽略而不是报错。

三、重连之后:把长任务做成可查询的作业

这是最容易被忽略的一环。stdio 场景下连接断了进程就没了,任务状态自然丢失;HTTP 场景连接可以重连,但请求-响应的对应关系已经断了——客户端重连后拿着旧的请求 ID 等响应,是等不到的。

根治的思路是换模型:别把长任务建模成一次 RPC,建模成一个作业(job)。

  1. 工具 start_indexing 立即返回 { jobId: "job_8f3a", status: "running" },任务在服务端后台跑;
  2. 提供查询工具 get_job_status(jobId),返回进度、阶段、错误信息;
  3. 任务的输入参数里带一个幂等键(比如基于源文件清单的哈希)。客户端重连后不确定任务死没死,重新调用 start_indexing,服务端发现幂等键相同且任务还在跑,直接返回现有 jobId,不会重复执行。

这个模式把"断线恢复"从协议问题变成了应用问题:连接断了无所谓,作业的真相在服务端,客户端随时可以重新对齐状态。我们实测下来,这是唯一在移动网络环境下能稳定工作的方案。

四、一个判断标准

不是所有工具都需要这三件事。判断标准就一条:工具执行时间会不会超过客户端的等待意愿。

  • 秒级返回的查询类工具:什么都不用加,保持简单;
  • 十秒到一分钟:加进度上报就够了;
  • 分钟级以上:三件套全套上,并且优先考虑作业化。

把查询工具也套上作业模型是过度设计——多一次状态查询的往返,模型多一轮调用,没有任何收益。

小结

MCP 长任务的三件事:进度用 progressToken + 节流;取消是协作式的,检查点和半成品清理都要自己写;断线恢复靠作业化 + 幂等键,别指望连接替你记住状态。本质是把 RPC 思维升级成作业系统思维——服务端持有真相,客户端随时对齐。

下一篇计划写 MCP 的 sampling 与 elicitation:服务端反过来向客户端/用户借能力(采样、追问)时的设计边界,有兴趣可以关注。

本文是 MCP 系列的一篇,前作:《MCP Server 部署形态怎么选》《MCP Server 上线之后:怎么知道模型都在拿你的工具干什么》《别让大模型陪你跑测试:MCP Server 的分层测试实践》。

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

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

目录
  • 前言
  • 一、进度上报:progressToken 机制
  • 二、取消:协作式的,不是强杀
  • 三、重连之后:把长任务做成可查询的作业
  • 四、一个判断标准
  • 小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档