从 v1 迁移

面向熟悉 v1 的实现者的 ACP v2 完整指南:破坏性变更、新功能和分步迁移

ACP v2 是一个整合版本。它重新设计了提示生命周期并允许更灵活的会话状态模式,统一了流式和非流式更新,使 schema 默认向前兼容,并移除了生态系统已经弃用的协议面。

迁移并不意味着放弃 v1。仅支持 v1 的 Agent 和客户端在一段时间内仍将常见,因此实现者应该同时支持两个版本:按连接协商版本,保持 v1 支持正常工作,并在功能标志后面添加 v2 直到它稳定。参阅同时支持 v1 和 v2 了解如何构建此结构。

如果你只记住五件事,请记住这些:

  1. session/prompt 响应不再结束轮次。它确认接受。前台进度和完成以 state_update 通知到达,停止原因也移到了那里。
  2. 更新是 upsert。消息、工具调用和计划按 ID 修补,具有统一的语义:省略字段 = 不变,null = 清除,值 = 替换,分块追加。
  3. 客户端文件系统、终端执行和会话模式 API 已移除。Agent 拥有的终端输出是单独的仅显示 v2 面;当 Agent 需要客户端工具时,使用客户端提供的 MCP 服务器。
  4. 能力已重组。双方各有一个 capabilities + 必需的 info 字段,会话作用域组嵌套在 session 下,对象支持标记代替布尔值,以及必需的基线会话方法。
  5. 现在一切都是可扩展的。枚举和标签联合接受未知值。_ 前缀的值是你的,其余保留给未来的 ACP 版本。

范围:稳定 v2 和草案面

本指南描述稳定 v2 基线(schema/v2/schema.json)。v2 协议面整体仍标记为草案,因此在稳定之前,请将 v2 支持置于显式版本协商和功能标志之后。

不稳定 schema(schema/v2/schema.unstable.json)在该基线之上叠加了可选的草案功能。协商 protocolVersion: 2 意味着它们中的任何一个。与 v1 一样,在每个功能后面加上其自己的能力或功能标志。

版本协商

机制未变:客户端在 initialize 中发送其支持的最新协议版本,Agent 如果支持则以相同版本响应,否则以其自己的最新版本响应。要使用 v2,发送 "protocolVersion": 2。仅支持 v1 的 Agent 将以 "protocolVersion": 1 回答,客户端决定是否继续使用 v1 或断开连接。

将 v2 支持视为增量。添加 v2 时继续为 protocolVersion: 1 对等方提供服务:放弃 v1 的 Agent 会切断与现有客户端的联系,放弃 v1 的客户端会失去对现有 Agent 的访问。每方根据协商的版本按连接选择其 v1 或 v2 面。

v2 的任何内容都不会改变底层 JSON-RPC 帧,因此单个连接在 initialize 之后始终只说一种协商版本。

概览

方法变更

v1 方法v2
initialize名称不变。参数和结果已重构
authenticate重命名为 auth/login
logout(由 agentCapabilities.auth.logout 门控)重命名为 auth/logoutauthMethods 非空时必需;无 logout 能力标记
session/newmcpServers 现在可选。响应不再包含 modes
session/load已移除。使用 session/resume 配合 "replayFrom": { "type": "start" }
session/resume新增可选 replayFrom。必需(无能力标记)
session/list必需。无能力
session/close必需。无能力
session/delete不变。仍通过 session.delete 能力可选
session/prompt形状不变。响应语义已重新设计(参阅提示生命周期
session/cancel名称不变。完成现在通过 state_update 报告而非提示响应
session/set_mode已移除。使用 session/set_config_option
session/set_config_option不变
session/request_permission名称不变。参数已重构(必需 title,可选 subject)
session/update名称不变。更新变体已更改(见下)
fs/read_text_file, fs/write_text_file已移除
terminal/create, terminal/output, terminal/release, terminal/wait_for_exit, terminal/kill已移除
$/cancel_request不变

session/update 变体变更

v1 sessionUpdatev2
user_message_chunk保留。messageId 现在必需
agent_message_chunk保留。messageId 现在必需
agent_thought_chunk保留。messageId 现在必需
-新增:user_messageagent_messageagent_thought 整条消息 upsert
-新增:前台 state_updaterunningidlerequires_action)附带停止原因
tool_call已移除。toolCallId 的第一个 tool_call_update 创建工具调用
tool_call_update保留。现在是显式 upsert,具有省略/null/值补丁语义
-新增:tool_call_content_chunk 用于流式传输单个内容项目
-新增:terminal_update 用于 Agent 拥有的显示终端状态和重放
-新增:terminal_output_chunk 用于追加显示终端字节
plan替换为 plan_update,带 planIdtype 判别字段
current_mode_update已移除。模式是配置选项。使用 config_option_update
available_commands_update保留。命令输入现在带有必需的 type 判别字段
config_option_update不变
session_info_update不变
usage_update不变

初始化

角色无关的 info 和 capabilities

v1 使用角色特定的字段名:客户端发送 clientCapabilities 和可选的 clientInfo,Agent 返回 agentCapabilities 和可选的 agentInfo。v2 在两个方向使用相同的两个字段名(capabilitiesinfo),且 info 现在双方都必需。

{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": 1,
    "clientCapabilities": {
      "fs": { "readTextFile": true, "writeTextFile": true },
      "terminal": true
    },
    "clientInfo": { "name": "my-client", "version": "1.0.0" }
  }
}
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": 2,
    "info": {
      "name": "my-client",
      "title": "My Client",
      "version": "1.0.0"
    },
    "capabilities": {}
  }
}
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": 1,
    "agentCapabilities": {
      "loadSession": true,
      "promptCapabilities": { "image": true, "embeddedContext": true },
      "mcpCapabilities": { "http": true, "sse": false },
      "sessionCapabilities": {
        "list": {},
        "resume": {},
        "close": {}
      }
    },
    "authMethods": [],
    "agentInfo": { "name": "my-agent", "version": "0.3.0" }
  }
}
{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": 2,
    "info": {
      "name": "my-agent",
      "title": "My Agent",
      "version": "0.3.0"
    },
    "capabilities": {
      "session": {
        "prompt": {
          "image": {},
          "embeddedContext": {}
        },
        "mcp": {
          "stdio": {},
          "http": {}
        },
        "delete": {},
        "additionalDirectories": {}
      }
    },
    "authMethods": []
  }
}

以上两个响应都有空的 authMethods 数组。在 v2 中,省略 authMethods 具有相同的可用性含义:Agent 不声明认证面,客户端 不得 调用 auth/loginauth/logout

支持标记是对象,不是布尔值

在 v1 中,能力支持是布尔值("image": true)和对象("list": {})的混合。在 v2 中,每个支持标记都是对象:提供 {}(或带字段的对象)表示支持,省略键或提供 null 表示不支持。

这意味着像 promptCapabilities.image === true 的检查变为存在性检查:capabilities.session.prompt.image != null。对象形状为每个能力增长字段或通过 _meta 扩展留出了空间,无需另一次破坏性变更。

能力重组

所有会话作用域能力组现在位于 capabilities.session 下。v1 的 agentCapabilities.promptCapabilities 现在是 capabilities.session.promptagentCapabilities.mcpCapabilities 现在是 capabilities.session.mcp

capabilities.session 本身是可选的,因此不进行基于会话提示的 Agent(例如,仅提供专用扩展面的 Agent)可以完全省略它。目前还没有对此的协议支持,但有几个实验正在进行中,我们正在确保非会话组件可以通过 capabilities 表达。

loadSessionsession/load 一起移除(参阅会话设置)。

单独的 listresumeclose 标记已移除:声明 capabilities.session 现在要求支持基线方法 session/newsession/listsession/resumesession/closesession/promptsession/cancelsession/update。可选额外功能如 session.deletesession.additionalDirectoriessession.promptsession.mcp 保留各自的标记。

v1 客户端能力 fsterminal 已完全移除(参阅客户端文件系统和终端执行)。稳定 v2 目前未定义标准客户端能力字段。Agent 拥有的终端显示是基线行为,不是客户端执行能力。

认证

认证方法归组在 auth/ 前缀下:

  • authenticate -> auth/login。参数不变(methodId 选择一个声明的认证方法)。描述符的标识符字段从 id 重命名为 methodId 以匹配,其类型判别字段现在是必需的。
  • logout -> auth/logout。在 v1 中,logout 支持通过能力标记可选。在 v2 中,在 authMethods 中返回一个或多个有效条目即声明了认证面,Agent 必须 同时实现 auth/loginauth/logout。如果 authMethods 被省略或为空,客户端 不得 调用任一方法。没有 logout 支持标记;capabilities.auth 保持正交,仅声明认证相关扩展。
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "auth/login",
  "params": { "methodId": "agent-login" }
}

认证方法

authMethods 中的每个条目现在使用 methodId 而非 id,并带有必需的类型判别字段,以便可以在不破坏现有实现的情况下引入未来的认证流程:

{
  "methodId": "agent-login",
  "type": "agent",
  "name": "Agent login",
  "description": "Sign in using the agent's login flow"
}

initialize 响应中包含此条目要求 Agent 同时支持 auth/loginauth/logout

稳定 v2 定义了 type: "agent"。自定义类型 必须_ 开头。不带前导 _ 的未知值保留给未来的 ACP 版本。

新的提示生命周期

这是 v2 中最重要的语义变更。即使你略读其余部分,也要阅读本节。简而言之,v1 通过待处理的 session/prompt 请求表达的一切都移到了 session/update 通知中:

前台信号v1v2
提示已接受隐式session/prompt 响应 ({})
历史中的用户消息隐式(请求本身)user_message 更新,带 Agent 拥有的 messageId
前台工作运行中session/prompt 仍待处理state_updatestate: "running"
前台工作等待隐式(待处理权限请求)state_updatestate: "requires_action"
前台工作结束session/prompt 响应带 stopReasonstate_updatestate: "idle"stopReason
取消已确认提示响应带 stopReason: "cancelled"空闲 state_updatestopReason: "cancelled"

v1:响应即轮次

在 v1 中,session/prompt 在整个轮次期间保持待处理。Agent 在工作时流式传输 session/update 通知,最终的响应携带结束轮次的 stopReason

{ "jsonrpc": "2.0", "id": 2, "result": { "stopReason": "end_turn" } }

这将提示接受与轮次完成纠缠在一起,使得重放、多客户端会话、后台工作和排队消息难以表达。

v2:响应是确认

在 v2 中,Agent 必须 在接受提示后立即以空结果响应 session/prompt

{ "jsonrpc": "2.0", "id": 2, "result": {} }

其他一切都通过 session/update 通知发生:

用户消息确认。 接受提示后,Agent 必须 报告用户消息在会话历史中的插入位置,要么作为带完整内容数组的 user_message 更新,要么作为流式 user_message_chunk 更新。此更新是 Agent 拥有的 messageId 的事实来源:

{
  "jsonrpc": "2.0",
  "method": "session/update",
  "params": {
    "sessionId": "sess_abc123",
    "update": {
      "sessionUpdate": "user_message",
      "messageId": "msg_user_8f7a1",
      "content": [{ "type": "text", "text": "Can you analyze this code?" }]
    }
  }
}

运行状态。 当前台工作开始或恢复时,Agent 必须 发送带 "state": "running"state_update

输出。 消息、思考、计划和工具调用像以前一样流式传输(具有以下部分描述的变更)。

完成。 当 Agent 准备好处理新提示时,它 必须 报告空闲。当转换结束前台工作时,它 必须 包含停止原因:

{
  "jsonrpc": "2.0",
  "method": "session/update",
  "params": {
    "sessionId": "sess_abc123",
    "update": {
      "sessionUpdate": "state_update",
      "state": "idle",
      "stopReason": "end_turn"
    }
  }
}

会话状态

state_update 是 v2 中的新功能,使用三种稳定状态之一(像所有 v2 枚举一样可扩展)报告前台工作:

  • running:前台工作正在进行中。
  • idle:Agent 准备好处理新提示。当转换结束前台工作时携带 stopReason
  • requires_action:前台工作被用户操作阻塞。Agent 应该 在等待权限响应时发送此状态,并在工作恢复时发送另一个 running 更新。

后台活动可以继续并在 Agent 报告空闲时发出其他 session/update 通知。这些通知不会改变状态。

停止原因本身与 v1 相比不变(end_turnmax_tokensmax_turn_requestsrefusalcancelled)。它们只是从 session/prompt 响应移到了空闲 state_update,并描述前台工作停止的原因。

取消

session/cancel 作为通知不变,但确认随生命周期的其余部分移动:Agent 不再以 "stopReason": "cancelled" 响应 session/prompt,而是 必须 完成发送任何待处理更新,然后发送带 cancelled 停止原因的空闲 state_update。客户端仍然以 cancelled 结果响应所有待处理的 session/request_permission 请求,并 应该 接受在发送 session/cancel 后到达的工具调用更新。

为什么这不仅仅是簿记问题

因为前台进度现在完全以通知表达,相同的消息流适用于 session/resume 上的历史重放、多个客户端观察一个会话,以及未来的 Agent 发起或排队工作。

消息和消息 ID

消息 ID 是必需的

v2 中的每个消息分块和消息更新 必须 携带 messageId。在 v1 中,messageId 在分块上是可选的。消息 ID 是 Agent 生成的不透明字符串:Agent 拥有会话历史,因此它是消息身份的唯一来源。一条消息的所有分块共享一个 messageIdmessageId 变化表示新消息。

整条消息 upsert

除了分块更新外,v2 还添加了 user_messageagent_messageagent_thought 更新,携带完整内容数组(分块携带单个内容块)。这些是以 messageId 为键的 upsert,具有三态补丁语义:

  • 省略 content 时保留现有内容不变(适用于在不重发内容的情况下更新 _meta 或其他字段)。
  • content: nullcontent: [] 清除消息内容。
  • 具体数组替换该消息当前存储的所有内容,包括从先前分块累积的内容。

分块始终追加到当前内容。例如:agent_messagecontent: [A],然后 agent_message_chunkB 渲染为 [A, B]。后续带 content: [C]agent_message 替换为 [C];先前的完整内容和分块被替换。后续分块追加到 [C]

会话设置

session/load 已移除

session/load 已从协议中移除。要加载会话并重放历史,使用带 replayFromsession/resume

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "session/resume",
  "params": {
    "sessionId": "sess_abc123",
    "cwd": "/home/user/project",
    "mcpServers": [],
    "replayFrom": { "type": "start" }
  }
}

当包含 replayFrom 时,Agent 必须 在响应请求之前通过 session/update 通知重放完整对话。这与旧的 session/load 语义一致。

不带 replayFromsession/resume 在不重放的情况下恢复会话,与 v1 的 session/resume 一致。

session/resume 现在是必需的

在 v1 中,session/resume 是可选的(通过能力标记)。在 v2 中,它是必需的基线方法。不需要恢复会话的 Agent 可以在收到 session/resume 请求时返回错误,但仍 必须 实现该方法。

客户端文件系统和终端执行

v1 的客户端文件系统方法(fs/read_text_filefs/write_text_file)和终端执行方法(terminal/createterminal/outputterminal/releaseterminal/wait_for_exitterminal/kill)已完全移除。

需要客户端工具的 Agent 应使用客户端提供的 MCP 服务器。Agent 拥有的终端输出现在是 v2 中的单独仅显示面,通过 terminal_updateterminal_output_chunk 会话更新报告。

工具调用

tool_call 已移除,tool_call_update 创建工具调用

在 v1 中,tool_call 通知创建工具调用,后续的 tool_call_update 更新它。在 v2 中,tool_call 已移除。toolCallId 的第一个 tool_call_update 创建工具调用。

tool_call_update 是显式 upsert

v2 使补丁语义显式化:

  • 省略字段保留先前值不变
  • null 清除值
  • 具体值替换先前值
  • contentlocations 作为整个数组替换

新增:tool_call_content_chunk

用于流式传输单个内容项目,客户端追加每个项目而非替换整个 content 集合。

新增:终端更新

terminal_updateterminal_output_chunk 用于 Agent 拥有的仅显示终端状态和实时输出。

计划

plan 通知替换为 plan_update,带 planIdtype 判别字段。计划条目以完整列表发送,客户端完全替换计划内容。

同时支持 v1 和 v2

实现者应该同时支持两个版本:

  1. 按连接协商:在 initialize 中,发送你支持的最新版本。如果对等方以不同版本响应,决定是否继续或断开连接。
  2. 保持 v1 正常工作:不要放弃 v1 支持。现有的客户端和 Agent 仍将使用 v1。
  3. 在功能标志后添加 v2:在 v2 稳定之前,将其置于显式功能标志之后。
  4. 按连接选择面:根据协商的版本,使用 v1 或 v2 方法面。单个连接在 initialize 之后始终只说一种版本。
  5. 共享代码:在可能的地方共享 JSON-RPC 帧、传输和消息路由代码。版本特定的逻辑隔离在版本协商和处理层中。