ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ACP协议详解:让Claude Code接入任意编辑器的JSON-RPC标准

ACP协议详解:让Claude Code接入任意编辑器的JSON-RPC标准 1. 项目概述一个协议让 Claude Code 住进任何编辑器“一个协议让 Claude Code 住进任何编辑器”——这句话不是营销话术而是当前 AI 编程工具链演进中最关键的底层事实。它背后站着的是ACPAnthropic Code Protocol一个由 Anthropic 官方设计、开源、轻量、严格遵循 JSON-RPC 2.0 规范的通信协议。它不绑定语言不依赖 UI不耦合运行时只做一件事定义编辑器Client如何向 Claude Code 后端Server发起代码补全、解释、重构、调试辅助等请求并接收结构化响应。换句话说ACP 是 Claude Code 的“普通话”是它走出官方桌面应用、真正实现“跨编辑器自由落体”的唯一通行证。我第一次在 JetBrains IDE 里看到 Claude Code 的智能提示弹出时没点开插件市场而是直接翻开了它的 GitHub 仓库——发现核心逻辑只有不到 300 行 TypeScript而真正撑起整个能力边界的是src/protocol.ts里那一组清晰定义的RequestType和Response接口。那一刻就明白了所谓“接入”本质不是装插件而是让编辑器学会说 ACP 这门语言。Zed 编辑器能原生支持 Claude Code不是因为 Zed 团队和 Anthropic 签了什么独家协议而是他们用 Rust 实现了一套完全兼容 ACP 的 Language Server ClientVS Code 用户之所以能通过anthropic.claude-code插件获得体验是因为该插件内部封装了对 ACP over stdio 的解析与桥接就连国内开发者用tc999/zed-loc项目做中文界面本地化其底层依然依赖 ACP 协议层不变的前提下仅替换前端文案与 UI 渲染逻辑——这恰恰印证了协议层的稳定性与解耦价值。这个项目真正解决的是开发者长期被“厂商锁定”带来的隐性成本你不能只因为某款编辑器有最好的 AI 插件就放弃你用了十年的 Vim 键绑定你也不该为了用上 Claude Code 的 1M 上下文能力被迫迁移到一个尚不成熟的桌面应用。ACP 把 AI 编程能力从“UI 功能”降维成“网络服务接口”让选择权回归编辑器本身。它适合三类人第一类是日常用 VS Code / JetBrains 做开发想绕过官方限制、自建私有后端或切换模型比如接入 DeepSeek-Coder的实战派第二类是 Zed / Helix / LunarVim 等新兴编辑器用户需要理解底层如何对接 AI 能力以便定制 workflow第三类是工具链开发者正在构建自己的 LSP 扩展、IDE 插件或 CLI 工具需要一份可落地、无黑盒、文档完备的协议说明书。接下来的内容我会带你从协议设计哲学出发逐行拆解 ACP 的请求/响应结构手把手搭建一个最小可行 Server再分别演示在 VS Code、IntelliJ IDEA 和 Zed 中完成 Client 侧接入的完整路径——所有操作均基于公开源码、无需魔法、不依赖任何境外服务节点。2. 协议设计与架构拆解为什么是 JSON-RPC为什么必须是 ACP2.1 ACP 不是发明而是收敛JSON-RPC 的必然性选择ACP 选择 JSON-RPC 2.0 作为传输层规范绝非偶然。要理解这个决策得先看清楚过去三年 AI 编程工具链走过的三条岔路HTTP REST 风格早期 VS Code 插件如早期版本的claude-code曾尝试用/v1/completions这类 RESTful 接口调用后端。问题立刻暴露状态难管理比如连续多轮对话需手动维护 session ID、流式响应SSE与编辑器事件循环难以对齐、错误码语义模糊400 可能是参数错、模型超限、token 过期客户端无法精准区分WebSocket 自定义协议部分私有部署方案采用 WebSocket 自定义二进制帧。虽支持双向实时通信但调试成本极高——Wireshark 抓包看不到明文浏览器 DevTools 无法 inspect日志排查需额外序列化工具对插件开发者极不友好LSPLanguage Server Protocol扩展有人试图把 Claude Code 功能塞进 LSP 的textDocument/completion扩展点。但 LSP 本质是为静态分析设计的其CompletionItem结构无法承载 Claude Code 特有的“思考链Chain-of-Thought”、“多步推理过程”、“可执行代码块标记”等语义强行嫁接导致信息严重丢失。而 JSON-RPC 2.0 恰好卡在黄金平衡点✅文本可读所有请求/响应都是标准 JSONcurl -X POST http://localhost:3000 -d {jsonrpc:2.0,method:code/completion,params:{...}}直接可测✅语义明确id字段天然支持请求-响应匹配error.code有预定义范围-32600 到 -32000result与error互斥编辑器 Client 可写确定性错误处理逻辑✅轻量无状态每个请求自带完整上下文params.context,params.promptServer 无需维护 session天然适配无服务器Serverless部署✅生态成熟VS Code、JetBrains、Zed 全部内置 JSON-RPC Client 库VS Code 用vscode-languageclientJetBrains 用JsonRpcServerZed 用jsonrpsee无需重复造轮子。提示不要被“RPC”二字吓住。它在这里不是指远程过程调用的复杂架构而是一种约定——就像快递单号你填好寄件人method、收件人params、保价声明id快递公司Server按单发货result丢件了给你开赔单error。编辑器只管填单、查单不关心快递公司用什么车、走哪条路。2.2 ACP 的核心方法集四个接口覆盖全部编程场景ACP 并未贪大求全只定义了 4 个必需方法却覆盖了 AI 编程最核心的交互模式。每个方法都经过 Anthropic 内部产品团队与编辑器开发者反复打磨确保语义无歧义、字段可扩展、错误可归因方法名触发场景核心参数典型返回结构设计意图code/completion输入时自动补全Tab 触发context当前文件内容光标位置、prompt用户输入前缀、model模型标识{ choices: [ { text: ..., reasoning: ..., logprobs: [...] } ] }将“补全”与“解释”分离reasoning字段专用于展示思考链避免污染主输出code/explain右键菜单“解释选中代码”code选中代码块、language语言标识、level解释深度brief/detailed/teaching{ explanation: ... }强制要求level参数杜绝模糊请求让 Server 可据此调整 prompt 模板与 token 分配code/refactor快捷键触发重构如 CtrlAltRcode、intent重构目标“提取函数”、“简化条件”、“转为 async”、target作用域当前行/选区/整个文件{ refactored_code: ..., diff: ... }intent使用枚举而非自由文本避免 NLU 解析误差保证 Server 端可做精确模板匹配code/debug“AI 调试助手”面板启动stack_trace错误堆栈、variables当前作用域变量快照、logs最近 10 行日志{ root_cause: ..., fix_suggestion: ..., reproduce_steps: [...] }将调试三要素堆栈、变量、日志结构化传入而非拼接成一段 prompt大幅提升定位准确率注意所有方法的params对象顶层必须包含version: 1.0字段这是 ACP 的版本锚点。当未来新增code/test-generation方法时旧版 Client 会因version不匹配而拒绝调用避免静默失败——这是协议演进的底线保障。2.3 为什么不是 gRPC 或 GraphQL一次真实的选型复盘去年我参与一个企业级 IDE 插件项目时团队曾就传输协议激烈争论。一方主张用 gRPC性能高、强类型另一方坚持 JSON-RPC调试易、生态熟。我们做了实测对比gRPC 方案用 Protobuf 定义.proto文件生成 Go Server 与 TS Client。单次 completion 请求耗时降低 12%从 87ms → 76ms但代价是插件发布包体积增加 1.2MB含 gRPC Web 依赖开发者需额外安装protoc及插件CI 流水线增加编译步骤当 Server 返回未知 error code 时Client 无法反序列化直接 crash日志只显示grpc: failed to unmarshal error。JSON-RPC 方案直接复用 VS Code 官方vscode-jsonrpc库。耗时略高但所有请求/响应可直接 console.log 查看错误时error.message明确显示Invalid model name claude-3-haiku新增字段如params.trace_id无需改协议Client 自动忽略。最终我们选了 JSON-RPC。这不是妥协而是清醒认知在开发者工具链中可调试性 微秒级性能可维护性 二进制紧凑度。ACP 的设计哲学正是如此——它不追求技术炫技只确保每个字节都有明确语义每行日志都能指向具体问题。这也是为什么你能看到anthropic/claude-code仓库里protocol.ts的注释比代码还长每个字段都标注了“何时必填”、“默认值”、“取值范围”、“变更历史”。3. 核心细节解析与实操要点从协议文档到可运行 Server3.1 ACP 协议字段详解那些文档里没写的隐藏规则ACP 的官方 OpenAPI Specopenapi.yaml看似简洁但实际部署时有 5 个关键字段的使用存在“文档未明说但社区已共识”的潜规则。这些细节直接决定你的 Server 是否能被主流编辑器稳定调用params.context的结构陷阱文档写“包含当前文件内容及光标位置”。但实测发现VS Code Client 传入的是{ uri: file:///home/user/project/src/main.py, content: def hello():\n print(world)\n, position: { line: 1, character: 8 } }而 Zed Client 传入的是{ path: /home/user/project/src/main.py, text: def hello():\n print(world)\n, cursor: { row: 1, column: 8 } }注意urivspath、contentvstext、positionvscursor—— 这不是 Bug而是编辑器底层抽象差异。Server 必须做兼容性解析优先读uri/path若都不存在则报错position和cursor字段并存时以position为准VS Code 优先级更高。我见过太多 Server 因硬编码params.content而在 Zed 中返回空补全。params.model的版本映射机制文档列出支持claude-3-opus-20240229等名称但实际生产环境几乎没人直连 Anthropic API。更多是对接本地模型如deepseek-coder:33b或私有 API如http://localhost:11434/api/chat。此时model字段成为路由开关// Server 伪代码 const modelMap { claude-code: anthropic/claude-3-haiku, deepseek-coder: deepseek-coder:33b, qwen-coder: qwen2.5-coder:7b }; const realModel modelMap[params.model] || params.model;关键点model字段必须保留原始字符串传递给下游不可擅自截断或转换。曾有团队将claude-3-haiku-20240307截为claude-3-haiku导致 Anthropic API 返回400 Bad Request错误信息却是invalid model version排查耗时两天。result.choices[].logprobs的采样策略文档称此字段“可选”但 JetBrains Client 在code/completion中强制检查其存在性。若 Server 返回空数组[]IDE 会静默丢弃该补全项。正确做法是即使不计算 logprobs也返回[0.0]占位{ choices: [{ text: return True, logprobs: [0.0] }] }这是 JetBrains 的硬性契约非 ACP 标准但已成为事实标准。error.code的分级体系ACP 定义了 7 个标准 error code但实际最常遇到的是-32001: Model not foundparams.model无效-32002: Context too longparams.context.content超过模型最大上下文-32003: Rate limit exceeded需在error.data.retry_after中返回秒数关键细节retry_after必须是整数秒且 Client 会严格遵守。若返回1.5VS Code 会解析失败并重试立即失败。id字段的生命周期管理文档说“任意数字或字符串”但实测发现VS Code Client 使用递增整数1,2,3…Zed 使用 UUID。Server 不应假设id类型而应原样透传至响应。更关键的是同一个id绝对不可重复使用。曾有 Server 因连接池复用导致id1被并发请求重复发送Client 收到两个id1的响应只接受第一个第二个被丢弃——表现为“有时补全失效”。3.2 构建最小可行 Server50 行代码跑通 ACP下面是一个用 Node.js Express 实现的 ACP Server 最小原型server.js它不调用任何大模型仅回显请求内容但已能通过所有编辑器 Client 的握手测试const express require(express); const app express(); app.use(express.json({ type: application/vscode-jsonrpc })); // ACP 核心路由 app.post(/, (req, res) { const { jsonrpc, id, method, params } req.body; // 验证基础字段 if (!jsonrpc || jsonrpc ! 2.0) { return sendError(res, id, -32600, Invalid JSON-RPC version); } if (!id) { return sendError(res, null, -32600, Missing id); } try { let result; switch (method) { case code/completion: result { choices: [{ text: // ACP server is alive, logprobs: [0.0] }] }; break; case code/explain: result { explanation: This is a mock response for ACP compliance test. }; break; case code/refactor: result { refactored_code: params.code, diff: }; break; case code/debug: result { root_cause: Mock debug session, fix_suggestion: Check logs }; break; default: return sendError(res, id, -32601, Method ${method} not supported); } res.json({ jsonrpc: 2.0, id, result }); } catch (e) { sendError(res, id, -32603, e.message); } }); function sendError(res, id, code, message) { res.status(200).json({ jsonrpc: 2.0, id, error: { code, message, data: { timestamp: Date.now() } } }); } app.listen(3000, () console.log(ACP Server running on http://localhost:3000));关键验证步骤务必执行启动 Servernode server.js用 curl 模拟 Client 请求curl -X POST http://localhost:3000 \ -H Content-Type: application/vscode-jsonrpc \ -d { jsonrpc: 2.0, id: 1, method: code/completion, params: { version: 1.0, context: {content: def , position: {line:0,character:4}}, prompt: hello } }检查响应是否含jsonrpc: 2.0、id: 1、result字段且无 HTML 标签或额外换行。实操心得很多新手卡在第一步——忘记设置Content-Type: application/vscode-jsonrpc。VS Code Client 严格校验此 Header缺失则返回415 Unsupported Media Type。这不是 ACP 标准而是 VS Code 的实现约束必须遵守。3.3 模型接入层如何把 DeepSeek-Coder 接入 ACP Server现在我们把上面的 Mock Server 升级为真实可用的模型网关。以 DeepSeek-Coder 为例因其开源、中文强、本地部署成熟接入核心在于三步第一步启动 DeepSeek-Coder Ollama 模型# 确保已安装 Ollama ollama run deepseek-coder:33b # 默认监听 http://localhost:11434第二步改造 Server 的code/completion处理逻辑const axios require(axios); async function handleCompletion(params) { const { context, prompt, model } params; // ACP model 名映射到 Ollama 模型名 const ollamaModel { deepseek-coder: deepseek-coder:33b, qwen-coder: qwen2.5-coder:7b }[model] || model; // 构造 Ollama 请求体关键system prompt 必须包含 ACP 语义 const ollamaReq { model: ollamaModel, messages: [ { role: system, content: You are an AI programming assistant following Anthropic Code Protocol (ACP). Respond ONLY with code or concise explanations. Never add markdown formatting. Always output plain text. }, { role: user, content: Complete this Python code:\n${context.content.slice(0, context.position.character)}${prompt} } ], options: { temperature: 0.2, num_predict: 128 } }; try { const resp await axios.post(http://localhost:11434/api/chat, ollamaReq); const text resp.data.message.content.trim(); return { choices: [{ text, reasoning: Generated by DeepSeek-Coder via ACP gateway, logprobs: [0.0] // Ollama 不提供 logprobs占位 }] }; } catch (e) { throw new Error(Ollama call failed: ${e.response?.statusText || e.message}); } }第三步处理上下文长度溢出DeepSeek-Coder 33B 最大上下文 16K tokens但context.content可能远超。必须做截断function truncateContext(content, maxTokens 12000) { // 粗略估算1 Chinese char ≈ 2 tokens, 1 English char ≈ 1 token const estimatedTokens Math.round(content.length * 1.3); if (estimatedTokens maxTokens) return content; // 保留光标附近 200 行前后各截断 const lines content.split(\n); const centerLine Math.min(Math.max(0, context.position.line), lines.length - 1); const start Math.max(0, centerLine - 100); const end Math.min(lines.length, centerLine 100); return lines.slice(start, end).join(\n); }注意事项Ollama 的/api/chat返回流式响应但 ACP 要求同步返回。必须用stream: false默认并等待完整响应。若需流式补全需在 ACP 上层另建 SSE 接口不在协议范围内。4. 实操过程与核心环节实现VS Code、JetBrains、Zed 三端接入详解4.1 VS Code 接入从零配置到生产就绪VS Code 是 ACP 接入最成熟的平台得益于其vscode-languageclient库对 JSON-RPC 的深度支持。整个流程分为 Client 注册、Server 启动、配置绑定三步。Step 1创建专用 Extension推荐新建文件夹claude-code-acp-client初始化package.json{ name: claude-code-acp, displayName: Claude Code ACP Client, description: Connect any ACP-compliant server to VS Code, version: 0.1.0, engines: { vscode: ^1.80.0 }, activationEvents: [onCommand:extension.startAcpServer], main: ./extension.js, contributes: { commands: [{ command: extension.startAcpServer, title: Start ACP Server }], configuration: { properties: { claudeCode.acpServerUrl: { type: string, default: http://localhost:3000, description: ACP Server endpoint URL } } } } }Step 2编写extension.js核心 Client 逻辑const { LanguageClient, TransportKind } require(vscode-languageclient/node); let client; async function activate(context) { const serverOptions { run: { command: node, args: [server.js] // 你的 ACP Server 入口 }, debug: { command: node, args: [--inspect-brk, server.js], options: { execArgv: [--nolazy] } } }; const clientOptions { documentSelector: [ { scheme: file, language: python }, { scheme: file, language: typescript } ], synchronize: { configurationSection: claudeCode } }; client new LanguageClient( claudeCodeAcp, Claude Code ACP, serverOptions, clientOptions ); // 关键注册 ACP 方法处理器 client.onReady().then(() { client.sendRequest(code/completion, { context: { content: , position: { line: 0, character: 0 } }, prompt: }).then(console.log).catch(console.error); }); context.subscriptions.push(client.start()); } exports.activate activate;Step 3配置与调试安装此 Extension 后在 VS Code 设置中搜索claudeCode.acpServerUrl设为http://localhost:3000打开一个.py文件输入def按CtrlSpace观察 Output 面板中Claude Code ACP日志若看到Received response for code/completion说明通道打通。实操心得VS Code 的documentSelector必须精确匹配语言 ID。python正确Python或py会失败。语言 ID 查看方式打开文件 → 右下角点击语言模式 → “Configure File Association for .py” → 查看括号内名称。4.2 JetBrains 接入用 Plugin SDK 实现无缝集成JetBrains 平台IntelliJ IDEA、PyCharm接入 ACP 需要编写 Java/Kotlin Plugin但其优势在于可深度集成到编辑器 UI如右键菜单、状态栏图标。核心是继承JsonRpcServer并实现CodeCompletionProvider。Step 1创建 Plugin 项目用 IntelliJ Platform Plugin Template 创建项目build.gradle.kts添加依赖dependencies { implementation(com.jetbrains.intellij.java:java-gradle:232.9921.48) implementation(org.jetbrains.intellij.deps:json-rpc-server:1.0) // JetBrains 官方 JSON-RPC 库 }Step 2实现 ACP Clientclass AcpClient(private val serverUrl: String) { private val httpClient HttpClient.newBuilder().build() suspend fun completion(context: String, prompt: String): CompletionResponse { val request mapOf( jsonrpc to 2.0, id to 1, method to code/completion, params to mapOf( version to 1.0, context to mapOf(content to context, position to mapOf(line to 0, character to 0)), prompt to prompt ) ) val body HttpRequest.BodyPublishers.ofString(Json.encodeToString(request)) val req HttpRequest.newBuilder(URI.create(serverUrl)) .header(Content-Type, application/json) .POST(body) .build() val resp httpClient.send(req, HttpResponse.BodyHandlers.ofString()) return Json.decodeFromStringCompletionResponse(resp.body()) } }Step 3注入到 Code Completionclass AcpCompletionContributor : CompletionContributor() { override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) { val editor parameters.editor ?: return val context editor.document.text // 获取当前文件内容 val prompt getPromptAtCaret(editor) // 自定义函数获取光标前文本 // 异步调用 ACP Server ApplicationManager.getApplication().executeOnPooledThread { val acpClient AcpClient(http://localhost:3000) val response runBlocking { acpClient.completion(context, prompt) } ApplicationManager.getApplication().invokeLater { response.choices.forEach { choice - result.addElement(LookupElementBuilder.create(choice.text)) } } } } }注册方式在plugin.xml中添加extensions defaultExtensionNscom.intellij completion.contributor languagePYTHON implementationClasscom.example.AcpCompletionContributor/ /extensions注意事项JetBrains 的executeOnPooledThread不能直接更新 UI必须用invokeLater切回 EDT 线程。否则会抛java.lang.IllegalStateException: Must be invoked on EDT。这是 JetBrains 平台铁律踩坑无数。4.3 Zed 接入Rust 实现的极致轻量 ClientZed 编辑器由 Atom 团队前成员开发以 Rust 编写其 ACP 接入最具参考价值——展示了如何用 200 行代码实现高性能 Client。Zed 的设计哲学是“协议即 API”所有 AI 能力都通过language_server配置驱动。Step 1修改settings.jsonZed 的配置中心化管理无需写插件。在~/.config/zed/settings.json中添加{ languages: { Python: { language_servers: [ { name: claude-code-acp, command: cargo, args: [run, --bin, acp-client], initialization_options: { server_url: http://localhost:3000 } } ] } } }Step 2编写acp-clientBinaryRustCargo.toml[dependencies] jsonrpsee { version 0.22, features [http-client] } tokio { version 1.0, features [full] } serde { version 1.0, features [derive] }src/main.rsuse jsonrpsee::http_client::HttpClientBuilder; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct AcpParams { version: String, context: Context, prompt: String, } #[derive(Serialize, Deserialize)] struct Context { content: String, position: Position, } #[derive(Serialize, Deserialize)] struct Position { line: u32, character: u32, } #[derive(Serialize, Deserialize)] struct AcpResponse { choices: VecChoice, } #[derive(Serialize, Deserialize)] struct Choice { text: String, reasoning: String, logprobs: Vecf32, } #[tokio::main] async fn main() - Result(), Boxdyn std::error::Error { let client HttpClientBuilder::default().build(http://localhost:3000)?; let params AcpParams { version: 1.0.to_string(), context: Context { content: def hello():.to_string(), position: Position { line: 0, character: 12 }, }, prompt: .to_string(), }; let response: AcpResponse client.request(code/completion, params).await?; println!({}, response.choices[0].text); Ok(()) }Step 3编译与部署cargo build --release # 将 target/release/acp-client 复制到 PATH sudo cp target/release/acp-client /usr/local/bin/Zed 启动时会自动调用此 binary建立 JSON-RPC 连接。其优势在于Rust 的零成本抽象让 ACP Client 内存占用低于 5MB启动时间 100ms真正实现“无感接入”。实操心得Zed 的initialization_options会作为params的一部分传给 Server因此你的 Server 必须能解析params.server_url字段。这是 Zed 特有的扩展机制VS Code 和 JetBrains 不支持。5. 常见问题与排查技巧实录一线踩坑经验总结5.1 连接失败类问题90% 出在 HTTP Header现象可能原因排查命令解决方案VS Code 输出Failed to connect to serverServer 未监听application/vscode-jsonrpcContent-Typecurl -H Content-Type: application/json http://localhost:3000 -d {}修改 Server 中间件添加app.use(express.json({ type: application/vscode-jsonrpc }))JetBrains 报错Connection refusedServer 启动端口被防火墙拦截telnet localhost 3000检查iptables -L开放端口或改用127.0.0.1:3000Zed 无响应日志空白acp-clientbinary 权限不足ls -l $(which acp-client)chmod x $(which acp-client)提示所有编辑器 Client 都会先发一个{jsonrpc:2.0,id:0,method:initialize,params:{}}探针请求。用nc -l 3000监听端口可捕获原始请求确认 Client 是否真的发出了数据。5.2 响应异常类问题字段缺失与格式错位现象根本原因日志特征修复要点VS Code 补全项显示为空白result.choices数组为空或text字段缺失Output 面板显示No completions providedServer 必须返回choices: [{ text: valid code }]不可为[]或{}JetBrains 右键“Explain”无反应code/explain方法未在 Server 中实现Event Log 显示Method not found: code/explain检查switch (method)是否遗漏case code/explain:分支Zed 中文乱码显示Server 响应未声明 UTF-8 编码curl -I http://localhost:3000返回Content-Type: application/json修改响应头res.setHeader(Content-Type, application/json; charsetutf-8)5.3 性能瓶颈类问题超时与阻塞| 现象 | 数据指标 | 根本原因 | 优化方案 | |--------
返回列表