跳到主要内容

在 Cursor 中使用智能体客户端协议(ACP)

Agent Client Protocol with Cursor

智能体客户端协议(Agent Client Protocol,ACP)是一个开放标准,让编辑器客户端与编程智能体通过一个共享的、基于 JSON-RPC 的接口相互通信。无需每个编辑器都为每个智能体构建自己的桥接层,客户端和智能体都使用 ACP,配对关系就不再固定。Cursor 可以通过 cursor-agent 充当这段对话中的智能体一方,这意味着任何符合 ACP 的客户端都能驱动 Cursor 的智能体。本指南将解释这个协议、会话生命周期、如何通过 ACP 暴露 Cursor、实践中的消息流、如何构建你自己的客户端,以及当前的缺口在哪里。

什么是智能体客户端协议​

ACP 是一个开放标准,定义了编辑器客户端如何与编程智能体通信。该协议由 Zed 推广,Zed 用它让自己的编辑器可以驱动不同的智能体,但它刻意做到既不依赖编辑器,也不依赖智能体。

核心的设计选择很简单:

  • 传输层是基于 stdio 的 JSON-RPC。 客户端把智能体作为子进程启动,并在 stdin 和 stdout 上交换以换行分隔的 JSON-RPC 消息。
  • 双方都可以发送请求和通知。 客户端要求智能体开启会话并发送提示词;智能体向客户端请求运行工具的许可,并把进展流式回传。
  • 会话是显式的。 工作发生在用 newSession 创建的会话中,会话携带一个工作目录和若干选项。

由于传输层是纯粹的 stdio,消息是纯粹的 JSON,因此只要进程可以被启动,ACP 就能工作:本地编辑器、终端工具以及无头 CI 运行器。

ACP 不是 MCP

MCP 把智能体连接到外部工具和数据源。ACP 把编辑器客户端连接到智能体。它们解决的是不同的问题,而且常常一起使用:一个 ACP 智能体本身可能就是 MCP 客户端。

ACP 架构​

该架构由三部分组成:客户端、智能体,以及二者之间的传输层。客户端拥有 UI 和权限;智能体拥有模型和工具。

层次角色示例
客户端渲染对话、收集用户输入、回应权限请求Zed、自定义编辑器、无头脚本
智能体运行模型、执行工具、流式发送更新cursor-agent acp、其他 ACP 智能体
传输层双向传递 JSON-RPC 消息基于 stdio 的换行分隔 JSON

会话生命周期是一个固定的序列。一旦你理解它,消息流读起来就像一份脚本。

initialize -> client and agent agree on protocol version and capabilities
newSession -> client asks the agent to create a session with a cwd and options
prompt -> client sends user input into the session
session/update -> agent streams progress, tool calls, and messages back (notification)
permission request-> agent asks the client to approve a tool action
cancel -> client aborts the current turn

智能体在没有先询问客户端之前,绝不会对受保护的资源采取行动。正是这个权限往返让 UI 能够控制一个自主的智能体。

ACP 为什么重要​

ACP 之所以重要,是因为它把编辑器与智能体解耦。它的价值体现在三个方面。

编辑器可移植性​

如果 Cursor 和另一个客户端都说 ACP,你就可以在任意一个中使用同一个智能体,而无需定制集成。你的智能体配置、模型选择和规则都随智能体走,而不是随编辑器走。

在任何符合规范的客户端中复用 Cursor 的智能体​

与其在每个编辑器里重新实现智能体行为,你可以把任何 ACP 客户端指向 cursor-agent,从而在那个客户端的 UI 中获得 Cursor 的推理、工具使用和模型选择能力。这对于统一使用某个非 Cursor 编辑器、但又想要 Cursor 智能体行为的团队尤其有用。

无头自动化​

由于传输层是 stdio,你可以在完全没有编辑器的情况下用脚本或 CI 任务驱动智能体。这让在流水线中实现脚本化、可重复的智能体运行成为可能。

# A headless run is just a client that writes JSON-RPC to the agent's stdin
cat initialize.json newSession.json prompt.json | cursor-agent acp
无头运行同样需要权限

当你用脚本驱动智能体时,没有人类去点击工具调用的「批准」。请提前决定你的权限模式,并且在带有敏感凭据的环境中绝不要默认使用自动运行。

使用 cursor-agent 通过 ACP 暴露 Cursor​

cursor-agent 是可以充当连接中 ACP 智能体一方的 CLI。请像安装 Cursor CLI 那样安装它。

# Install via npm
npm install -g cursor-agent

# Or install via the Cursor CLI script
curl https://cursor.com/install -fsS | bash

# Confirm it is on your PATH
cursor-agent --version

以 ACP 模式运行​

ACP 的入口是一个子命令。当你运行它时,进程会在 stdio 上使用 JSON-RPC,并等待客户端连接。

# Start the agent as an ACP server, talking JSON-RPC over stdio
cursor-agent acp

# Useful flags
cursor-agent acp --cwd /path/to/project # set the working directory
cursor-agent acp --model claude-sonnet-4-5 # pin a model for the session
cursor-agent acp --verbose # log messages for debugging

身份认证​

智能体在能够处理提示词之前需要凭据。登录一次,凭据就会被存储在本地。

# Interactive login, opens a browser flow
cursor-agent login

# Check current auth status
cursor-agent status

# Log out and clear stored credentials
cursor-agent logout

如果智能体能启动但每个提示词都失败,原因几乎总是缺少认证或认证已过期。在做其他任何调试之前,先运行 cursor-agent status。

配置会话​

会话在创建时会带上一个工作目录以及若干选项,这些选项决定该次对话中智能体的行为方式。

模型选择​

模型可以在启动时固定,也可以按会话选择。按会话传递模型,能让同一个智能体进程以不同的模型偏好服务不同的客户端。

{
"method": "session/new",
"params": {
"cwd": "/home/dev/project",
"model": "claude-sonnet-4-5",
"mcpServers": []
}
}

上下文窗口用量上报​

有些客户端会显示上下文窗口有多满。在客户端支持的情况下,智能体会把 token 用量作为会话更新的一部分上报。

{
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": {
"type": "usage",
"inputTokens": 12480,
"outputTokens": 2310,
"contextLimit": 200000
}
}
}

权限模式与工作目录​

权限模式决定智能体在运行工具之前是否询问。在交互式客户端中,默认是询问。在无头自动化中,由你有意地选择。

  • 每个工具都询问(Ask-per-tool): 智能体发送权限请求并等待。最安全,最适合交互式编辑器。
  • 自动运行(Auto-run): 智能体在工作目录内直接执行而无需询问。速度快,但只适合在沙箱中使用。
  • 只读(Read-only): 智能体可以读取和搜索,但绝不写入。适合只做审查的会话。

newSession 中的 cwd 限定了智能体把什么视为项目。它之外的文件不应被触及。

JSON-RPC 消息流示例​

下面是一段最小但完整的对话。每条消息都是线路上的一行 JSON。

初始化​

客户端以 initialize 开场,智能体回复自己的能力与协议版本。

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": 1,
"clientCapabilities": {
"fs": { "readTextFile": true, "writeTextFile": true },
"terminal": true
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": 1,
"agentCapabilities": {
"promptCapabilities": { "image": false },
"loadSession": true
}
}
}

新建会话​

能力达成一致后,客户端创建一个会话并获得一个 ID。

{
"jsonrpc": "2.0",
"id": 2,
"method": "session/new",
"params": { "cwd": "/home/dev/project", "mcpServers": [] }
}
{
"jsonrpc": "2.0",
"id": 2,
"result": { "sessionId": "sess_01HXYZ" }
}

发送提示词​

客户端发送用户这一轮的内容。注意这是一个请求;智能体会确认它,但实际内容是以流式通知的形式到达的。

{
"jsonrpc": "2.0",
"id": 3,
"method": "session/prompt",
"params": {
"sessionId": "sess_01HXYZ",
"content": [{ "type": "text", "text": "Add input validation to the signup route." }]
}
}

流式更新​

在智能体工作期间,它会发出不带 ID 的 session/update 通知,因为它们是单向的。

{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "sess_01HXYZ",
"update": { "type": "agent_message_chunk", "text": "Reading src/routes/signup.ts" }
}
}

权限请求​

在一个受保护的操作之前,智能体会询问客户端。客户端通过标准的 JSON-RPC 请求机制回复。

{
"jsonrpc": "2.0",
"id": "perm-1",
"method": "session/request_permission",
"params": {
"sessionId": "sess_01HXYZ",
"toolCall": {
"title": "Write file",
"path": "src/routes/signup.ts"
}
}
}
{
"jsonrpc": "2.0",
"id": "perm-1",
"result": { "outcome": { "outcome": "selected", "optionId": "allow" } }
}

取消​

要中止一轮,客户端发送 session/cancel。智能体停止当前工作,并以一个取消停止原因结束该提示词请求。

{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": { "sessionId": "sess_01HXYZ" }
}

构建你自己的 ACP 客户端​

一个最小的客户端会启动智能体、把 JSON-RPC 行写入 stdin,并解析来自 stdout 的换行分隔 JSON。下面的代码片段展示了 TypeScript 中的形态。

import { spawn } from "node:child_process";
import { createInterface } from "node:readline";

const agent = spawn("cursor-agent", ["acp", "--cwd", process.cwd()], {
stdio: ["pipe", "pipe", "inherit"],
});

let nextId = 1;

function send(method: string, params: unknown): void {
const message = { jsonrpc: "2.0", id: nextId++, method, params };
agent.stdin.write(JSON.stringify(message) + "\n");
}

// Read newline-delimited JSON-RPC from the agent
const reader = createInterface({ input: agent.stdout });
reader.on("line", (line) => {
if (!line.trim()) return;
const msg = JSON.parse(line);

// Notifications have no id
if (!("id" in msg)) {
if (msg.method === "session/update") {
console.log("update:", JSON.stringify(msg.params.update));
}
return;
}

// A request from the agent needs a reply
if (msg.method === "session/request_permission") {
const reply = {
jsonrpc: "2.0",
id: msg.id,
result: { outcome: { outcome: "selected", optionId: "allow" } },
};
agent.stdin.write(JSON.stringify(reply) + "\n");
return;
}

console.log("response:", JSON.stringify(msg.result ?? msg.error));
});

// Kick off the session
send("initialize", { protocolVersion: 1, clientCapabilities: {} });
send("session/new", { cwd: process.cwd(), mcpServers: [] });

需要记住的关键规则:每个请求都有一个 id 并期待一个匹配的响应,通知永远不带 id,而且智能体也可以向你发送请求,因此你的读取器必须同时处理两个方向。

记录原始线路数据

当某个客户端集成行为异常时,在改动逻辑之前先把两个方向的每一行都记录下来。ACP 的缺陷通常是缺少 id、丢失的换行,或者一个从未发送的响应,而原始线路数据能让这三种情况都变得显而易见。

局限与当前缺口​

ACP 还很年轻,并非每一项 Cursor 能力都通过它暴露出来。在基于它构建之前,请先了解你会放弃什么。

无法自定义系统提示词​

ACP 会话选项目前不允许客户端像 IDE 中的 .cursor/rules 那样替换或扩展智能体的系统提示词。项目中配置的规则仍会被智能体应用,但客户端无法注入自己的规则。

上下文窗口上报不完整​

用量上报存在,但不保证完整或实时。想要精确上下文计量器的客户端不应只依赖 ACP 的用量更新。

与 IDE 的功能对等性差异​

有几项 IDE 功能在 ACP 上不可用或被削弱:

  • 除非客户端自行实现,否则无法在客户端内对提议的改动进行可视化 diff 审查。
  • 后台代理和云端代理不通过这条路径驱动。
  • 部分工具和 MCP 行为与 IDE 的 Agent 模式不同。
  • Tab 补全是另一套独立系统,完全不属于 ACP。

故障排查​

找不到智能体​

如果客户端启动 cursor-agent 时得到 ENOENT,说明该二进制文件不在执行启动操作的那个进程的 PATH 中。在启动调用中使用绝对路径,并记住 GUI 客户端继承的 PATH 可能与你的终端不同。

# Find the absolute path and use it in the client config
which cursor-agent

认证错误​

每个提示词都因认证错误而失败,说明智能体没有有效的凭据。运行 cursor-agent status,然后运行 cursor-agent login,并检查客户端运行时所使用的用户或主目录是否与你登录时所用的不同。

没有流式更新​

如果提示词请求有返回,但更新始终不来,客户端很可能没有正确解析通知。通知没有 id,因此只处理响应的读取器会把它们丢掉。请确认你的行读取器在换行时触发事件,并且解析器能处理不带 id 的消息。

客户端始终收不到权限提示​

如果智能体不询问就运行了工具,那么要么会话是以自动运行权限模式创建的,要么客户端在 initialize 期间从未声明自己具备处理权限请求的能力。请同时检查会话选项和你发送的能力声明。

症状原因修复
启动时 ENOENT智能体二进制文件不在 PATH 中在启动调用中使用绝对路径
每个提示词都失败缺少认证或认证已过期运行 cursor-agent status,然后运行 cursor-agent login
没有 session/update 消息读取器只处理带 id 的响应处理不带 id 的通知
没有权限提示自动运行模式,或缺少客户端能力声明设置为每个工具都询问,并声明该能力
提示词一直挂起对某个请求的响应从未发送对智能体的每个请求都用匹配的 id 回复
响应解析错误消息被拼接在一起,缺少换行每行只写入和读取恰好一个 JSON 对象

ACP 把编辑器与智能体的关系变成了一份契约,而正是这份契约让 Cursor 的智能体可以在任何会讲该协议的地方使用。从上面的消息流开始,把线路数据记录下来,集成就变成一点点管线工作,而不是一次重写。