> ## Documentation Index
> Fetch the complete documentation index at: https://mcp-zh.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SEP-2663：任务扩展（Tasks Extension）

> 定义一个扩展，让服务器能以异步的任务句柄（task handle）而非最终结果来响应 tools/call 请求，客户端通过轮询取回最终结果。

* **状态（Status）**: Final
* **类型（Type）**: Extensions Track
* **创建时间（Created）**: 2026-04-27
* **作者（Author(s)）**: Luca Chang (@LucaButBoring), Caitie McCaffrey (@CaitieM20)；代表 Agents Working Group
* **发起人（Sponsor）**: Caitie McCaffrey (@CaitieM20)
* **扩展标识符（Extension Identifier）**: `io.modelcontextprotocol/tasks`
* **PR**: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)

## 摘要（Abstract）

本 SEP 定义一个扩展，允许服务器以异步的 *任务句柄（task handle）* 而非最终结果来响应 `tools/call` 请求，使客户端可以通过轮询取回最终结果。该扩展引入三个方法：`tasks/get`、`tasks/update` 与 `tasks/cancel`；一个多态结果判别器（`resultType: "task"`）；以及一个携带任务状态、进行中的服务器到客户端请求、最终结果或错误的 `Task` 形态。任务创建由服务器主导：客户端通过在其按请求能力中包含该扩展来表示支持，而服务器按请求逐一决定是否物化一个任务。

任务将成为 MCP 的基础构件，并预计在未来的协议版本中得到支持。`2025-11-25` 规范中的实验性 `tasks` 特性在协议的扩展机制可用之前充当了权宜之计。既然 [扩展](https://modelcontextprotocol.io/extensions/overview) 已被[正式化](./2133-extensions.md)，将任务移至一个官方扩展能让该特性有时间孵化并基于更多真实世界的实现反馈演进，而不受核心规范发布节奏的约束。一旦该扩展稳定并获得广泛采用，即计划将其提升进核心协议。

本提案将 `2025-11-25` 发布中规定的 [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) 版本从核心协议中 *移除*，并移至一个扩展。它还提议基于该发布以来的实现反馈、以及 `2026-06-30` 规范中对基础协议的若干变更，对任务进行更新：

* [SEP-2260：要求服务器请求与客户端请求关联](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md)
* [SEP-2322：多轮往返请求](./2322-MRTR.md)
* [SEP-2243：Streamable HTTP 传输的 HTTP 头部标准化](./2243-http-standardization.md)
* [SEP-2567：经由显式状态句柄的无会话 MCP](./2567-sessionless-mcp.md)
* [SEP-2575：让 MCP 无状态](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)

## 动机（Motivation）

实验性 [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) 特性充当了工具调用、征询与采样的一种替代执行模式，让接收方可以返回一个轮询句柄，而非阻塞直到最终结果就绪。实现经验暴露了若干挑战：

1. **握手脆弱。** 如今任务暴露方法级能力（`tasks.requests.tools.call` 声明 `tools/call` **可以（MAY）** 被任务增强），同时有一个工具级的 `execution.taskSupport` 字段声明某个特定工具是否接受该增强。客户端通过在其请求上传递 `task` 参数来表达自己对任务的支持，但若方法/工具不支持任务则 **必须不（MUST NOT）** 包含它。因此想要选择加入任务的客户端必须先用一次 `tools/list` 调用来给其状态预热，之后才能发出任何任务增强请求，并且不能盲目地给每个请求附加 `task` 参数来同构地处理工具。这令人困惑、隐晦且容易出错。

2. **`tasks/result` 是一个阻塞陷阱。** 在当前流程中，观察到 `input_required` 的客户端被要求过早地调用 `tasks/result`，以便服务器有一条 SSE 流可在其上旁路发送征询或采样请求。`tasks/result` 随后会阻塞直到整个操作完成。这迫使产生许多客户端和服务器并不想实现的长连接，并且它与 [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md) 冲突——后者彻底禁止未经请求的服务器到客户端请求。在 SEP-2260 之下，为阻塞行为辩护的 SSE 语义已不再适用。

3. **`tasks/list` 的作用域无法定义。** 为避免客户端取消或取回它们本不应访问的任务的结果，所有任务都应绑定到某种 "授权上下文"，其实现留给各服务器按其既有的定制权限模型处理。然而在许多情况下无法进行这种绑定，此时任务 ID 成为防止污染的唯一防线。在此情形下，服务器根本就不安全去支持 `tasks/list`。虽然任务原本可以改为绑定到一个会话，但 [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567) 从协议中移除了会话。没有其他服务器可以单方面定义的自然作用域——任务 ID 可以是不可猜测的句柄，服务器可以逐个识别，但服务器无法在没有额外状态的情况下可靠地将两个无关句柄关联到同一调用方。

除实现挑战外，任务还面临另一个结构性问题：**客户端托管的任务不再可表达。** [SEP-1686](./1686-tasks.md) 允许客户端为征询和采样托管任务，部分原因是为了避免将任务耦合到工具调用。[SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md) 使任何未经请求的服务器到客户端请求无效；客户端托管任务下的每个服务器到客户端轮询请求按定义都将是未经请求的。

本提案意在通过重新设计该特性的某些方面并将任务移出为一个官方扩展来解决上述问题。将任务重新定义为官方扩展，让该特性有更多时间独立于核心规范孵化与演进，促进采用。作为重新设计的一部分，本提案将轮询生命周期整合进 `tasks/get` 和一个新的 `tasks/update`，以移除阻塞的 `tasks/result` 方法。该重新设计允许服务器未经请求地返回任务（响应普通的、未打 `task` 标记的请求），从而消除按请求的选择加入和 `tools/list` 预热，转而依赖扩展能力作为单一握手点。最后，本提案为遵循 [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md) 移除了客户端托管的征询和采样任务。

## 规范（Specification）

MCP 任务扩展允许某些请求被 **任务（tasks）** 增强。任务是持久的状态机，携带关于它们所增强请求底层执行状态的信息，旨在用于客户端轮询和延迟的结果取回。每个任务由服务器生成的 **任务 ID（task ID）** 唯一标识。

任务对于表示昂贵计算和批处理请求很有用，并能自然映射到外部作业 API 上。

### 扩展标识符（Extension Identifier）

此扩展标识为：`io.modelcontextprotocol/tasks`。

### 能力协商（Capability Negotiation）

客户端与服务器在各自的 capabilities 对象中声明对任务扩展的支持（使用 [SEP-2575：让 MCP 无状态](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) 的更新形式）：

```jsonc theme={null}
// Client to server, in per-request capabilities
{
  // Other request parameters...
  "params": {
    "_meta": {
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {},
        },
      },
    },
  },
}
```

```jsonc theme={null}
// Server to client, in response to server/discover
{
  "result": {
    // Other response parameters...
    "capabilities": {
      "extensions": {
        "io.modelcontextprotocol/tasks": {},
      },
    },
  },
}
```

当前未定义任何扩展专属设置；空对象即表示支持。

已协商此扩展的服务器 **可以（MAY）** 自行裁量、按请求逐一，在响应任何受支持的请求时返回 `CreateTaskResult` 来代替标准结果（例如 `CallToolResult`）。服务器是唯一决策方；客户端不在请求本身上表达任务偏好。客户端声明扩展能力并不意味着它要求对该请求返回 `CreateTaskResult`。

服务器 **必须不（MUST NOT）** 向在其请求上未包含扩展能力的客户端返回 `CreateTaskResult`，无论此前有何声明。已协商此扩展的客户端 **必须（MUST）** 准备好处理对其发出的任何受支持请求返回的 `CallToolResult` 或 `CreateTaskResult`。客户端若收到对不受支持请求类型返回的 `CreateTaskResult`，**必须（MUST）** 将其解释为对该请求的无效响应。

如果服务器无法在不返回 `CreateTaskResult` 的情况下为未声明此扩展能力的客户端提供服务，服务器 **必须（MUST）** 返回错误码 `-32021`（Missing Required Client Capability）的错误，并在错误响应中指明所需扩展：

```jsonl theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    // MISSING_REQUIRED_CLIENT_CAPABILITY
    "code": -32021,
    // Message provided for example purposes only. The content of this example message is non-normative.
    "message": "Missing required client capability",
    "data": {
      "requiredCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}
```

### 受支持的方法（Supported Methods）

以下方法当前支持任务增强执行：

* `tools/call`

本规范未来可能扩展以支持对其他请求类型使用任务；实现 **应当（SHOULD）** 被设计为能在本规范未来的修订中容纳更多请求类型。

### 多态结果（Polymorphic Results）

有资格接受任务增强的请求可能返回两种不同的结果形态之一——请求的标准结果，或一个 `CreateTaskResult`。判别器是结果对象上的 `resultType` 字段，由 [SEP-2322](./2322-MRTR.md) 引入：

```typescript theme={null}
// "task" is introduced by this extension.
type ResultType = "complete" | "input_required" | "task" | string;
```

服务器在返回 `CreateTaskResult` 时 **必须（MUST）** 将 `resultType` 设为 `"task"`，以便客户端能将其与标准结果区分开。服务器 **必须不（MUST NOT）** 在 `CreateTaskResult` 以外的结果类型上将 `resultType` 设为 `"task"`。

提醒客户端实现者：返回固定形态的既有代码（例如返回 `CallToolResult` 的 `tools/call` 方法）无需改变其公共契约——它们可以在内部透明地驱动轮询流程，只对外呈现最终的、已完成的结果。新的实现面 **可以（MAY）** 直接暴露任务生命周期，供有能力利用它的应用使用。

### 任务（Tasks）

`Task` 携带关于进行中工作的操作性元数据。

```typescript theme={null}
interface Task {
  /** Stable identifier for this task. */
  taskId: string;

  /** Current task status. */
  status: "working" | "input_required" | "completed" | "cancelled" | "failed";

  /**
   * Optional message describing the current task state.
   * This can provide context for any status, for example (non-normative):
   * - Progress descriptions for "working"
   * - Work blocked on "input_required"
   * - Reasons for "cancelled" status
   * - Summaries for "completed" status
   * - Additional information for "failed" status (e.g., error details, what went wrong)
   *
   * This MAY be exposed to the end-user or model.
   */
  statusMessage?: string;

  /** ISO 8601 timestamp when the task was created. */
  createdAt: string;

  /** ISO 8601 timestamp when the task was last updated. */
  lastUpdatedAt: string;

  /**
   * Time-to-live duration from creation in integer milliseconds, null for unlimited.
   * The server may discard the task after the TTL elapses. This value MAY change
   * over the lifetime of a task.
   */
  ttlMs: number | null;

  /**
   * Suggested polling interval in integer milliseconds. Clients SHOULD honor
   * this value to avoid overwhelming the server. This value MAY change over
   * the lifetime of a task.
   */
  pollIntervalMs?: number;
}
```

#### 任务状态（Task Status）

任务可以处于以下状态之一：

* `working`：请求当前正在被处理。
* `input_required`：服务器需要来自客户端的输入才能使任务继续。`tasks/get` 响应将在 `inputRequests` 字段中包含未决请求。客户端 **必须（MUST）** 检查此字段，并 **应当（SHOULD）** 在后续的 `tasks/update` 请求中经由 `inputResponses` 字段提供响应。
* `completed`：请求成功完成，结果在 `result` 字段中可用。这包括返回了 `isError: true` 结果的工具调用。
* `failed`：请求因执行期间的一个 JSON-RPC 错误而失败。任务将包含带 JSON-RPC 错误详情的 `error` 字段。此状态 **必须不（MUST NOT）** 用于非 JSON-RPC 错误。
* `cancelled`：请求在完成前被取消。

`Task` 的派生形态内联状态专属的载荷字段，用于 `tasks/get` 响应和 `notifications/tasks` 通知：

```ts theme={null}
/**
 * A task that is in a normal working state.
 * Used by tasks/get and notifications/tasks.
 */
export interface WorkingTask extends Task {
  status: "working";
}

/**
 * A task that is waiting for input from the client.
 * Used by tasks/get and notifications/tasks.
 */
export interface InputRequiredTask extends Task {
  status: "input_required";
  /**
   * Server-to-client requests that need to be fulfilled during task execution.
   * Keys are arbitrary identifiers for matching requests to responses.
   */
  inputRequests: InputRequests;
}

/**
 * A task that has completed successfully.
 * Used by tasks/get and notifications/tasks.
 */
export interface CompletedTask extends Task {
  status: "completed";
  /**
   * The final result of the task.
   * The structure matches the result type of the original request.
   * For example, a CallToolRequest task would return the CallToolResult structure.
   */
  result: JSONObject;
}

/**
 * A task that has failed due to a JSON-RPC error.
 * Used by tasks/get and notifications/tasks.
 */
export interface FailedTask extends Task {
  status: "failed";
  /**
   * The JSON-RPC error that caused the task to fail.
   */
  error: JSONObject;
}

/**
 * A task that has been cancelled.
 * Used by tasks/get and notifications/tasks.
 */
export interface CancelledTask extends Task {
  status: "cancelled";
}

/**
 * A union type representing a task with optional inlined result/error/inputRequests fields.
 * This type is used by tasks/get and notifications/tasks to provide complete task state
 * including terminal results or pending input requests.
 */
export type DetailedTask =
  WorkingTask | InputRequiredTask | CompletedTask | FailedTask | CancelledTask;
```

### 任务创建（Task Creation）

服务器对一个请求返回 `CreateTaskResult` 来代替标准结果形态，以指示该请求将被异步处理。

```typescript theme={null}
// resultType: "task"
type CreateTaskResult = Result & Task;
```

**示例请求（CallToolRequest）：**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "New York"
    }
  }
}
```

**示例响应（CreateTaskResult）：**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "statusMessage": "The operation is now in progress.",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttlMs": 60000,
    "pollIntervalMs": 5000
  }
}
```

内嵌的 `task` 是任务的初始种子状态，通常（但不必然）为 `status: "working"`。客户端在所有后续的 `tasks/get`、`tasks/update` 和 `tasks/cancel` 调用中使用 `task.taskId`。

服务器 **必须不（MUST NOT）** 在任务被持久创建之前返回 `CreateTaskResult`——即在针对返回的 `taskId` 的 `tasks/get` 能够解析之前。在最终一致性的环境中，服务器 **必须（MUST）** 等待一致性达成后再响应。此要求消除了客户端为任务创建推测性轮询的需要。

将多轮往返请求与任务创建结合使用的服务器实现（例如一个在创建任务前需要经由 `InputRequiredResult` 进行征询的工具）**应当（SHOULD）** 在以 `CreateTaskResult` 响应之前 *同步地* 解决所有 MRTR 交换。

### 任务轮询（Task Polling）

客户端通过发送 `tasks/get` 请求来轮询任务完成。

客户端在确定轮询频率时 **应当（SHOULD）** 尊重响应中提供的 `pollIntervalMs`。`pollIntervalMs` **可以（MAY）** 在任务生命周期内改变。服务器 **可以（MAY）** 对轮询频率高于所记录 `pollIntervalMs` 的客户端进行限流。

客户端 **应当（SHOULD）** 持续轮询，直到任务达到终态或直到调用 `tasks/cancel`。客户端 **应当（SHOULD）** 将任务 ID 持久化到持久存储，以便在崩溃或重启后恢复轮询。

#### 请求

```typescript theme={null}
interface GetTaskRequest extends JSONRPCRequest {
  method: "tasks/get";
  params: {
    /** Identifier of the task to query. */
    taskId: string;
  };
}
```

#### 响应

收到 `tasks/get` 请求后，服务器 **必须（MUST）** 检查任务状态并相应响应：

1. 若状态为 `working`，服务器 **必须（MUST）** 返回一个 status 为 `working` 的 `Task` 对象。
2. 若状态为 `input_required`，服务器 **必须（MUST）** 返回一个 status 为 `input_required` 且带有 [多轮往返请求](./2322-MRTR.md) 中定义的 `inputRequests` 字段的 `Task` 对象。`inputRequests` 字段 **必须（MUST）** 包含服务器到客户端的所有、需要在任务继续前被满足的未决请求。
3. 若状态为 `completed`，服务器 **必须（MUST）** 返回一个 status 为 `completed` 且带有含任务最终结果的 `result` 字段的 `Task` 对象。
4. 若状态为 `cancelled`，服务器 **必须（MUST）** 返回一个 status 为 `cancelled` 的 `Task` 对象。
5. 若状态为 `failed`，服务器 **必须（MUST）** 返回一个 status 为 `failed` 且带有执行期间发生错误的 `Task` 对象。

```typescript theme={null}
type GetTaskResult = Result & DetailedTask;
```

响应携带与任务当前状态对应的恰当响应变体（见 [任务状态](#任务状态task-status)）。此对象上的 `resultType` 字段 **必须（MUST）** 设为 `"complete"`，因为它是 `tasks/get` 请求的标准结果形态。

若任务具有非 null 的 `ttlMs`，客户端 **可以（MAY）** 将 TTL 视为兜底：若在 `createdAt` 加 `ttlMs` 过去后任务的可观测状态仍未反映更新，客户端 **可以（MAY）** 认为该任务不再可用。反之，服务器 **可以（MAY）** 在 TTL 过去后的任意时刻将任务标记为 `failed`，并随后在任意时刻删除它。`ttlMs` 的值 **可以（MAY）** 在任务生命周期内改变。

### 任务更新请求（Task Update Requests）

当任务需要来自客户端的输入（由 `input_required` 状态指示）时，服务器在 `tasks/get` 响应的 `inputRequests` 字段中包含未决请求（见 [多轮往返请求](./2322-MRTR.md)）。客户端在一个或多个后续的 `tasks/update` 请求中经由 `inputResponses` 字段提供响应。

当客户端观察到带 `status: "input_required"` 的 `tasks/get` 响应（或 `notifications/tasks` 通知）时，客户端 **应当（SHOULD）** 通过发送一个或多个带相应 `inputResponses` 的 `tasks/update` 请求来满足 `inputRequests` 中的未决请求。发送 `tasks/update` 后，客户端 **应当（SHOULD）** 继续经由轮询（`tasks/get`）或通知（`notifications/tasks`）观察任务状态，直到它达到终态。

客户端 **必须（MUST）** 将 `inputRequests` 中的每个条目视同等价的独立服务器到客户端请求——例如，经由 `inputRequests` 呈现的征询请求与直接的 `elicitation/create` 请求受同样的信任模型与面向用户行为约束。客户端 **应当（SHOULD）** 在连续轮询间对 `inputRequests` 键去重，以避免向用户或模型多次呈现同一请求。

`inputRequests` 中的每个请求键在单个任务的生命周期内 **必须（MUST）** 唯一。服务器 **必须不（MUST NOT）** 在某个键的响应已被投递后，为后续的服务器到客户端请求重用该键，并 **必须不（MUST NOT）** 在一个任务的生命周期内用同一键指代两个不同的请求。这保证了以同一标识符为键的 `inputResponses` 总是指向客户端所期待的请求，消除了客户端跨轮询去重的歧义，并让服务器可以忽略针对未知或已满足请求的 `inputResponses`。

#### 请求

```typescript theme={null}
interface UpdateTaskRequest extends JSONRPCRequest {
  method: "tasks/update";
  params: {
    /** Identifier of the task to update. */
    taskId: string;

    /**
     * Responses to outstanding inputRequests previously surfaced by the
     * server. Shape per MRTR. Each key MUST correspond to a currently-
     * outstanding inputRequest key.
     */
    inputResponses: InputResponses;
  };
}
```

#### 响应

```typescript theme={null}
type UpdateTaskResult = Result; // empty acknowledgement
```

成功时，服务器 **必须（MUST）** 以空结果确认该请求。该确认是 *最终一致的*：服务器 **可以（MAY）** 接受响应并在任务的可观测状态（经由 `tasks/get` 或 `notifications/tasks`）反映它们之前返回确认。若 `taskId` 不对应已知任务，服务器 **应当（SHOULD）** 返回 JSON-RPC 错误。客户端 **应当（SHOULD）** 跟踪 `inputRequests` 键以避免对请求响应多于一次。

服务器 **应当（SHOULD）** 忽略任何映射到当前对该任务非未决的键的 `inputResponses` 响应——包括从未发出的键、已被回答的键，以及其对应请求已被取代的键。服务器 **可以（MAY）** 接受一组部分响应（当前未决键的一个严格子集）；

`UpdateTaskResult` 上的 `resultType` 字段 **必须（MUST）** 设为 `"complete"`，因为它是 `tasks/update` 请求的标准结果形态。

### 任务取消（Task Cancellation）

客户端发送 `tasks/cancel` 请求来表示其取消进行中任务的意图。`notifications/cancelled` 通知 **必须不（MUST NOT）** 用于任务取消。

#### 请求

```typescript theme={null}
interface CancelTaskRequest extends JSONRPCRequest {
  method: "tasks/cancel";
  params: {
    taskId: string;
  };
}
```

#### 响应

```typescript theme={null}
type CancelTaskResult = Result; // empty acknowledgement
```

服务器 **必须（MUST）** 以空结果确认该请求。若 `taskId` 不对应已知任务，服务器 **应当（SHOULD）** 返回 JSON-RPC 错误。取消处理是 *最终一致的*——任务的可观测状态 **可以（MAY）** 在确认后仍为 `working`（或某个其他非终态），并 **可以（MAY）** 在工作于取消生效之前已完成时最终达到 `cancelled` 以外的终态。

取消是 **协作式的（cooperative）**：请求表示意图，而服务器决定是否以及何时予以尊重。服务器没有义务真正停止工作；它只有义务确认该请求。并不保证最终会转换到 `cancelled`。

客户端 **可以（MAY）** 在发送取消后立即删除与该任务关联的所有状态（例如，它不再需要保留已响应过的 `inputRequests` 键列表）。客户端无需再次轮询 `tasks/get` 来等待任务达到 `cancelled` 状态。

`CancelTaskResult` 上的 `resultType` 字段 **必须（MUST）** 设为 `"complete"`，因为它是 `tasks/cancel` 请求的标准结果形态。

### 任务状态通知（Task Status Notifications）

除服务于客户端轮询外，服务器 **可以（MAY）** 经由 `notifications/tasks` 通知推送状态更新：

```typescript theme={null}
export type TaskStatusNotificationParams = NotificationParams & Task;

export interface TaskStatusNotification extends JSONRPCNotification {
  method: "notifications/tasks";
  params: TaskStatusNotificationParams;
}
```

为开始监听任务状态通知，客户端向服务器发送一个 `subscriptions/listen` 请求，包含客户端感兴趣的任务 ID 列表（见 [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)）：

```typescript theme={null}
export interface SubscriptionsListenRequest extends Request {
  method: "subscriptions/listen";
  params: {
    // Other existing fields...
    notifications: {
      taskIds?: string[];
      // Other existing fields...
    };
  };
}
```

在其确认通知中，服务器包含它已同意为之发送任务状态通知的任务 ID 列表（如果有的话）：

```typescript theme={null}
export interface SubscriptionsAcknowledgedNotification extends Notification {
  method: "notifications/subscriptions/acknowledged";
  params: {
    notifications: {
      /**
       * Subscribe to notifications/tasks for specific task IDs.
       */
      taskIds?: string[];
      // Other existing fields...
    };
  };
}
```

若客户端请求任务状态通知但未声明 `io.modelcontextprotocol/tasks` 扩展能力，服务器 **必须（MUST）** 返回指明缺失能力的 JSON-RPC 错误：

```jsonl theme={null}
{
  "jsonrpc": "2.0",
  "id": 12,
  "error": {
    // MISSING_REQUIRED_CLIENT_CAPABILITY
    "code": -32021,
    // Message provided for example purposes only. The content of this example message is non-normative.
    "message": "Missing required client capability",
    "data": {
      "requiredCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}
```

每条通知携带当前状态的完整 `DetailedTask`，与 `tasks/get` 在那一刻会返回的内容相同。

**通知：**

```json theme={null}
{
  "jsonrpc": "2.0",
  "method": "notifications/tasks",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "completed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 60000,
    "pollIntervalMs": 5000,
    "result": {
      "content": [
        {
          "type": "text",
          "text": "Operation completed successfully."
        }
      ],
      "isError": false
    }
  }
}
```

该通知包含完整的任务对象，允许客户端无需轮询 `tasks/get` 方法即可访问完整的任务状态与最终结果。客户端 **可以（MAY）** 在订阅任务状态通知之外继续轮询 `tasks/get`，但无需这么做。

`notifications/progress` 与 `notifications/message` 通知 **必须不（MUST NOT）** 在某任务的 `subscriptions/listen` 流上发送，在本规范中总体上也不在任务上受支持。

### Streamable HTTP：路由头部

当 `tasks/get`、`tasks/update` 或 `tasks/cancel` 经由 Streamable HTTP 传输发送时，客户端 **必须（MUST）** 将 `Mcp-Name` 头部（由 [SEP-2243](./2243-http-standardization.md) 定义）设为 `params.taskId` 的值。这允许传输中间件和负载均衡器将同一任务的后续请求路由到持有其状态的服务器实例，而这通常是正确性所必需的。`Mcp-Method` 头部按 [SEP-2243](./2243-http-standardization.md) 设为 JSON-RPC 方法名。

### 示例消息流程

设想一个简单的工具调用 `hello_world`，它需要一次征询让用户提供姓名。该工具本身不接受参数。

为调用此工具，客户端发起一个 `CallToolRequest` 如下：

```jsonc theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "hello_world",
    "arguments": {},
    "_meta": {
      // Other metadata...
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {},
        },
      },
    },
  },
}
```

服务器（经由定制逻辑）判定它想要创建一个任务来表示这项工作，于是立即返回一个 `CreateTaskResult`：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000
  }
}
```

客户端收到 `CreateTaskResult` 后，即开始轮询 `tasks/get`：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
```

在任务处于 `"working"` 状态期间的每个请求上，服务器返回一个常规任务响应：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resultType": "complete",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000
  }
}
```

最终，服务器到达需要向用户发送征询的点。它将任务状态设为 `"input_required"` 以发出此信号。在客户端的下一个 `tasks/get` 请求上，服务器经由 `inputRequests` 字段发送征询载荷。请注意，虽然任务 `inputRequests` 与 [SEP-2322](./2322-MRTR.md) 多轮往返请求有结构上的相似性，但它们是一个不同的机制：任务 `inputRequests` 经由 `tasks/get` 呈现、经由 `tasks/update` 满足，而非经由原始方法的重试。需要在返回 `CreateTaskResult` *之前* 获取客户端输入（例如为决定是否继续）的服务器，在原始请求上使用多轮往返请求流程；需要在任务执行 *期间* 获取客户端输入的服务器，使用此处描述的 `inputRequests`/`inputResponses` 机制。

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
```

```json theme={null}
{
  "id": 4,
  "jsonrpc": "2.0",
  "result": {
    "resultType": "complete",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "input_required",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000,
    "inputRequests": {
      "name": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Please enter your name.",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "name": { "type": "string" }
            },
            "required": ["name"]
          }
        }
      }
    }
  }
}
```

为求周全，我们考虑一种情形：客户端恰好在用户满足征询请求 *之前* 再次轮询 `tasks/get`。由于 `inputRequests` 实际上是任务关联的所有未决服务器到客户端请求的时间点快照，服务器会再次包含同一请求，尽管客户端已经见过这一信息（建议客户端出于 UX 目的对同一键的 `inputRequests` 去重）：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
```

```json theme={null}
{
  "id": 5,
  "jsonrpc": "2.0",
  "result": {
    "resultType": "complete",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "input_required",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000,
    "inputRequests": {
      "name": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Please enter your name.",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "name": { "type": "string" }
            },
            "required": ["name"]
          }
        }
      }
    }
  }
}
```

用户输入其姓名，客户端携带所满足的信息发起一个 `tasks/update` 请求：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tasks/update",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "inputResponses": {
      "name": {
        "action": "accept",
        "content": {
          "input": "Luca"
        }
      }
    }
  }
}
```

服务器确认该请求：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "resultType": "complete"
  }
}
```

异步地，服务器处理它并将任务移回 `working` 状态：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
```

```json theme={null}
{
  "id": 7,
  "jsonrpc": "2.0",
  "result": {
    "resultType": "complete",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000
  }
}
```

最终，服务器完成请求，于是它存储最终的 `CallToolResult` 并将任务移入 `"completed"` 状态。在下一个 `tasks/get` 请求上，服务器发送内联进任务对象中的最终工具结果：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
```

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "resultType": "complete",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "completed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttlMs": 3600000,
    "pollIntervalMs": 5000,
    "result": {
      "content": [
        {
          "type": "text",
          "text": "Hello, Luca!"
        }
      ],
      "isError": false
    }
  }
}
```

### 错误处理（Error Handling）

任务使用两种错误报告机制：

1. **协议错误（Protocol Errors）**：用于协议级问题的标准 JSON-RPC 错误
2. **任务执行错误（Task Execution Errors）**：底层请求执行中的错误，通过任务状态报告

#### 协议错误

服务器 **必须（MUST）** 为以下协议错误情形返回标准 JSON-RPC 错误：

* 无效或不存在的 `taskId`：`-32602`（Invalid params）
  * 服务器 **必须（MUST）** 为 `tasks/get` 返回此错误。
  * 服务器 **应当（SHOULD）** 为 `tasks/update` 和 `tasks/cancel` 返回此错误。
* 内部错误：`-32603`（Internal error）
* 缺少必需的客户端能力：`-32021`（Missing Required Client Capability）
  * 服务器 **必须（MUST）** 为在 `subscriptions/listen` 上请求任务通知的未声明客户端返回此错误。
  * 服务器 **必须（MUST）** 为发出 `tasks/get`、`tasks/update` 和 `tasks/cancel` 请求的未声明客户端返回此错误。

服务器 **应当（SHOULD）** 提供信息丰富的错误消息来描述错误原因。

**示例：任务未找到**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 70,
  "error": {
    "code": -32602,
    "message": "Failed to retrieve task: Task not found"
  }
}
```

**示例：任务已过期**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 71,
  "error": {
    "code": -32602,
    "message": "Failed to retrieve task: Task has expired"
  }
}
```

服务器无需无限期保留任务。若服务器已清除一个过期任务，它返回一个声明找不到该任务的错误是符合规范的行为。

#### 任务执行错误

当底层请求在执行期间遇到 JSON-RPC 协议错误时，任务移入 `failed` 状态。`tasks/get` 响应 **应当（SHOULD）** 包含带关于失败的诊断信息的 `statusMessage` 字段，并 **必须（MUST）** 包含带 JSON-RPC 错误的 `error` 字段。

`failed` 状态 **必须不（MUST NOT）** 用于表示非 JSON-RPC 错误，例如以 `isError: true` 完成的工具结果。协议方法结果上下文内的错误 **必须（MUST）** 使用 `completed` 状态，并在 `result` 字段中附带错误详情。这在协议级故障（使用 `failed` 状态）与其他故障之间保持了强分离。

**示例：带 JSON-RPC 执行错误的任务**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
    "status": "failed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttlMs": 3600000,
    "statusMessage": "Tool execution failed: API rate limit exceeded",
    "error": {
      "code": -32603,
      "message": "API rate limit exceeded"
    }
  }
}
```

**示例：以工具错误（isError: true）完成的工具调用**

对于在协议级成功完成但返回工具级错误（由工具结果中的 `isError: true` 指示）的工具调用，任务达到 `completed` 状态，工具结果在 `result` 字段中：

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
    "status": "completed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttlMs": 3600000,
    "result": {
      "content": [
        {
          "type": "text",
          "text": "Failed to process request: invalid input"
        }
      ],
      "isError": true
    }
  }
}
```

`tasks/get` 端点恰好返回底层请求本会返回的内容：

* 若底层请求导致了 JSON-RPC 错误，任务使用 `failed` 状态，且 `error` 字段 **必须（MUST）** 包含该 JSON-RPC 错误。
* 若请求以结果完成（即使工具结果为 `isError: true`），任务使用 `completed` 状态，且 `result` 字段 **必须（MUST）** 包含该结果。

### 保留项（Reservations）

* `tasks/` 方法前缀和 `notifications/tasks/` 通知前缀为本扩展保留。
* `resultType` 的结果判别值 `"task"` 为本扩展保留。
* 标签 `io.modelcontextprotocol/tasks` 为本扩展保留。

## 理由（Rationale）

### 未经请求的任务 vs. 即时结果

一份[替代提案](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1905)本会单独处理即时结果情形，且前提条件略有不同：*若* 支持任务，*且* 客户端支持即时任务结果，*则* 服务器可以对一个任务增强请求返回一个常规结果。那一版的即时结果在当时看来是更好的选择，因为它意味着在初始任务规范之上没有破坏性变更。

然而，随着我们着眼于[摆脱](https://blog.modelcontextprotocol.io/posts/2025-12-19-mcp-transport-future/)有状态的协议交互，并鉴于任务总体上当前的实验性状态，此时提出一个稍微更激进、能降低整体规范复杂度并使任务对 MCP 更 "原生" 的变更似乎是值得的。特别地，允许未经请求的任务（在即时结果 *之外*）这一选择，意味着将任务提升为一个面向所有持久操作的一等概念，而非一个并行且略显特化的概念。

这恰好与所提议的 [SEP-2322](./2322-MRTR.md) 对齐，但两者彼此并不耦合。

### 分离读（`tasks/get`）与写（`tasks/update`）

此次重新设计的早期草案让 `tasks/get` 携带 `inputResponses`，以便单次往返既提交响应又观察结果状态。这种混淆有代价：它使读路径非幂等（重试的 `tasks/get` 可能重复提交响应），它迫使读路径共享写的最终一致性模型，并且它使想要缓存或去重读操作的中间件复杂化。分离方法使 `tasks/get` 成为一个纯粹、幂等的读，任何层都能安全地缓存或重放，并将写语义——包括其最终一致性窗口——限定在 `tasks/update`。

`tasks/update` 的仅确认响应形态源自同一分离：没有服务器需要返回、而客户端无法从后续 `tasks/get` 获取的读数据，且强行将一个内嵌 `Task` 塞进响应会重新引入我们正试图避免的非幂等性。代价是每轮输入多一次往返——仅在任务实际需要客户端请求时才付出。

### 任务创建一致性

引入以下新要求：

> 服务器 **必须不（MUST NOT）** 在任务被持久创建之前返回 `CreateTaskResult`——即在针对返回的 `taskId` 的 `tasks/get` 能够解析之前。在最终一致性的环境中，服务器 **必须（MUST）** 等待一致性达成后再响应。此要求消除了客户端为任务创建推测性轮询的需要。

与 `tasks/update` 和 `tasks/cancel` 不同，任务创建是强一致的。这是必需的，以避免请求方发出推测性 `tasks/get` 请求——否则它们无从知晓某任务是被悄然丢弃还是仅仅尚未被创建。反之，`tasks/update` 和 `tasks/cancel` 中的最终一致性之所以可行，是因为客户端行为并不取决于这些操作的结果（客户端无论如何都可以继续轮询）。虽然一致的任务创建确实会在本来不这么行事的分布式系统中增加时延成本，但显式引入此要求简化了客户端实现，并消除了一个未定义行为的来源。

这也与长时运行操作 API 总体对齐，后者通常要求一旦某操作被确认，它就必须可经由轮询端点找到。

### 仅确认的取消

在任务的 `2025-11-25` 设计中，`tasks/cancel` 返回一个描述取消尝试后任务状态的任务。那种返回形态意味着一次同步读——服务器必须查询任务状态来填充它——但取消在许多应用中本质上是异步的（一个单独的 worker 决定是否以及何时予以尊重），因此返回的任务对象在许多情况下只会重复下一次 `tasks/get` 会显示的内容。将 `tasks/cancel` 简化为一个确认符合该操作的实际语义：该请求是一个信号，而非一次状态查询。想要知道取消后状态的客户端经由 `tasks/get`、在它们用于所有其他状态观察的同一代码路径上这么做。

确认上的最终一致性与 `tasks/update` 是同样的分离：服务器可以记录取消请求并在 worker 实际转换任务之前响应，同时不允许客户端将该确认解释为强一致。

虽然 `tasks/update` 和 `tasks/cancel` 出于上述原因使用仅确认的响应形态，服务器 **应当（SHOULD）** 仍为明显无效的请求返回错误——例如未知的 `taskId`。仅确认设计是为了在成功路径中避免对任务状态的同步读，而非为了抑制服务器在请求时即可检测到的错误。为无效输入返回错误能给客户端一个更快的出错信号，而非迫使它们经由后续 `tasks/get` 轮询间接发现问题。

### 与多轮往返请求的组合

引入以下新要求：

> 将多轮往返请求与任务创建结合使用的服务器实现（例如一个在创建任务前需要经由 `InputRequiredResult` 进行征询的工具）**应当（SHOULD）** 在以 `CreateTaskResult` 响应之前 *同步地* 解决所有 MRTR 交换。

同时支持 MRTR（[SEP-2322](./2322-MRTR.md)）和本扩展的 `tools/call` 可以按顺序使用它们：先发送一个或多个 `InputRequiredResult` 交换以同步收集输入，随后以一个 `CreateTaskResult` 交接给异步执行。这种组合是 `resultType` 判别器的结果——每个响应独立定型，客户端根据它收到的值切换行为，*无需* 在两种模式之间维护任何状态。禁止这一点将需要强加一个在协议级无机制可强制执行的人为约束，因为客户端事先并不知道服务器将创建一个任务。

这两个流程尽管共享字段名，仍维护各自独立的状态。MRTR 阶段在服务器返回任何非 `"input_required"` 的 `resultType` 时结束，此时其 `inputRequests` 键被消耗。任务阶段以 `CreateTaskResult` 开始，并独立维护 *其自身的* `inputRequests` 键。任务 `inputRequests` 的键唯一性被限定在任务的生命周期内，不延伸到前面 MRTR 阶段的键。客户端无需跨这两个流程去重。

## 向后兼容性（Backward Compatibility）

`2025-11-25` 发布中的实验性任务特性与本扩展 **在线格式上不兼容**。需要与两个面互操作的实现可以在 SDK 层打补丁，方法是并行实现实验性流程与扩展流程，并根据所协商的协议版本和对端声明的客户端能力进行分派。下表总结了每种排列的预期行为：

| 协议版本         | `tasks.*`（遗留）                                                                                                                                                  | `io.modelcontextprotocol/tasks`                                                                                                                                                                                                                                                                                                                                                                                |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `2025-11-25` | 按 `2025-11-25` 规范的遗留实验性任务。客户端经由 `CallToolRequest` 上的 `task` 参数按请求选择加入任务增强；服务器按该规范使用 `tasks/result`、`tasks/get`、`tasks/cancel` 及（在支持处）`tasks/list`。本扩展不适用。      | 本扩展在 `2025-11-25` 协议版本下未定义。服务器 **必须不（MUST NOT）** 将此能力视为在该协议版本下启用任务；请求按客户端根本未声明任何任务能力那样进行。                                                                                                                                                                                                                                                                                                                      |
| `2026-06-30` | 遗留能力不属于本扩展。服务器 **必须（MUST）** 将仅声明遗留能力的客户端就本扩展而言视为未声明。同时支持 `2025-11-25` Tasks 规范与本扩展的服务器 **应当（SHOULD）** 继续允许此类客户端的 `tasks/get` 和 `tasks/cancel` 请求作用于在该流程下创建的任务。 | 典型情形。按本文档规定的完整任务生命周期，与 `2025-11-25` 实验性特性相比有以下线级差异：<ul><li>`tasks/result` 被移除；调用它的客户端 **必须（MUST）** 收到 `-32601`（Method Not Found）。</li><li>`CallToolRequest` 上的 `task` 参数被移除；服务器 **必须（MUST）** 忽略它（将该字段视为未知）而非将其用作选择加入。</li><li>`tasks.requests.*`、`tasks.cancel` 和 `tasks.list` 能力声明不属于本扩展。此前广告过这些的服务器 **必须（MUST）** 迁移到声明 `io.modelcontextprotocol/tasks`，并 **必须不（MUST NOT）** 在任何包含本扩展的协议版本下继续广告遗留能力。</li></ul> |

返回标准 `CallToolResult` 形态——即从不选择创建任务——的服务器在本扩展下仍完全符合规范。已协商该扩展的客户端 **必须（MUST）** 为任何增强请求处理两种结果形态。

## 安全影响（Security Implications）

* **任务 ID 的不可猜测性。** 服务器 **可以（MAY）** 将任务 ID 用作其存储状态的 bearer token。服务器 **必须（MUST）** 以足够的熵生成它们，使第三方无法枚举或猜测它们。
* **授权绑定。** 服务器 **必须（MUST）** 对每个与任务相关的请求执行身份认证与授权检查，以确保客户端有权限访问某任务。
* **跨调用方关联。** 由于没有 `tasks/list`，服务器不会无意中把一个调用方任务的存在泄露给另一个。这相较于 `2025-11-25` 任务规范是一项改进——在后者中，一个作用域欠佳的列表可能暴露无关的任务 ID。
* **输入请求信任模型。** `inputRequests` 携带从服务器经客户端到用户或模型的征询和采样载荷。宿主 **必须（MUST）** 对这些载荷应用与标准征询/采样请求相同的信任模型。任务并非更高信任的通道。

## 参考实现（Reference Implementation）

已在 [mcpkit](https://github.com/panyam/mcpkit/blob/02cfbe0d2cada8167b9043b9130804c8638b0aa5/core/task_v2.go) 中实现（见[使用示例](https://github.com/panyam/mcpkit/tree/02cfbe0d2cada8167b9043b9130804c8638b0aa5/examples/tasks-v2)）。
