☰
SEP-2663 Tasks 扩展实战:MCP 长任务从轮询到推送的配置骨架
2026/9/28 19:50:01 网站建设 项目流程

1. 为什么你的 MCP 长任务还在傻等轮询

如果你正在用 MCP 跑分钟级甚至小时级的任务,大概率写过这样的循环:调一次tools/call,拿到一个task_id,然后sleep(2)再查一次状态,直到status变成completed。这套轮询逻辑能跑,但代价很实在——连接被反复占用、进度条只能靠猜、客户端和服务端都在做无用功。

SEP-2663 Tasks 扩展想解决的就是这件事。它给 MCP 补上了任务生命周期语义:queued → running → completed / failed / cancelled,再配合 Progress notifications 和 Subscriptions/listen,让服务端在任务状态变化时主动把结果推给客户端,而不是客户端一遍遍问「好了没」。换句话说,长任务的结果应该主动到达,而不是你反复去捞。

这篇面向已经用轮询等结果的开发者,给出config.toml与settings.json里的 Tasks 推送配置骨架、CC Switch 切换验证步骤,以及从轮询改成推送后可复现的验证动作。适合谁:自建 Agent 平台、写过私有 job API、被长任务轮询折磨过的后端和工具链同学。读完你能拿到一套能直接抄的配置骨架,并知道怎么确认推送真的生效了。

2. TaoToken 前置:把模型与任务通道接起来

推送化改造不只是改客户端逻辑,模型侧和任务通道得先通。我习惯用 TaoToken 做统一入口,它的 API 地址是https://taotoken.net/api,官网在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。先在控制台建一个 Key,后面config.toml和settings.json都要用到。

拿 Key 的入口在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面把请求格式、鉴权头、错误码都列清楚了,配置前扫一遍能省不少排障时间。

如果你只是想先验证模型对话通不通,可以用模型对话页快速试一条:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite。但本篇重点是 Tasks 推送,模型对话只是前置确认,别在这里停太久。

注意:Tasks 推送依赖服务端主动发消息,所以你的 MCP Server 必须支持长连接或 SSE 通道。如果服务端还是纯无状态短请求,推送配置写了也不会生效,这点后面排障会再讲。

3. 可复制配置:config.toml 与 settings.json 骨架

先给config.toml的骨架。这里的关键是把任务模式从poll切成push,并声明订阅通道。字段名按你实际 Server 实现微调,但结构可以直接抄:

[mcp] endpoint = "https://taotoken.net/api" api_key = "sk-你的Key" transport = "sse" [mcp.tasks] mode = "push" # 从 poll 改为 push task_id_field = "task_id" status_field = "status" subscribe_channel = "tasks/events" progress_notifications = true heartbeat_interval_ms = 15000 reconnect_backoff_ms = 2000 max_reconnect_attempts = 5 [mcp.tasks.states] queued = "queued" running = "running" completed = "completed" failed = "failed" cancelled = "cancelled"

mode = "push"是总开关,subscribe_channel是客户端订阅的事件通道,progress_notifications打开后服务端会推百分比或阶段。heartbeat_interval_ms用来保活,reconnect_backoff_ms处理断线重连。

再看settings.json,这里管客户端行为,重点是订阅回调和超时策略:

{ "mcp": { "tasks": { "mode": "push", "subscribe": { "channel": "tasks/events", "on_event": "handleTaskEvent", "auto_resubscribe": true }, "timeout": { "task_max_ms": 3600000, "idle_ms": 120000 }, "fallback": { "enable_poll": false, "poll_interval_ms": 5000 } } } }

fallback.enable_poll建议先设false,逼自己走通推送链路;等稳定后再考虑作为兜底打开。task_max_ms给到一小时,覆盖小时级任务。on_event指向你的事件处理函数,收到completed时直接取结果,收到running时更新进度。

两个文件的分工要清楚:config.toml管连接和任务语义,settings.json管客户端订阅和超时。改完别急着跑,先做下一步的切换验证。

4. CC Switch 切换验证与推送成功结果

CC Switch 用来在轮询和推送两套配置间切换,方便对比。切换步骤:

第一步,备份当前轮询配置,避免改坏回不去。第二步,把config.toml的mode改成push,settings.json的fallback.enable_poll设false。第三步,执行切换命令:

cc-switch --profile mcp-tasks-push --config ./config.toml --settings ./settings.json

第四步,确认切换生效:

cc-switch --status

输出里应该看到mode: push和channel: tasks/events。如果还是poll,说明 profile 没加载对,检查路径。

验证推送是否真的生效,用一个可复现的动作:发起一个长任务,然后不要写轮询循环,只监听事件。下面是一段最小验证代码:

import json from sseclient import SSEClient def handle_task_event(event): data = json.loads(event.data) print(f"task={data['task_id']} status={data['status']}") if data["status"] == "completed": print("结果:", data.get("result")) url = "https://taotoken.net/api/mcp/tasks/events" client = SSEClient(url, headers={"Authorization": "Bearer sk-你的Key"}) for event in client: handle_task_event(event)

成功结果长这样:任务发起后,终端先打印status=running,可能带progress=30,任务完成后自动打印status=completed和结果。整个过程你没有发过一次查询请求。如果只看到running没有completed,多半是服务端没推终态,去排障章节对号入座。

5. 本篇常见错排查

推送配置写了但收不到事件。先确认transport是sse而不是普通 HTTP。纯短请求通道没有长连接,服务端推不过来。再确认subscribe_channel和服务端实际通道名一致,大小写和斜杠都算。

收到 running 但永远等不到 completed。检查服务端任务状态机是否真的会写终态。有些实现只在内存里改状态,进程重启就丢,客户端自然等不到。另外看task_max_ms是不是设太短,任务没跑完就被客户端超时断开了。

断线后事件丢失。打开auto_resubscribe,并把reconnect_backoff_ms调到合理值。如果服务端不支持断点续传,重连后要主动拉一次当前状态做补偿,别只依赖推送。

CC Switch 切换后行为没变。大概率是 profile 缓存。执行cc-switch --status确认当前 profile,必要时清缓存再切。还有一种情况是两个配置文件路径写反了,config.toml和settings.json别搞混。

轮询和推送同时开着导致重复处理。把fallback.enable_poll关掉,或者确保事件处理函数幂等,同一个task_id的completed只处理一次。

6. 长期编码与 Agent 场景的下一步

如果你只是偶尔跑长任务,上面的配置够用了。但如果你在做长期编码、Agent 长跑这类场景,任务数量和并发会明显上来,这时候建议把任务通道和模型调用统一管理,用 Coding Plan 把额度、并发和任务订阅放在一起规划:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。

我自己的做法是先把内部 job 模型抽象成可查询的task_id,工具返回值区分同步结果和 task handle,日志里带上task_id方便和链路追踪关联。这样等 SEP-2663 定稿时,你只需要换配置,不用推翻整套私有 job API。推送化改造现在做,比 spec 定稿后再返工便宜得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询