ACP 协议详解——编辑器与编码 Agent 之间的开放标准

本文为「AI Agent 技术系列」第 9 篇。MCP(第 3 篇)解决“Agent 如何接入工具与数据”,A2A(第 8 篇)解决“独立 Agent 之间如何协作”,ACP 补上第三个方向:人每天使用的编辑器与编码 Agent 之间如何通信。三条通道互不替代,可以同时存在。

在编辑器里用 AI 编码助手已是日常,但“接进来”这件事并不便宜。每个编辑器要为每个想支持的 Agent 写定制集成;反过来,Agent 想触达用户,就得逐个实现编辑器的私有 API。落到使用者身上,就是你选定一个 Agent,往往等于接受了它所支持的界面 [1]。

Zed 团队在 2025 年发起的 Agent Client Protocol(ACP)正是冲着这个问题来的 [1][2]:像 LSP 统一语言服务器的接入那样,把 Agent 的接入也统一掉 [1]。本文以当前稳定的 v1 为基准,讲清它的架构、通信模型与一次会话的完整流程,再单列一节梳理 v2 的重构。除特别说明,方法名与字段以官方文档为准,JSON 示例中的 ID 仅为说明结构。

一、ACP 是什么:把 M×N 的集成矩阵变成 M+N

官方定义与本文版本

ACP 标准化代码编辑器(查看和编辑源码的交互程序)与编码 Agent(用生成式 AI 自主修改代码的程序)之间的通信,本地与远程场景都适用 [1][3]。本地 Agent 作为编辑器的子进程运行,走 stdio 上的 JSON-RPC;远程 Agent 可以托管在云端或独立基础设施上,走 HTTP 或 WebSocket,官方明确注明远程的完整支持仍在推进 [1]。

版本状态需要先说清楚:当前稳定的协议版本是 1;v2 已有完整的规范草案与迁移指南,但整个 v2 协议面仍标注为 draft [3][4]。所以本文以 v1 为主线,v2 的变化集中在第四节。

集成为什么痛

文档把问题归为三条 [1]:

痛点 表现
集成开销 每一个新的“编辑器 × Agent”组合都要定制开发
兼容性受限 Agent 只能工作在一部分编辑器里
开发者锁定 选了 Agent,就得接受它支持的界面

好处是双向的:实现 ACP 的 Agent 可以用于任何兼容的编辑器,支持 ACP 的编辑器则获得整个 Agent 生态。解耦让两侧独立创新,M×N 的集成矩阵被压成 M+N [1]。

与 MCP、A2A 的位置关系

graph LR
    U[🧑 用户] --> ED[🖥️ 编辑器 Client]
    ED -->|ACP| AG[🤖 编码 Agent]
    AG -->|MCP| TOOLS[🔧 工具与数据]
    AG2[🤖 独立 Agent] <-->|A2A| AG
协议 连接的两侧 回答的问题
MCP Agent ↔ 工具与数据 一个 Agent 如何接入能力
ACP 编辑器 ↔ 编码 Agent 人的工作台如何承载 Agent
A2A 独立 Agent ↔ 独立 Agent 跨系统的 Agent 如何协作

三者不是竞争关系。ACP 在 JSON 表示上尽可能复用 MCP 的类型,让集成方不必再造一套常见数据结构,同时为编码场景的交互体验定义了自己的类型,比如 diff 展示 [1][5]。一个编码 Agent 完全可以同时对用户讲 ACP、对工具讲 MCP、对其他 Agent 讲 A2A。

一个被刻意选择的信任模型

架构文档里有条原则值得单独拎出来:ACP 面向“你用编辑器对话一个你信任的模型”的场景。你仍然控制着 Agent 的工具调用,但编辑器会把本地文件和 MCP servers 的访问交给 Agent [5]。

这决定了 ACP 与 A2A 是两个不同的威胁模型:ACP 里 Agent 跑在你自己的机器上、改动你的代码,安全边界在于权限确认的交互设计;A2A 里对方是跨组织的外部系统,安全边界在于认证、授权与数据保护。给 Agent 选通道时,先分清自己站在哪个场景里。


二、架构与通信模型:双向 JSON-RPC 加流式通知

两个角色与连接方式

角色 是什么 职责
Client 编辑器、IDE,或其他给 Agent 提供 UI 的程序 管理环境、处理用户交互、控制资源访问
Agent 用生成式 AI 自主修改代码的程序 通常作为 Client 的子进程运行 [6]

用户尝试连接某个 Agent 时,编辑器按需拉起子进程,之后的通信全部走 stdin/stdout。一条连接可以承载多个并发会话,几条互不干扰的“思路”可以同时进行 [5]。

三条设计原则

  1. MCP-friendly:协议建立在 JSON-RPC 之上,尽可能复用 MCP 类型,避免给集成方增加又一套数据表示 [5];
  2. UX-first:为“与 AI Agent 交互”的 UX 挑战而设计——足够灵活地呈现 Agent 的意图(diff、工具调用、计划),但不做多余的抽象 [5];
  3. Trusted:如上节所述,编辑器授予 Agent 本地文件与 MCP servers 的访问,用户保留对工具调用的控制 [5]。

通信原语:方法与通知

协议遵循 JSON-RPC 2.0,消息只有两类 [6]:

  • 方法(Method):请求-响应成对出现,期待 result 或 error;
  • 通知(Notification):单向消息,不期待任何响应。

ACP 大量使用通知,让 Agent 能把输出实时流式推给界面;同时利用 JSON-RPC 的双向性,Agent 也可以反向调用编辑器的方法,比如为一次工具调用请求用户授权 [5]。这是它与许多“单向网关 API”的本质区别:谁调用谁取决于消息的方向,而不是角色——Client 调 Agent 发提示词,Agent 也调 Client 要权限、要文件。

几条贯穿始终的约定:文件路径必须是绝对路径;行号从 1 开始;JSON 属性键用 camelCase,判别字段的取值用 snake_case;错误处理遵循 JSON-RPC 2.0 [6]。

一次会话的消息流

sequenceDiagram
    participant U as 用户
    participant C as 编辑器 Client
    participant A as Agent 子进程

    C->>A: initialize(协商版本与能力)
    C->>A: authenticate(如需要)
    C->>A: session/new(cwd 与 MCP 配置)
    U->>C: 输入提示词
    C->>A: session/prompt(内容块)
    A-->>C: session/update 通知流(消息块 / 工具调用 / 计划)
    A->>C: session/request_permission(反向请求授权)
    C-->>A: 用户的选择
    A-->>C: session/prompt 响应(stopReason)

与 MCP 的协作方式

编辑器通常持有用户配置的 MCP servers。向 Agent 转发提示词时,会把这些配置一并传过去,让 Agent 直连 MCP server [5]。

编辑器自己的工具想暴露给 Agent 时,也不必让 MCP 和 ACP 挤在同一个 socket 上:把自身包装成一个 MCP server 的配置交给 Agent 即可。考虑到部分 Agent 只支持 stdio 上的 MCP,编辑器可以提供一个小型 proxy,把请求隧道回自身 [5]。


三、v1 全貌:从 initialize 到 stopReason

典型消息流分三步

  1. 初始化:Client 调用 initialize 建立连接;如果 Agent 要求,再调用 authenticate 认证;
  2. 建立会话session/new 创建新会话;或用 session/load 恢复已有会话(需要 loadSession 能力);
  3. Prompt 回合session/prompt 发送用户消息,Agent 用 session/update 通知流式回报进度,期间可能发生权限请求;必要时 Client 发 session/cancel 中断;回合以 session/prompt 的响应结束,携带 stopReason [6]。

初始化:版本与能力协商

initialize 是双方交换“我能做什么”的时刻(示例精简自官方迁移指南中的 v1 样例 [4]):

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"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" }
}
}

Agent 的响应会带回自己支持的协议版本、能力清单与认证方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"protocolVersion": 1,
"agentCapabilities": {
"loadSession": true,
"promptCapabilities": { "image": true, "embeddedContext": true },
"mcpCapabilities": { "http": true, "sse": false }
},
"authMethods": [],
"agentInfo": { "name": "my-agent", "version": "0.3.0" }
}
}

这份清单决定了后续每个可选方法是否可用:协议把功能切成基线与可选两层,可选层一律要靠能力声明解锁。

Prompt 回合:流式输出、工具调用与反向请求

用户消息由内容块组成,共五种类型:text、image、audio、resource_link、resource [4]。Agent 的产出则通过 session/update 通知呈现,主要 variant 包括 [6]:

variant 内容
user_message_chunk / agent_message_chunk / agent_thought_chunk 用户消息、回复、思考过程的流式片段
tool_call / tool_call_update 工具调用及其状态更新
plan 任务计划
available_commands_update 可用的斜杠命令
current_mode_update 会话模式变化

工具调用的通知长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "sess_abc123",
"update": {
"sessionUpdate": "tool_call_update",
"toolCallId": "call_001",
"title": "Reading configuration file",
"kind": "read",
"status": "pending"
}
}
}

需要用户授权时,Agent 反向调用 session/request_permission,给出 allow_once / allow_always / reject_once / reject_always 等选项,由编辑器呈现给用户 [6]。需要访问文件或终端时,Agent 调用 Client 的可选方法:fs/read_text_filefs/write_text_file、terminal 系列(创建、输出、释放、等待退出、终止),以及 elicitation/create(向用户请求结构化输入)[6]。

注意这些方法都不是必选的:Client 可以一个都不实现,Agent 必须依据能力声明优雅降级。

回合结束时,session/prompt 的响应携带 stopReason(end_turn / max_tokens / max_turn_requests / refusal / cancelled)[4]。用户随时可以发 session/cancel 通知中断当前回合 [6]。

v1 方法总览

方法 调用方向 必选性 能力门槛
initialize Client → Agent 基线
authenticate Client → Agent 可选 Agent 声明认证方式
session/new Client → Agent 基线
session/prompt Client → Agent 基线
session/load Client → Agent 可选 loadSession
session/set_mode Client → Agent 可选
logout Client → Agent 可选 auth.logout
session/cancel Client → Agent 通知
session/update Agent → Client 通知
session/request_permission Agent → Client 基线
fs/read_text_file、fs/write_text_file Agent → Client 可选 fs
terminal/create 等五个 Agent → Client 可选 terminal
elicitation/create Agent → Client 可选 对应 elicitation 模式能力

这张表也解释了 ACP 的渐进式复杂度:实现一个最小可用 Client,只需要 request_permission 加通知处理;剩下的能力按需补齐。


四、v2:一次按生态现实做的重构

定位:合并式重构,v1/v2 并存

v2 是一次合并式重构(consolidation release):重新设计 prompt 生命周期、统一流式与非流式更新、让 schema 默认向前兼容,并移除生态已经实际放弃的部分 [4]。

两点前提必须强调:其一,v2 整体仍标注为 draft,文档要求用显式版本协商与 feature flag 控制接入,直到它稳定 [4];其二,迁移不等于放弃 v1——v1 的实现会在很长一段时间里继续存在,官方的推荐做法就是两侧并存,按连接协商协议版本,把 v2 放在开关后面逐步成熟 [4]。

如果只记五件事

官方迁移指南给了一个“只记五件事”的清单 [4],我按自己的理解展开:

1. session/prompt 的响应不再是回合结束。 响应只确认“已接受”(空对象),前台进度与完成全部改由 state_update 通知表达:running(工作中)、idle(空闲,携带 stopReason)、requires_action(等待用户操作,比如权限确认)。stopReason 从响应挪到了 idle 通知里 [4]。

2. 更新都是 upsert。 消息、工具调用、计划都按 ID 打补丁,语义统一:字段省略 = 不变,null = 清除,具体值 = 替换,chunk = 追加;messageId 变为必填,由 Agent 生成并作为消息身份的唯一来源 [4]。

3. Client 的文件系统与终端执行 API 被移除。 v1 的 fs/* 与 terminal/* 在少数 IDE 之外实现得参差不齐,Agent 无论如何都要自备文件与执行能力。v2 里客户端工具统一通过 MCP server 提供,和 Agent 用的其他工具站在同一起跑线上;Agent 侧的终端输出变成单独的 display-only 展示面,只播不控 [4]。

4. 能力结构重组。 双方统一为 capabilities 加必填的 info;会话域能力嵌套在 session 之下;支持标记从布尔值变成对象({} 表示支持,为将来扩展留位);宣告 session 能力即承诺实现 new、list、resume、close、prompt、cancel、update 七个基线方法,Client 不必再逐个探测 [4]。

5. 一切皆可扩展。 枚举与 tagged union 接受未知值:_ 前缀保留给实现私有扩展,不带下划线的未知值保留给未来的 ACP 版本;接收方应当保留这些值并安全降级,而不是解析失败 [4]。

Prompt 生命周期对比

这是 v2 最重要的语义变化,我单独列成表 [4]:

信号 v1 v2
提示词已被接受 隐含 session/prompt 响应(空对象)
前台工作运行中 session/prompt 仍挂起 state_update: running
等待用户操作 隐含(权限请求挂起中) state_update: requires_action
前台工作结束 session/prompt 响应带 stopReason state_update: idle 携带 stopReason
取消确认 响应 stopReason: cancelled idle state_update 携带 cancelled

为什么值得重构?v1 把“提示词已被接受”和“回合结束”纠缠在一个挂起请求里,导致历史回放、多客户端观察同一会话、后台工作、消息排队都不好表达。v2 把前台进度全部搬进通知之后,同一套消息流可以同时服务实时对话、resume 时的历史回放和多端观察 [4]。

方法映射精选

v1 v2
authenticate / logout auth/login / auth/logout(authMethods 非空则必须成对实现)
session/load + session/resume 仅 session/resume,可选 replayFrom 控制是否回放历史
session/list、session/close 转为必备
session/set_mode + current_mode_update 统一为 config options(mode / model / model_config / thought_level)
tool_call + tool_call_update 仅保留 tool_call_update,首个通知即创建
plan plan_update(带 planId,支持多计划)
fs/* 与 terminal/*(Client 执行) 移除;客户端工具经 MCP server 提供
diff(oldText/newText) 结构化 changes 加可选 git patch 文本
clientCapabilities / agentCapabilities 双向统一的 capabilities,对象化支持标记

以 diff 为例:v1 的 oldText/newText 无法区分“删除文件”与“清空文件”,表达不了重命名、复制和二进制变更,渲染 diff 还得客户端自己算。v2 改为结构化的 changes 列表(add / delete / modify / move / copy,附 fileType 与 mimeType),外加可选的 git patch 文本,客户端可以直接据此构建文件树和摘要 [4]。

权限请求也做了清理:v1 里 Agent 常把提示文案塞进 tool call 的 title,顺带污染了工具调用的展示状态。v2 把权限提示的 title(必填)与请求对象 subject(tool_call 或 command)分开,提示归提示,状态归状态 [4]。

还有几处变化,实现时都会碰到 [4][7]:MCP server 配置必须带 type 判别字段,删除了已废弃的 HTTP+SSE 传输;stdio 上明确遵循 JSON-RPC 2.0 batch 行为,但 initialize、auth/login、session/new、session/resume、session/prompt 这类会改变后续消息有效性的消息不要批处理。


五、生态现状:Agent、Client、Registry 与 SDK

Agent 侧:四十个左右的实现,适配器是常见的接入方式

官方 Agent 列表已有约四十项,知名的有 Gemini CLI、Codex CLI、Claude Agent、Cursor、GitHub Copilot(公开预览)、Qwen Code、OpenCode、Cline、Kimi CLI、Junie 等 [8]。

接入方式里有个模式很能说明问题:Claude Agent 经 Zed 的 SDK 适配器接入,Codex CLI 经官方的 codex-acp 适配器接入 [8]。两者都不是原生实现——为存量 Agent 写一层 ACP 适配,比重写执行循环划算得多,接入成本就是这样被压下来的。

Client 侧:从 IDE 到手机

Client 的名单更长:Zed(发起方)、JetBrains、Visual Studio Code(多个扩展)、Neovim(CodeCompanion、avante.nvim 等插件)、Emacs、Obsidian、Pulsar、Qt Creator、Sublime Text、Unity;再往外是桌面应用、移动 App,以及接到 Telegram、Discord、Slack、飞书、微信的消息桥 [9]。

ACP 的生态已经溢出了“编辑器”这个词:任何能给 Agent 提供 UI 和环境的地方,都可以长出一个 Client。这对 Agent 作者是个好消息——实现一次 ACP,可能同时出现在 IDE、笔记本应用、手机和聊天软件里。

Registry 与 SDK

Registry 是官方策展的 Agent 分发目录,目前收录支持认证的 Agent,任何讲 ACP 的 Client 都可以从这里发现和安装 [10]。

官方 SDK 覆盖 TypeScript、Rust、Python、Kotlin、Java 五种语言;协议本体以 JSON Schema 形式随 GitHub release 发布,其他语言可以据此生成 [3]。准备实现时,建议从 introduction 与 protocol overview 进入概念,用 JSON Schema 逐项核对字段,别凭记忆写接口。


结论

回到开头的问题:怎么终结 M×N 的集成矩阵。ACP 的答案是给“编辑器 ↔ Agent”定一份契约:Agent 实现一次,就能进入所有支持 ACP 的界面;界面实现一次,就能获得整个 Agent 生态。放到系列语境里:MCP 统一的是 Agent 的“手”,A2A 统一的是 Agent 之间的“对话”,而 ACP 补的是人与 Agent 之间的“工作台”。

我的落地建议是先选角色,再看版本:

  1. Agent 作者:如果程序只打算在某一个编辑器里运行,直接适配它的 API 也许更快;想进生态,就实现 ACP 或写一层适配器。从官方 SDK 的 examples 起步,先做 v1,v2 用 feature flag 跟进。
  2. Client 作者:一次接入换来整个生态,但要清楚标准化不覆盖的地方——权限 UX、diff 与终端的渲染恰恰是各家差异化的空间,也是安全边界所在。记住 ACP 的信任模型:Agent 跑在你机器上、动你的代码,request_permission 的呈现质量直接影响风险。
  3. 版本策略:按连接协商 protocolVersion,v1/v2 并行支持;v2 仍是 draft,不要让未灰度的用户直连 v2。如果只记 v2 的一条心法,就是“响应即确认,一切皆 upsert”。

最后是两点保留意见。ACP 目前的重心显然在“本地子进程”这个形态:远程 Agent 的完整支持仍在推进,远程传输(streamable HTTP 加 WebSocket)还停在独立的 RFD 阶段 [1][4],把它当现成的远程协议用为时尚早。更让我在意的是生态扩张带来的安全面——消息桥和移动端意味着本地文件与终端的授权可能从手机上发起,把本地资源交给 Agent 的每一道口子,都得在 Client 侧默认收紧。


参考资料

以下链接为英文官方资料;v2 相关内容目前标注为 draft,接入前请留意页面上的版本说明。

[1] Introduction:协议介绍

[2] Zed 博客:How the Community is Driving ACP Forward

[3] GitHub:agent-client-protocol

[4] Migrating from v1:v1 到 v2 迁移指南

[5] Architecture:架构

[6] Protocol v1 Overview:v1 协议总览

[7] Protocol v2 Overview:v2 协议总览

[8] Agents:Agent 列表

[9] Clients:Client 列表

[10] ACP Registry:Agent 注册目录