主页
当一次工具调用要跑五分钟、要等人审批、要熬过一次客户端重启——阻塞连接就不再是答案。Tasks 用一个持久句柄换掉了长连接,把「等结果」变成一件可恢复、可取消、可中途追问的事。
When a single tool call takes five minutes, waits on a human approval, or has to survive a client restart, holding the connection open stops being an answer. Tasks trade the long-lived connection for a durable handle, turning "waiting for a result" into something resumable, cancellable, and interruptible.
最朴素的做法是:工具调用发出去,连接一直挂着,直到活干完再返回结果。对一个 200 毫秒的数据库查询,这完全没问题。问题出在那些「不是立刻返回」的操作上——CI 流水线、批量数据处理、人工审批。官方文档给出了四个阻塞解决不了的问题,每一个都对应一类真实故障:
The naive approach is simple: fire the tool call, hold the connection open, return the result when the work finishes. For a 200 ms database query that is entirely fine. The trouble starts with operations that do not return immediately — CI pipelines, batch processing, human approvals. The official docs list four problems blocking cannot solve, and each maps to a real class of failure:
阻塞会把一条连接占满整个操作时长。大量客户端和中间层(负载均衡、网关、WAF)都有超时,超过几秒就不现实了。
Blocking ties up a connection for the whole operation. Many clients and transport intermediaries impose timeouts that make this impractical beyond a few seconds.
task ID 是一个持久句柄。客户端断线或重启后,拿同一个 ID 继续轮询即可,活还在那儿。
A task ID is a durable handle. If the client disconnects or restarts, it resumes polling with the same ID — the work is still there.
任务自带状态元数据(working、input_required、completed、failed、cancelled)和可选状态消息,客户端能看到进展。
Tasks carry status metadata (working, input_required, completed, failed, cancelled) plus optional status messages, so clients can see progress.
任务需要输入时(例如一次用户确认的 elicitation)转入 input_required 并把请求暴露出来,客户端用 tasks/update 回应——不需要第二条连接,也不需要服务端主动推消息。
When a task needs input (an elicitation for user confirmation, say) it moves to input_required and surfaces the request. The client answers via tasks/update — no second connection and no unsolicited server-to-client messages required.
还有第五条,是设计上最容易被忽略但工程上最省事的一条:是否转任务由服务端逐请求决定。客户端只需要在能力里声明一次「我支持 Tasks」,然后准备好接收两种形状的结果即可——不需要给每个工具做预热,也不需要在每次请求上打标记。
There is a fifth property, easy to overlook in the design but the one that saves the most engineering effort: the server decides per request whether to create a task. The client opts in once through the extension capability and then handles whichever result shape arrives — no per-tool warmup, no per-request flag.
Tasks 由 AWS 贡献,是 MCP 首批官方扩展之一。在 2026-07-28 规范中它从实验性核心正式移入 io.modelcontextprotocol/tasks 扩展(SEP-2663),同时引入基于轮询的 tasks/get 和新的 tasks/update。
Tasks was contributed by AWS and is one of MCP's first official extensions. In the 2026-07-28 specification it moved out of the experimental core and into the io.modelcontextprotocol/tasks extension (SEP-2663), together with a poll-based tasks/get and a new tasks/update.
Tasks 不是一套新的请求方法,而是对标准请求流程的一层扩展。当服务端判断某个请求会跑很久,它返回的不是最终结果,而是一个任务句柄;客户端随后轮询直到终态。整个过程分六步:
Tasks are not a new family of request methods; they are an extension of the standard request flow. When the server decides a request will be long-running, it returns a task handle instead of the final result, and the client polls until a terminal state. The whole process has six steps:
io.modelcontextprotocol/tasks;服务端在自己的 server/discover 能力里声明同一个扩展。CreateTaskResult(以 resultType: "task" 标识),内含 taskId、初始状态、TTL 和建议轮询间隔。任务必须在响应发出之前就已持久化创建。taskId 调 tasks/get,响应携带当前状态;终态时携带最终结果或错误。input_required 时,tasks/get 的响应里会带一个 inputRequests 映射(elicitation 或其它服务端请求),客户端通过 tasks/update 逐一回填。completed 时,result 字段里就是「如果同步执行本来会返回的东西」;状态是 failed 时,error 字段里是 JSON-RPC 错误。tasks/cancel。取消是协作式的——服务端确认收到意图,但没有义务真的停下来。io.modelcontextprotocol/tasks in its per-request capabilities; the server advertises the same extension in its own server/discover capabilities.CreateTaskResult (identified by resultType: "task") containing a taskId, an initial status, a TTL, and a suggested polling interval. The task must be durably created before the response is sent.tasks/get with the taskId. The response carries the current status and, for terminal states, the final result or error.input_required, the tasks/get response includes an inputRequests map with elicitations or other server requests. The client fulfills these via tasks/update.completed, the result field contains what the original request would have returned synchronously. If the status is failed, the error field carries the JSON-RPC error.tasks/cancel at any time. Cancellation is cooperative — the server acknowledges the intent but is not obligated to stop the work.Source: MCP Tasks · How Tasks work
五个状态,三个是终态。终态的定义很硬:一旦到达,任务状态就不再改变。这条约束是客户端能安全停止轮询、安全落盘归档的基础。
Five statuses, three of them terminal. The definition of terminal is strict: once reached, the task's state does not change. That constraint is what lets a client safely stop polling and safely archive the outcome.
| 状态 | 含义 | 终态 |
|---|---|---|
working | 操作正在进行中。 | 否 |
input_required | 服务端需要客户端提供输入才能继续,详见 inputRequests。 | 否 |
completed | 操作完成,result 字段是最终输出。 | 是 |
failed | 执行过程中发生 JSON-RPC 错误,error 字段含细节。 | 是 |
cancelled | 操作被取消(不保证一定被采纳)。 | 是 |
| Status | Meaning | Terminal |
|---|---|---|
working | The operation is in progress. | No |
input_required | The server needs client input before continuing. See inputRequests. | No |
completed | The operation finished. The result field contains the final output. | Yes |
failed | A JSON-RPC error occurred during execution. The error field has details. | Yes |
cancelled | The operation was cancelled (not always honored). | Yes |
input_required 是唯一会「退回」working 的中间态input_required is the only intermediate state that hands control back to workingcancelled 是终态,但「发了 tasks/cancel」不等于「任务会变成 cancelled」。取消是协作式的:服务端确认收到,但任务完全可能继续跑完并落到 completed。客户端逻辑必须能接受「我取消了,结果还是回来了」。
cancelled is terminal, but "I sent tasks/cancel" does not mean "the task will become cancelled". Cancellation is cooperative: the server acknowledges it, yet the task may well run to completion and land on completed. Client logic has to tolerate "I cancelled and the result came back anyway".
先看客户端怎么声明支持。注意它放在 每个请求的 _meta 里,不是一次性握手——这是 2026-07-28 无状态核心带来的直接后果:initialize/initialized 握手和 Mcp-Session-Id 头都已被移除,每个请求必须自描述。
Start with how the client declares support. Note that it lives in each request's _meta, not in a one-time handshake — a direct consequence of the 2026-07-28 stateless core: the initialize/initialized exchange and the Mcp-Session-Id header are both gone, so every request must be self-describing.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "run_ci_pipeline",
"arguments": { "branch": "main" },
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "run_ci_pipeline",
"arguments": { "branch": "main" },
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
}
服务端在 server/discover 的能力里声明同一个扩展:
The server advertises the same extension in its server/discover capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"capabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
于是同一个 tools/call 可能回来两种形状之一。这是客户端最需要适配的地方——结果是多态的:
As a result, the same tools/call can come back in one of two shapes. This is the main thing a client has to adapt to — results are polymorphic:
就是你一直在处理的那个 tools/call 结果,没有 resultType: "task"。
The tools/call result you have always handled, with no resultType: "task".
CreateTaskResultresultType: "task",内含 taskId、status、ttlMs、pollIntervalMs。
CreateTaskResultresultType: "task" with a taskId, a status, a ttlMs, and a pollIntervalMs.
轮询与回填的骨架代码(伪代码,任何语言 SDK 思路一致):
The skeleton of polling and answering (pseudocode; the shape is the same in every SDK):
result = call_tool("run_ci_pipeline", {branch: "main"})
if result.resultType != "task":
return result # 形状 A:直接就是答案
task_id = result.taskId
persist(task_id) # 关键:落盘,进程挂了还能续
interval = result.pollIntervalMs
while True:
task = rpc("tasks/get", {taskId: task_id})
if task.status == "input_required":
answers = {}
for key, req in task.inputRequests.items():
answers[key] = ask_user_or_model(req)
rpc("tasks/update", {taskId: task_id, inputResponses: answers})
continue # 回填后继续轮询
if task.status == "completed": return task.result
if task.status == "failed": raise TaskError(task.error)
if task.status == "cancelled": return None
sleep(interval) # 尊重服务端建议的间隔
result = call_tool("run_ci_pipeline", {branch: "main"})
if result.resultType != "task":
return result # Shape A: this is already the answer
task_id = result.taskId
persist(task_id) # Key step: durable, so a crash can resume
interval = result.pollIntervalMs
while True:
task = rpc("tasks/get", {taskId: task_id})
if task.status == "input_required":
answers = {}
for key, req in task.inputRequests.items():
answers[key] = ask_user_or_model(req)
rpc("tasks/update", {taskId: task_id, inputResponses: answers})
continue # keep polling after answering
if task.status == "completed": return task.result
if task.status == "failed": raise TaskError(task.error)
if task.status == "cancelled": return None
sleep(interval) # respect the server's suggested interval
来源:MCP Tasks · Implementation guide · 2026-07-28 规范:无握手无会话
Sources: MCP Tasks · Implementation guide · The 2026-07-28 Specification: no handshake or sessions
_meta 能力字段。tools/call 这类支持的请求时,准备好接收标准结果或 CreateTaskResult。tasks/get,尊重 pollIntervalMs,直到 completed / failed / cancelled。input_required 时读 inputRequests,把请求呈现给用户或模型,用 tasks/update 提交回应。_meta of every request.tools/call, be ready for either the standard result or a CreateTaskResult.tasks/get, respect pollIntervalMs, and continue until completed / failed / cancelled.input_required, read inputRequests, present them to the user or the model, and submit answers via tasks/update.server/discover 的 capabilities。CreateTaskResult 之前,必须确认客户端在本次请求里声明了该扩展。永远不要给没声明支持的客户端返回任务。CreateTaskResult——resultType: "task",带唯一 taskId、初始状态、ttlMs、pollIntervalMs。任务必须在响应发出之前完成持久化创建。tasks/get——每次轮询返回当前状态;终态时带上 result(completed)或 error(failed)。tasks/update——接受与未决 inputRequests 对应的 inputResponses,用空结果确认;对未知的或已满足的 key 直接忽略。tasks/cancel——用空结果确认。尽量执行,但要记住取消是协作式的,任务仍可能落到非 cancelled 的终态。server/discover capabilities.CreateTaskResult, verify that the client declared the extension on this request. Never return a task to a client that did not declare support.CreateTaskResult — resultType: "task" with a unique taskId, an initial status, ttlMs, and pollIntervalMs. The task must be durably created before the response is sent.tasks/get — return the current state on each poll; for terminal states include result (on completed) or error (on failed).tasks/update — accept inputResponses keyed to outstanding inputRequests, acknowledge with an empty result, and ignore responses for unknown or already-satisfied keys.tasks/cancel — acknowledge with an empty result. Honor it when possible, but remember cancellation is cooperative and the task may still reach a non-cancelled terminal status.服务端可以通过 notifications/tasks 推送状态更新,客户端通过 subscriptions/listen 机制按通知类型逐个订阅。每条通知携带完整任务状态,因此省掉一次额外的 tasks/get 往返。但要记住:轮询是默认路径,通知是可选优化——只有当服务端支持时,客户端才能依赖它代替轮询。
Servers can push status updates via notifications/tasks, which clients opt into per notification type through the subscriptions/listen mechanism. Each notification carries the full task state, eliminating the extra tasks/get round-trip. Keep in mind, though: polling is the default. Notifications are an optional optimization — a client can rely on them instead of polling only if the server supports them.
来源:MCP Tasks · For MCP clients / For MCP servers / Notifications
Sources: MCP Tasks · For MCP clients / For MCP servers / Notifications
2026-07-28 一次性引入了三套「不是普通一问一答」的机制,很容易搞混。它们解决的是三个不同的问题:
The 2026-07-28 release introduced three mechanisms that go beyond plain request-and-response, and they are easy to confuse. They solve three different problems:
| 机制 | 解决什么 | 关键形状 | 什么时候用 |
|---|---|---|---|
| Tasks 扩展 |
操作时间太长,不能占着连接等 | 返回 resultType: "task" + taskId,之后走 tasks/get |
CI、批处理、审批、外部 job 系统、弱网客户端 |
| MRTR 核心规范 |
工具执行中途缺一个输入,但整体很快 | 返回 resultType: "input_required",客户端带着答案重试原调用 |
删数据前确认、创建付费资源前确认、补一个缺失参数 |
| subscriptions/listen 核心规范 |
客户端不想一直主动拉 | 单一订阅流,按通知类型 opt-in | 已有长任务想省掉轮询开销;资源变更通知 |
| Mechanism | What it solves | Key shape | When to use it |
|---|---|---|---|
| Tasks extension |
The operation takes too long to hold a connection for | Returns resultType: "task" + a taskId, then tasks/get |
CI, batch jobs, approvals, external job systems, flaky clients |
| MRTR core spec |
A tool is missing one input mid-call but is otherwise fast | Returns resultType: "input_required"; the client retries the original call with the answers |
Confirm before deleting data, confirm before creating a billable resource, supply a missing parameter |
| subscriptions/listen core spec |
The client would rather not keep pulling | A single subscription stream, opted into per notification type | An existing long task where you want to drop the polling overhead; resource change notifications |
input_required + tasks/update 承接input_required + tasks/update carries the interaction这里有个容易混淆的细节值得单独点出:input_required 这个词在两处出现,含义相近但机制不同。在 MRTR 里它是 resultType,客户端的动作是带着 inputResponses 重试原始调用;在 Tasks 里它是 status,客户端的动作是调 tasks/update 回填,原始调用不重发。判断依据很简单:你手上有没有 taskId。
One detail is worth calling out because it trips people up: the phrase input_required appears in two places with related meanings but different mechanics. Under MRTR it is a resultType, and the client's move is to retry the original call with inputResponses attached. Under Tasks it is a status, and the client's move is to call tasks/update — the original call is not re-sent. The test is simple: do you hold a taskId?
来源:2026-07-28 规范:MRTR / Tasks · subscriptions 模式
Sources: The 2026-07-28 Specification: MRTR / Tasks · Subscriptions pattern
官方文档给了五类场景。它们的共同点不是「慢」,而是「结果的到达时间与请求的发出时间解耦」:
The official docs list five scenarios. What they share is not slowness but the decoupling of when a result arrives from when the request was made:
CI 流水线、批量数据处理、模型训练任务——分钟级到小时级。
CI pipelines, batch data processing, or model training jobs that take minutes or hours.
审批门、复核步骤,或任何需要暂停等用户确认的操作——任务转入 input_required,客户端把请求呈现出来。
Approval gates, review steps, or any operation that pauses for user confirmation. The task moves to input_required and the client presents the request.
如果你的 server 包着一个本来就用 job ID 的 API(云部署、异步 API、队列),那就在创建 job 时返回 task,job 完成时解析它。这是改造成本最低的一类。
If your server wraps an API that already uses job IDs (cloud deployments, async APIs, queued work), return a task when you create the job and resolve it when the job completes. This is the cheapest kind to retrofit.
移动端、断续网络,或任何连接会掉的环境——task ID 能挺过断线。
Mobile clients, intermittent networks, or environments where connections drop — task IDs survive disconnects.
处理大量条目的操作(批量导入、批量更新),部分进度本身就有意义,状态消息可以汇报进度。
Operations that process many items (bulk imports, mass updates) where partial progress is meaningful. Status messages report progress.
反过来,不该用 Tasks 的情形也很清楚:如果操作在一两秒内稳定返回,直接同步返回就好——多引入的持久化、轮询和状态管理都是净成本。Tasks 是给「不得不异步」的场景准备的,不是默认选项。
Conversely, when not to use Tasks is equally clear: if the operation reliably returns within a second or two, just return synchronously. The extra persistence, polling, and state management are pure overhead. Tasks exist for work that has no choice but to be asynchronous; they are not a default.
来源:MCP Tasks · When to use Tasks
Source: MCP Tasks · When to use Tasks
规范要求任务在响应发出之前就已持久化创建。反过来做的话,会出现一个致命窗口:客户端拿到 taskId 立刻发 tasks/get,而服务端还没写库,返回「任务不存在」。分布式部署下这个窗口尤其明显。
The spec requires the task to be durably created before the response is sent. Do it the other way around and you open a fatal window: the client receives the taskId, immediately issues tasks/get, and the server — which has not written to storage yet — answers "no such task". The window is especially wide in a distributed deployment.
服务端必须先检查客户端在本次请求的能力里带了 io.modelcontextprotocol/tasks。老客户端拿到 resultType: "task" 只会当成一个看不懂的结果,把 taskId 当答案塞进模型上下文——比直接报错更难排查。
The server must first verify that the client included io.modelcontextprotocol/tasks in this request's capabilities. An older client that receives resultType: "task" will simply treat it as an unintelligible result and feed the taskId to the model as if it were the answer — far harder to debug than a clean error.
pollIntervalMs
pollIntervalMs 是服务端建议的轮询间隔,不是装饰。用固定 100ms 死循环轮询,一个跑两小时的任务能给服务端打出 7 万次请求。反过来间隔过长又让「完成」延迟暴露给用户。照服务端说的做。
pollIntervalMs
pollIntervalMs is the server's suggested interval, not decoration. A hard loop at a fixed 100 ms will send roughly 70,000 requests to the server over a two-hour task. Poll too slowly, on the other hand, and completion reaches the user late. Do what the server asks.
Tasks 的核心卖点就是崩溃可恢复,而这个卖点完全依赖客户端把 task ID 落盘。存在进程内存里,进程一挂,任务变成孤儿——服务端还在烧钱跑,没人来收结果。
Crash resilience is the headline benefit of Tasks, and it depends entirely on the client persisting task IDs. Keep them in process memory and a crash orphans the task — the server keeps burning money on work nobody will ever collect.
CreateTaskResult 里带 ttlMs。任务不是永久的,过期后服务端可以清理。恢复轮询前先看看句柄是不是已经过了 TTL,别对着一个早被 GC 的 ID 重试到天荒地老。
CreateTaskResult carries a ttlMs. Tasks are not permanent; a server may reclaim them after expiry. Check whether a handle is past its TTL before resuming polling, instead of retrying forever against an ID that was garbage-collected long ago.
见第 03 节:tasks/cancel 只是表达意图。UI 上写「已取消」而不等终态确认,会和最终落到 completed 的现实打架。正确做法是显示「取消中」,以终态为准。
See section 03: tasks/cancel only expresses intent. Rendering "cancelled" in the UI without waiting for a terminal state will contradict reality when the task lands on completed. Show "cancelling" and let the terminal state decide.
关于本节的说明:坑一、二、五、六直接对应官方文档中的规范性要求;坑三、四是从文档明确写出的「尊重 pollIntervalMs」「持久化 task ID」两条实现要求推演出的常见后果,属于工程推断而非规范原文。
A note on this section: pitfalls 1, 2, 5, and 6 map directly to normative requirements in the official docs. Pitfalls 3 and 4 are engineering inferences drawn from two implementation requirements the docs state explicitly — "respect pollIntervalMs" and "persist task IDs" — rather than verbatim spec text.
2026 年 8 月 22 日,MCP 核心维护者 David Soria Parra 与 Den Delimarsky 发布了新版路线图,分为五个优先领域,「智能体消息原语」排在第一位。原文对这个方向的定位是:现代智能体负载已经不再适配标准的请求-响应模式——循环会跑更久、服务端会推流式结果、还需要在执行中途调整方向。
On 22 August 2026, MCP lead maintainers David Soria Parra and Den Delimarsky published a new roadmap organized into five priority areas, with agentic messaging primitives listed first. The stated framing: modern agentic workloads no longer fit the standard request-and-response pattern — loops run longer, servers push streamed results, and there is a clear need to steer work mid-flight.
这个方向下有三件具体的事,Tasks 占了其中一件:
Three concrete pieces of work sit under that area, and Tasks is one of them:
路线图明确写了:落在这五个优先领域里的 SEP 获得加速评审、通过概率最高;领域之外的提案不会被自动拒绝,但维护者的评审时间稀缺,会优先给这些方向。所以如果你正在做 MCP server 的异步能力,现在按 Tasks 扩展实现,是与规范演进方向一致的押注,而不是在赌一个可能被废弃的实验特性。
The roadmap says it plainly: SEPs that fall within these five priority areas get expedited review and have the best chance of acceptance. Proposals outside them are not rejected automatically, but maintainer review time is scarce and goes to these areas first. So if you are building asynchronous capability into an MCP server, implementing against the Tasks extension today is a bet aligned with where the spec is going, not a gamble on an experimental feature that might be dropped.
值得留意的一个张力:Tasks 目前是轮询优先的设计(通知是可选优化),而路线图第一条就是要做服务端主动推送。两者不冲突——通知机制早已经通过 subscriptions/listen 存在,webhooks/channels 是把「推」的能力扩展到「客户端不在线」的场景。但如果你现在就把架构死绑在轮询上,未来接推送时要动的地方会比预期多。建议把「怎么拿到状态」抽象成一层,轮询只是它的第一个实现。
One tension worth watching: Tasks today is poll-first by design, with notifications as an optional optimization, while the first item on the roadmap is server-initiated push. These do not conflict — a notification path already exists through subscriptions/listen, and webhooks/channels extend push to the case where the client is offline. But if you hard-wire your architecture to polling now, wiring in push later will touch more code than you expect. Abstract "how status arrives" behind one seam, and treat polling as its first implementation.
来源:The New MCP Roadmap · 2026-08-22 · 官方路线图页
Sources: The New MCP Roadmap · 2026-08-22 · Official roadmap page
本文全部事实性内容来自以下公开来源,均于 2026-08-26 访问。文中凡属工程推断而非规范原文之处,已在对应位置标注。
Every factual claim in this document comes from the public sources below, all accessed on 2026-08-26. Wherever a statement is an engineering inference rather than verbatim spec text, it is flagged in place.
本文为学习整理,不是官方文档。Tasks 目前是扩展而非规范正文,客户端支持情况因宿主而异——落地前请查客户端支持矩阵并以 ext-tasks 仓库中的规范原文为准。规范仍在演进中,本文内容对应 2026-08-26 的状态。
This is a study write-up, not official documentation. Tasks is currently an extension rather than part of the specification proper, and client support varies by host — check the client matrix before shipping, and treat the spec in the ext-tasks repository as authoritative. The specification is still evolving; this document reflects its state as of 2026-08-26.