跳到主要内容
协议修订: 2025-11-25
任务在 MCP 规范的 2025-11-25 版本中引入,目前被视为实验性功能。任务的设计和行为可能会在未来的协议版本中发生演变。
模型上下文协议 (MCP) 允许请求者(根据通信方向,可以是客户端或服务器)通过任务来增强其请求。任务是持久的状态机,承载着它们所包装请求的基础执行状态信息,旨在用于请求者轮询和延迟结果检索。每个任务都由接收者生成的任务 ID 唯一标识。 任务对于表示耗时的计算和批量处理请求非常有用,并且可以与外部作业 API 无缝集成。

定义

任务将参与方表示为“请求者”或“接收者”,定义如下:
  • 请求者 (Requestor): 增强任务请求的发送者。这可以是客户端或服务器——两者都可以创建任务。
  • 接收者 (Receiver): 增强任务请求的接收者,以及执行任务的实体。这可以是客户端或服务器——两者都可以接收并执行任务。

用户交互模型

任务被设计为由请求者驱动 - 请求者负责用任务增强请求并轮询这些任务的结果;同时,接收者严格控制哪些请求(如果有)支持基于任务的执行,并管理这些任务的生命周期。 这种请求者驱动的方法确保了确定性的响应处理,并支持复杂的模式,如调度并发请求,只有请求者拥有足够的上下文来进行编排。 实现可以自由地通过任何适合其需求的界面模式公开任务——协议本身不强制要求任何特定的用户交互模型。

功能

支持增强任务请求的服务器和客户端必须在初始化期间声明 tasks 能力。tasks 能力按请求类别进行结构化,布尔属性指示哪些特定请求类型支持任务增强。

服务端能力

服务器声明其是否支持任务,如果支持,则声明哪些服务器端请求可以由任务增强。
能力描述
tasks.list服务器支持 tasks/list 操作
tasks.cancel服务器支持 tasks/cancel 操作
tasks.requests.tools.call服务器支持增强任务的 tools/call 请求
{
  "capabilities": {
    "tasks": {
      "list": {},
      "cancel": {},
      "requests": {
        "tools": {
          "call": {}
        }
      }
    }
  }
}

客户端能力

客户端声明其是否支持任务,如果支持,则声明哪些客户端请求可以由任务增强。
能力描述
tasks.list客户端支持 tasks/list 操作
tasks.cancel客户端支持 tasks/cancel 操作
tasks.requests.sampling.createMessage客户端支持增强任务的 sampling/createMessage 请求
tasks.requests.elicitation.create客户端支持增强任务的 elicitation/create 请求
{
  "capabilities": {
    "tasks": {
      "list": {},
      "cancel": {},
      "requests": {
        "sampling": {
          "createMessage": {}
        },
        "elicitation": {
          "create": {}
        }
      }
    }
  }
}

能力协商

在初始化阶段,双方交换其 tasks 能力,以确定哪些操作支持基于任务的执行。只有在接收者已声明相应能力的情况下,请求者应该才使用任务增强请求。 例如,如果服务器的能力包含 tasks.requests.tools.call: {},则客户端可以使用任务增强 tools/call 请求。如果客户端的能力包含 tasks.requests.sampling.createMessage: {},则服务器可以使用任务增强 sampling/createMessage 请求。 如果未定义 capabilities.tasks,对等端不应该尝试在请求期间创建任务。 capabilities.tasks.requests 中的能力集是详尽的。如果某种请求类型不存在,则表示它不支持任务增强。 capabilities.tasks.list 控制该方是否支持 tasks/list 操作。 capabilities.tasks.cancel 控制该方是否支持 tasks/cancel 操作。

工具级协商

出于任务增强的目的,工具调用受到了特别考虑。在 tools/list 的结果中,工具通过 execution.taskSupport 声明对任务的支持,如果存在,其值可以是 "required"(必需)、"optional"(可选)或 "forbidden"(禁止)。 这被解释为在能力之上的精细层,遵循以下规则:
  1. 如果服务器的能力不包含 tasks.requests.tools.call,则无论 execution.taskSupport 的值如何,客户端绝不能尝试在该服务器的工具上使用任务增强。
  2. 如果服务器的能力包含 tasks.requests.tools.call,则客户端考虑 execution.taskSupport 的值,并据此处理:
    1. 如果 execution.taskSupport 不存在或为 "forbidden",客户端绝不能尝试将该工具作为任务调用。如果客户端尝试这样做,服务器应该返回 -32601 (找不到方法) 错误。这是默认行为。
    2. 如果 execution.taskSupport"optional",客户端可以将该工具作为任务调用,也可以作为普通请求调用。
    3. 如果 execution.taskSupport"required",客户端必须将该工具作为任务调用。如果客户端没有尝试这样做,服务器必须返回 -32601 (找不到方法) 错误。

协议消息

创建任务

增强任务的请求遵循一种与普通请求不同的两阶段响应模式:
  • 普通请求:服务器处理请求并直接返回实际的操作结果。
  • 增强任务请求:服务器接受请求并立即返回包含任务数据的 CreateTaskResult。实际的操作结果在任务完成后通过 tasks/result 提供。
要创建任务,请求者发送一个在请求参数中包含 task 字段的请求。请求者可以包含一个 ttl 值,表示自任务创建以来所需的任务存活时间(以毫秒为单位)。 请求:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "New York"
    },
    "task": {
      "ttl": 60000
    }
  }
}
响应
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "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",
      "ttl": 60000,
      "pollInterval": 5000
    }
  }
}
当接收者接受增强任务的请求时,它返回一个包含任务数据的 CreateTaskResult。该响应不包含实际的操作结果。实际结果(例如 tools/call 的工具结果)仅在任务完成后通过 tasks/result 提供。
当任务响应 tools/call 请求而创建时,宿主应用程序可能希望在任务执行时将控制权交还给模型。这允许模型在等待任务完成时继续处理其他请求或执行额外工作。为了支持这种模式,服务器可以在 CreateTaskResult_meta 字段中提供一个可选的 io.modelcontextprotocol/model-immediate-response 键。该键的值应该是一个旨在作为立即工具结果传递给模型的字符串。如果服务器未提供此字段,宿主应用程序可以使用其自身预定义的辅助消息。此指南是非约束性的,是旨在说明特定用例的临时逻辑。此行为可能会在未来的协议版本中作为 CreateTaskResult 的一部分被正式化或修改。

获取任务

在可流式 HTTP (SSE) 传输中,客户端可以随时断开由服务器为响应 tasks/get 请求而打开的 SSE 流。虽然本说明未对 SSE 流的具体用途做出规定,但所有实现必须继续遵守现有的 可流式 HTTP 传输规范
请求者通过发送 tasks/get 请求来轮询任务完成情况。在确定轮询频率时,请求者应该尊重响应中提供的 pollInterval 请求者应该继续轮询,直到任务达到终端状态(completedfailedcancelled),或者遇到 input_required 状态。请注意,调用 tasks/result 并不意味着请求者需要停止轮询——如果请求者没有在主动等待 tasks/result 完成,则应该继续通过 tasks/get 轮询任务状态。 请求:
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tasks/get",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
响应
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "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",
    "ttl": 30000,
    "pollInterval": 5000
  }
}

检索任务结果

在可流式 HTTP (SSE) 传输中,客户端可以随时断开由服务器为响应 tasks/result 请求而打开的 SSE 流。虽然本说明未对 SSE 流的具体用途做出规定,但所有实现必须继续遵守现有的 可流式 HTTP 传输规范
任务完成后,通过 tasks/result 检索操作结果。这与初始的 CreateTaskResult 响应不同,后者仅包含任务数据。结果结构与原始请求类型匹配(例如,对于 tools/callCallToolResult)。 要检索已完成任务的结果,请求者可以发送 tasks/result 请求: 虽然 tasks/result 会阻塞直到任务达到终端状态,但如果请求者没有被主动阻塞以等待结果(例如之前的 tasks/result 请求失败或被取消),则可以并行继续通过 tasks/get 进行轮询。这允许请求者在任务执行期间监控状态变化或显示进度更新,即使在调用 tasks/result 之后也是如此。 请求:
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tasks/result",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
响应
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
      }
    ],
    "isError": false,
    "_meta": {
      "io.modelcontextprotocol/related-task": {
        "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
      }
    }
  }
}

任务状态通知

当任务状态发生变化时,接收者可以发送 notifications/tasks/status 通知以告知请求者该变化。此通知包含完整的任务状态。 通知:
{
  "jsonrpc": "2.0",
  "method": "notifications/tasks/status",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "completed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:50:00Z",
    "ttl": 60000,
    "pollInterval": 5000
  }
}
通知包含完整的 Task 对象,包括更新后的 statusstatusMessage(如果存在)。这允许请求者在不进行额外 tasks/get 请求的情况下访问完整的任务状态。 请求者绝不能依赖接收此通知,因为它是可选的。接收者不被要求发送状态通知,并且可以选择仅针对某些状态转换发送通知。请求者应该继续通过 tasks/get 进行轮询,以确保收到状态更新。

列出任务

要检索任务列表,请求者可以发送 tasks/list 请求。此操作支持分页。 请求:
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tasks/list",
  "params": {
    "cursor": "optional-cursor-value"
  }
}
响应
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "tasks": [
      {
        "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
        "status": "working",
        "createdAt": "2025-11-25T10:30:00Z",
        "lastUpdatedAt": "2025-11-25T10:40:00Z",
        "ttl": 30000,
        "pollInterval": 5000
      },
      {
        "taskId": "abc123-def456-ghi789",
        "status": "completed",
        "createdAt": "2025-11-25T09:15:00Z",
        "lastUpdatedAt": "2025-11-25T10:40:00Z",
        "ttl": 60000
      }
    ],
    "nextCursor": "next-page-cursor"
  }
}

取消任务

要显式取消任务,请求者可以发送 tasks/cancel 请求。 请求:
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tasks/cancel",
  "params": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
响应
{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "cancelled",
    "statusMessage": "The task was cancelled by request.",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttl": 30000,
    "pollInterval": 5000
  }
}

行为要求

这些要求适用于所有支持接收增强任务请求的参与方。

任务支持与处理

  1. 未针对某请求类型声明任务能力的接收者必须正常处理该类型的请求,并忽略存在的任何任务增强元数据。
  2. 声明了任务能力的接收者可以对非增强任务请求返回错误,要求请求者必须使用任务增强。

任务 ID 要求

  1. 任务 ID 必须是字符串值。
  2. 任务 ID 必须由接收者在创建任务时生成。
  3. 任务 ID 在接收者控制的所有任务中必须是唯一的。

任务状态生命周期

  1. 任务在创建时必须working 状态开始。
  2. 接收者必须仅通过以下有效路径转换任务状态:
    1. working:可以移动到 input_requiredcompletedfailedcancelled
    2. input_required:可以移动到 workingcompletedfailedcancelled
    3. 处于 completedfailedcancelled 状态的任务处于终端状态,绝不能转换到任何其他状态。
任务状态转换图

需要输入 (Input Required) 状态

在使用可流式 HTTP (SSE) 传输时,服务器通常在传递响应消息后关闭 SSE 流,这可能会导致用于后续任务消息的流出现歧义。服务器可以通过将消息加入发往客户端的队列,从而与其他响应一起侧道传输任务相关消息。服务器在管理任务轮询和结果检索期间的 SSE 流方面具有灵活性,客户端应该预期消息会在任何 SSE 流上送达,包括 HTTP GET 流。一种可行的方法是在 tasks/result 上维持一个 SSE 流(参见关于 input_required 状态的说明)。在可能的情况下,服务器不应该响应 tasks/get 请求而升级到 SSE 流,因为客户端已表明其希望轮询结果。虽然本说明未对 SSE 流的具体用途做出规定,但所有实现必须继续遵守现有的 可流式 HTTP 传输规范
  1. 当任务接收者有完成任务所必需的消息要发给请求者时,接收者应该将任务移动到 input_required 状态。
  2. 接收者必须在请求中包含 io.modelcontextprotocol/related-task 元数据,以将其与任务关联。
  3. 当请求者遇到 input_required 状态时,它应该抢占式地调用 tasks/result
  4. 当接收者收到所有必需输入时,任务应该转换出 input_required 状态(通常回到 working)。

TTL 与资源管理

  1. 接收者必须在所有任务响应中包含 createdAt ISO 8601 格式的时间戳,以指示任务的创建时间。
  2. 接收者必须在所有任务响应中包含 lastUpdatedAt ISO 8601 格式的时间戳,以指示任务最后一次更新的时间。
  3. 接收者可以覆盖请求的 ttl 时长。
  4. 接收者必须tasks/get 响应中包含实际的 ttl 时长(如果是无限期则为 null)。
  5. 任务的 ttl 生命周期届满后,无论任务状态如何,接收者可以删除该任务及其结果。
  6. 接收者可以tasks/get 响应中包含 pollInterval 值(以毫秒为单位)以建议轮询间隔。如果提供了该值,请求者应该尊重它。

结果检索

  1. 接受增强任务请求的接收者必须返回 CreateTaskResult 作为响应。此结果应该在接受任务后尽快返回。
  2. 当接收者收到针对处于终端状态(completedfailedcancelled)任务的 tasks/result 请求时,它必须返回基础请求的最终结果,无论该结果是成功的还是 JSON-RPC 错误。
  3. 当接收者收到针对处于任何其他非终端状态(workinginput_required)任务的 tasks/result 请求时,它必须阻塞响应,直到任务达到终端状态。
  4. 对于处于终端状态的任务,接收者必须tasks/result 返回基础请求本应返回的内容,无论那是成功的响应还是 JSON-RPC 错误。
  1. 所有与任务相关的请求、通知和响应必须在其 _meta 字段中包含 io.modelcontextprotocol/related-task 键,其值设置为一个包含与关联任务 ID 匹配的 taskId 对象。
    1. 例如,增强任务的工具调用所依赖的引导 (elicitation) 必须与该工具调用的任务共享相同的相关任务 ID。
  2. 对于 tasks/gettasks/resulttasks/cancel 操作,请求中的 taskId 参数必须被用作识别目标任务的唯一事实来源。请求者不应该在这些请求中包含 io.modelcontextprotocol/related-task 元数据,接收者必须忽略此类元数据(如果存在),而优先使用 RPC 方法参数。同样,对于 tasks/gettasks/listtasks/cancel 操作,接收者不应该在结果消息中包含 io.modelcontextprotocol/related-task 元数据,因为 taskId 已经存在于响应结构中。

任务通知

  1. 当任务状态发生变化时,接收者可以发送 notifications/tasks/status 通知。
  2. 请求者绝不能依赖接收 notifications/tasks/status 通知,因为它是可选的。
  3. 发送时,notifications/tasks/status 通知不应该包含 io.modelcontextprotocol/related-task 元数据,因为任务 ID 已经存在于通知参数中。

任务进度通知

增强任务的请求支持在 进度 (progress) 规范中定义的进度通知。初始请求中提供的 progressToken 在整个任务生命周期内保持有效。

任务列表

  1. 接收者应该使用基于游标的分页来限制单个响应中返回的任务数量。
  2. 如果还有更多任务可用,接收者必须在响应中包含 nextCursor
  3. 请求者必须将游标视为不透明令牌,不得尝试解析或修改它们。
  4. 如果一个任务对请求者而言可以通过 tasks/get 检索,那么它必须也能通过 tasks/list 为该请求者检索。

任务取消

  1. 接收者必须拒绝针对已处于终端状态(completedfailedcancelled)任务的取消请求,并返回错误代码 -32602 (无效参数)。
  2. 在收到有效的取消请求后,接收者应该尝试停止任务执行,并且必须在发送响应之前将任务转换到 cancelled 状态。
  3. 任务一旦被取消,即使执行继续完成或失败,它也必须保持在 cancelled 状态。
  4. tasks/cancel 操作未定义删除行为。然而,接收者可以根据其判断随时删除已取消的任务,包括取消后立即删除或在任务 ttl 到期后删除。
  5. 请求者不应该依赖已取消任务会被保留任何特定时长,应在取消前检索任何所需信息。

消息流

基本任务生命周期

带引导的增强任务工具调用

增强任务采样请求

任务取消流程

数据类型

任务

任务表示请求的执行状态。任务状态包括:
  • taskId: 任务的唯一标识符
  • status: 任务执行的当前状态
  • statusMessage: 可选的人类可读消息,描述当前状态(可以存在于任何状态,包括失败任务的错误详情)
  • createdAt: 任务创建时的 ISO 8601 时间戳
  • ttl: 从创建到任务可能被删除的时间(以毫秒为单位)
  • pollInterval: 状态检查之间建议的时间间隔(以毫秒为单位)
  • lastUpdatedAt: 任务状态最后一次更新时的 ISO 8601 时间戳

任务状态

任务可以处于以下状态之一:
  • working: 请求当前正在处理中。
  • input_required: 接收者需要来自请求者的输入。即使任务尚未达到终端状态,请求者也应该调用 tasks/result 来接收输入请求。
  • completed: 请求成功完成,结果可用。
  • failed: 关联的请求未能成功完成。具体对于工具调用,这包括工具调用结果中 isError 设置为 true 的情况。
  • cancelled: 请求在完成前被取消。

任务参数

当使用任务执行增强请求时,task 字段包含在请求参数中:
{
  "task": {
    "ttl": 60000
  }
}
字段
  • ttl (数字, 可选): 请求的从创建起保留任务的时长(以毫秒为单位)
所有与任务关联的请求、响应和通知必须_meta 中包含 io.modelcontextprotocol/related-task 键。
{
  "io.modelcontextprotocol/related-task": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
  }
}
这在整个请求生命周期中将消息与其源任务关联起来。 对于 tasks/gettasks/listtasks/cancel 操作,请求者和接收者不应该在其消息中包含此元数据,因为 taskId 已经存在于消息结构中。tasks/result 操作必须在其响应中包含此元数据,因为结果结构本身不包含任务 ID。

错误处理

任务使用两种错误报告机制:
  1. 协议错误: 针对协议级别问题的标准 JSON-RPC 错误
  2. 任务执行错误: 基础请求执行中的错误,通过任务状态报告

协议错误

对于以下协议错误情况,接收者必须返回标准 JSON-RPC 错误:
  • tasks/gettasks/resulttasks/cancel 中使用了无效或不存在的 taskId: -32602 (无效参数)
  • tasks/list 中使用了无效或不存在的游标: -32602 (无效参数)
  • 尝试取消已处于终端状态的任务: -32602 (无效参数)
  • 内部错误:-32603 (内部错误)
此外,接收者可以返回以下错误:
  • 当接收者要求该请求类型必须使用任务增强时收到了非增强任务请求: -32600 (无效请求)
接收者应该提供详尽的错误消息来描述错误原因。 示例:需要任务增强
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32600,
    "message": "Task augmentation required for tools/call requests"
  }
}
示例:找不到任务
{
  "jsonrpc": "2.0",
  "id": 70,
  "error": {
    "code": -32602,
    "message": "Failed to retrieve task: Task not found"
  }
}
示例:任务已过期
{
  "jsonrpc": "2.0",
  "id": 71,
  "error": {
    "code": -32602,
    "message": "Failed to retrieve task: Task has expired"
  }
}
接收者不被要求无限期保留任务。如果接收者已清除了过期的任务,则返回任务无法找到的错误是符合规范的行为。
示例:任务取消被拒绝(已是终端状态)
{
  "jsonrpc": "2.0",
  "id": 74,
  "error": {
    "code": -32602,
    "message": "Cannot cancel task: already in terminal status 'completed'"
  }
}

任务执行错误

当基础请求未成功完成时,任务进入 failed 状态。这包括请求执行期间的 JSON-RPC 协议错误,或者专门针对工具调用,当工具结果的 isError 设置为 true 时。tasks/get 响应应该包含一个 statusMessage 字段,提供有关失败的诊断信息。 示例:具有执行错误的任务
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
    "status": "failed",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttl": 30000,
    "statusMessage": "Tool execution failed: API rate limit exceeded"
  }
}
对于包装工具调用请求的任务,当工具结果的 isError 设置为 true 时,任务应达到 failed 状态。 tasks/result 端点返回的内容与基础请求本应返回的内容完全一致:
  • 如果基础请求导致 JSON-RPC 错误,则 tasks/result 必须返回相同的 JSON-RPC 错误。
  • 如果请求以 JSON-RPC 响应完成,则 tasks/result 必须返回包含该结果的成功 JSON-RPC 响应。

安全注意事项

任务隔离与访问控制

任务 ID 是访问任务状态和结果的主要机制。如果没有适当的访问控制,任何能够猜测或获取任务 ID 的一方都可能访问敏感信息或操作他们未创建的任务。 当提供了授权上下文时,接收者必须将任务绑定到该上下文。 上下文绑定对所有应用程序并非都可行。某些 MCP 服务器运行在没有授权的环境中(如单用户工具),或使用不支持授权的传输协议。在这些场景中,接收者应该清楚地记录此限制,因为任何能够猜测任务 ID 的请求者都可能访问任务结果。如果上下文绑定不可用,接收者必须生成具有足够熵的加密安全任务 ID 以防止猜测,并应考虑使用更短的 TTL 时长以减少暴露窗口。 如果上下文绑定可用,接收者必须拒绝针对不属于请求者授权上下文的任务的 tasks/gettasks/resulttasks/cancel 请求。对于 tasks/list 请求,接收者必须确保返回的任务列表仅包含与请求者授权上下文关联的任务。 此外,接收者应该对任务操作实施速率限制,以防止拒绝服务和枚举攻击。

资源管理

  1. 接收者应该
    1. 强制限制每个请求者的并发任务数
    2. 强制执行最大 ttl 时长以防止无限期的资源占用
    3. 及时清理过期任务以释放资源
    4. 记录支持的最大 ttl 时长
    5. 记录每个请求者的最大并发任务数
    6. 实施对资源使用的监控和告警

审计与日志

  1. 接收者应该
    1. 记录任务创建、完成和检索事件以用于审计目的
    2. 在日志中包含授权上下文(如果可用)
    3. 监控可疑模式(例如,大量失败的任务查询、过度轮询)
  2. 请求者应该
    1. 记录任务生命周期事件以用于调试和审计目的
    2. 跟踪任务 ID 及其关联的操作