MCP、ACP与LSP:三大AI编程协议的分层解析与工程实践
2026/9/12 7:46:04 网站建设 项目流程

最近在折腾 AI 编程工具链时,我被一个非常现实的问题卡住:同一套编辑器里,LSP 在背后维护代码补全和诊断,Agent 通过 MCP 去查询数据库和设计稿,而人和 Agent 之间的多轮协作又需要一套独立的会话协议。MCP、ACP、LSP 这三个缩写频繁出现在同一篇技术文档里,但很多人把它们的边界画错了。有人以为 ACP 是 MCP 的升级版,有人觉得 LSP 早晚被 MCP 取代。这篇文章把我最近梳理的架构理解、三个协议的详细拆解,以及在实际集成中踩过的坑整理出来。适合正在做 AI 编程工具集成的开发者,也适合想彻底搞懂“这三个协议到底谁管哪一块”的读者。

1. 为什么突然冒出三个协议:背景与乱源

1.1 从“为每个编辑器写插件”到“一次接入、处处可用”

如果你经历过早期编辑器生态,应该对那种“每个编辑器都要单独写插件”的噩梦有印象。TypeScript 团队同时维护 VS Code、Sublime、Atom 的插件,稍有不慎就出现功能不同步;Java 语言支持在 Eclipse、IntelliJ、NetBeans 里各自为政,没有任何复用的可能。2016 年微软推出 LSP,本质原因就是大家受够了这种碎片化:协议把“编辑器与语言服务器之间的通信”标准化,语言实现方只要提供一个 language server,就能让所有支持 LSP 的编辑器获得补全、跳转、诊断等能力。这个模式被验证得非常成功,到今天几乎每个现代编辑器都在使用 LSP。

AI 时代到来后,历史上的一幕重新上演了,只是舞台从“语言服务”换到了“模型能力扩展”。Anthropic 发现,每个 Agent 或 AI 应用想调用外部工具时,都要跟数据库、浏览器、Figma、蓝湖这类平台做针对性适配;每接入一个新工具,就要重写一遍集成代码。于是他们提出了 MCP,目标是复制 LSP 的成功路径:一次接入,处处可用。而 Zed 这类 IDE 厂商在集成 Claude Code、Codex、Gemini CLI 等不同 Agent 时,也发现“编辑器与 Agent 的关系”不能靠各自的私有 API 维持,于是推出了 ACP。所以三个协议不是同一时间凭空冒出来的,而是在各自的真实痛点中先后长出来的。

1.2 三个协议解决的是完全不同的三层问题

我把它们理解为三个不同通信层面的标准,这可能是全文最重要的一句话:LSP 管“编辑器与语言引擎之间的语义通信”,MCP 管“AI 应用与外部工具/数据源之间的能力通信”,ACP 管“客户端与 Agent 之间的会话协作通信”。

用类比来说,LSP 是汽车发动机与变速箱之间的标准化连接方式,MCP 是车载充电口与外部设备之间的统一接口,ACP 是驾驶员与自动驾驶系统之间的方向盘仪表盘规范。你没法说“有了充电口就不需要发动机接口”,也没法说“有了方向盘就不需要轮胎”。那些纠结“MCP 会不会取代 LSP”“ACP 是不是 MCP 升级版”的讨论,本质上是在不同维度上比较,结论自然是一团乱麻。

这三层协议还有一个共同点:都基于 JSON-RPC 2.0,都采用客户端/服务器模型,都强调 capability 协商。这说明它们共享同一套工程哲学,只是在抽象层级上做了不同分工。理解了这个背景,再去看每个协议的细节,思路会顺畅很多。

2. MCP:给大模型插上“手”和“眼”的标准方案

2.1 Host / Client / Server 架构

MCP 的架构非常清晰,三层角色各司其职。Host 是承载 AI 应用的主体,比如 Claude Desktop、VS Code 里的 AI 插件、Cline 这类工具,它负责管理用户交互和模型调用;Host 内部会创建多个 MCP Client,每个 Client 对应一个外部的 MCP Server 连接;MCP Server 则是一个独立进程或远程服务,用来暴露工具、资源和提示词。

mcp server 本质上是运行起来的一个 JSON-RPC 服务,本地场景下通常以 stdio 子进程方式启动,远程场景则通过 Streamable HTTP 暴露。对于开发者来说,MCP 最大的价值在于“统一暴露接口”:你不需要为每个 AI 客户端重新写一套工具适配层,只要实现一次 MCP Server,任何支持 MCP 的 Host 都能直接使用你的能力。这也是为什么图生代码、数据库查询、设计稿转代码等场景都在快速普及 MCP server。

2.2 三大原语:Tools、Resources、Prompts

MCP 的核心抽象只有三个:Tools、Resources、Prompts。

Tools 是可执行动作,对应模型可以自主调用的函数,比如“查询订单状态”“执行 SQL”“创建 Jira 任务”。工具的输入输出都建议使用结构化 JSON,模型才能稳定地根据 schema 生成调用参数。Resources 是只读数据源,比如一个文件的路径、一个数据库表的 schema、一个 API 的文档片段,通常通过类似 URI 的标识符访问,用于给模型提供上下文。Prompts 是预定义的提示模板,用来标准化一些重复性流程,比如“按公司规范审查 PR”“生成单元测试”这类固定任务。

理解这三种原语的区别很重要。如果能力是需要模型主动触发的动作,做成 Tools;如果只是给模型补充背景信息,做成 Resources;如果要约束用户和模型之间的固定交互流程,做成 Prompts。把动作和数据混为一谈,是刚开始设计 MCP server 时最容易犯的错误。

2.3 MCP 工具调用的完整链路与最小实现

我以一个最小例子说明整条链路。用 Python 的 FastMCP 库写一个查询订单状态的工具:

# order_server.py from mcp.server.fastmcp import FastMCP mcp = FastMCP("order-assistant") @mcp.tool() def get_order_status(order_id: str) -> dict: """根据订单号查询最新物流状态""" # 这里替换为真实的接口调用 return { "order_id": order_id, "status": "shipped", "carrier": "yuantong", "tracking_number": "YT20240220012", } if __name__ == "__main__": mcp.run(transport="stdio")

然后在 Claude Desktop 的配置文件里注册这个服务:

{ "mcpServers": { "order-assistant": { "command": "python", "args": ["/path/to/order_server.py"] } } }

当用户在对话框里问“订单 2024020 发货了吗”,模型会先判断这需要调用工具,于是返回一个 tool_call 意图;Host 内部的 MCP Client 收到这个意图后,拼装成 JSON-RPC 请求发送给 server:

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "get_order_status", "arguments": { "order_id": "2024020" } } }

server 执行完函数,把结构化结果返回给 Host,Host 再把结果交给模型进行一次带上下文的推理,最终模型生成自然语言回复。整个过程对用户而言就是“模型突然会查数据了”,但背后是 JSON-RPC 消息在 Host 和 Server 之间做了一次准确的往返。

这里有个实战细节:工具返回结果尽量保持结构化,不要返回“你好,订单已发货”这样的自然语言。因为模型还需要基于结果做二次推理,结构化数据能让它保留更多信息,比如物流单号可以继续用于下一步查询。

2.4 为什么 Figma、蓝湖、Blender 都在做官方 MCP 插件

从最近的热度看,figma mcp、蓝湖mcp、mastergo mcp、blender mcp 的搜索量涨得非常快。原因很直接:设计工具和三维工具的数据是 AI 编程最有价值的上下文,但传统方式根本传不干净。

以前开发者想把 Figma 设计稿转成代码,通常是把设计稿截图发给大模型“看”。问题是截图丢失了图层关系、样式变量、自动布局信息,模型只能靠猜。Figma 官方 MCP server 则能把图层树、颜色、字号、间距、组件属性以结构化 JSON 暴露给 Agent,模型拿到的不再是一张平面图,而是一份可计算的“设计数据”。蓝湖和 MasterGo 也在做同样的事,它们都在向 MCP 生态靠拢,因为只有这样才能被 AI 编程工具链原生化地消费。这其实验证了一个趋势:以后不是 AI 去适配每个平台,而是平台都主动长出 MCP 接口,等着被 AI 调用。

3. ACP:从“人机会话”出发的 Agent 通信新规范

3.1 Zed 为什么还要再定义一个协议

MCP 已经把“Agent 调工具”这件事标准化了,但 Zed 在集成不同 Agent 时发现了一个 MCP 没有覆盖的空白:没有一个标准协议来描述“用户和 Agent 之间那种长时间、多轮、可打断、可审批的协作关系”。

在真实使用中,编辑器里的 Agent 不是简单的“调用一次就返回结果”。它会向用户提问“你希望怎么处理这个报错”、会请求权限“我准备修改 src/auth.ts,是否允许”,会持续汇报任务进度。这些语义如果每个 Agent 用自己私有 API 实现,编辑器就要为 Claude Code、Codex、Gemini CLI 分别写适配器,又回到了碎片化老路。因此 Zed 在 2025 年提出了 Agent Client Protocol,核心定位是“客户端与 Agent 之间的会话级协议”,注意是 Agent Client Protocol,不是 Agent Communication Protocol,别跟 IBM 早期那套概念混淆了。

3.2 ACP 的状态模型:Session、Task、Turn、Signal

ACP 基于 JSON-RPC 2.0,通过 stdio 在客户端和 Agent 进程之间通信,但它比 MCP 多了一套完整的状态机。

Session 是一次完整的会话生命周期,从用户在编辑器里启动 Agent 开始,到关闭会话结束。一个 Session 里可以包含多个 Task,Task 是一个可追踪的工作单元,比如“实现登录功能”“修复所有 lint 错误”。Task 内部由若干 Turn 组成,一个 Turn 就是一次交互往返:Agent 输出一段消息或行动,用户给予反馈,比如批准权限、补充说明、中止。Signal 则是异步信号机制,用来传递诸如 task 进度更新、任务完成、请求人工介入、客户端取消任务等事件。

这套模型解决了一个很实际的问题:编辑器可以真正“理解”Agent 现在处于什么状态。它知道 Agent 是在思考、在等你授权,还是任务已经结束,从而可以渲染出对应的人机交互界面。相比之下,MCP 的一次 tools/call 是无状态的,调用完就结束了,不关心整个任务生命周期。

3.3 ACP 和 MCP 不是替代关系,而是上下层关系

我见过最普遍的误解是把 ACP 当成 MCP 的“下一代版本”,但实际上它们服务的层级完全不同:MCP 是 Agent 向下接入工具的协议,ACP 是 Agent 向上承接客户端的协议。可以这样理解:ACP 管“人怎么和 Agent 协作”,MCP 管“Agent 怎么和世界互动”。

一个典型场景是在 Zed 里使用 Codex。Codex Agent 与 Zed 编辑器之间的连接走 ACP,编辑器才能展示“思考中”“等待授权”“已完成”这些状态。而在处理任务过程中,如果 Codex 需要读取公司内部设计稿,它自己又作为 MCP Client 去连接 Figma MCP server。一个 Agent 既是 ACP 的 Server,又是 MCP 的 Client,这种双重身份一点也不别扭,反而恰好说明两个协议各管一层、互不冲突。

4. LSP:八年验证过的“语言服务老前辈”

4.1 LSP 仍然是 AI 编程的底层基石

很多人一聊 AI 就觉得 LSP 是过时技术,这是最大的误判。我实测下来的结论是:AI 编程插件不但没有削弱 LSP 的价值,反而把它变成了更重要的基础设施。原因很简单,模型生成的代码不一定符合类型约束,编辑器里那些红色波浪线,绝大多数不是模型自己判断出来的,而是背后的 language server 在做类型检查和语义诊断。

以 VS Code 里的 GitHub Copilot 为例,它呈现给用户的是生成代码和错误提示;但底层它依然依赖 TSServer、Pyright 这类 LSP language server 提供符号表、类型信息和诊断结果。模型在生成代码时,也需要从 LSP 获取“当前文件导入了哪些符号”“这个变量作用域是什么”,这些上下文越准确,生成代码的命中率越高。所以 LSP 不是被 MCP 取代的旧东西,而是 AI 编程的“静态校验层”和“语义上下文层”。

4.2 LSP 的通信模型与 capability 协商机制

LSP 同样基于 JSON-RPC 2.0,但它的工作方式更能看出“协商优先”的设计哲学。客户端连接后,第一条消息必须是 initialize 请求,服务端返回自己支持的能力列表,包括是否支持 hover、补全、跳转定义、代码操作等。客户端再根据这份能力清单决定发出哪些请求,避免双方版本不一致导致事故。

之后的常规流程是:客户端通过 textDocument/didOpen、didChange、didSave、didClose 通知语言服务当前文档状态,语言服务则通过 textDocument/completion 响应补全,通过 textDocument/publishDiagnostics 主动推送诊断结果。值得注意的是,LSP 最近新增的 inlayHint、inlineValue 等能力,正是为了给内联 AI 补全提供更细粒度的语义落点。协议在持续演进,并不是静止的老古董。

4.3 LSP 给 MCP 和 ACP 铺好的路

回顾技术史会发现,LSP 最大的贡献不只是它本身,而是它验证了一个有效的工程模式:用“客户端-服务器 + 能力协商 + JSON-RPC”来解开工具链绑定。LSP 出现之前,没人相信一个语言服务可以无缝跑遍所有编辑器;LSP 证明之后,MCP 和 ACP 在设计时几乎都沿用了这套成熟范式。

所以如果你想快速理解 MCP 或 ACP 的报文结构,最好的学习方法其实是先去看 LSP 的报文。它们放在一起看,能明显感受到同一种设计语言在不同抽象层的复现:都是 client 发 initialize,server 回 capabilities,之后按各自领域的事件模型飘来飘去。理解了 LSP,等于拿到了一把打开后面两个协议的钥匙。

5. 三者的边界与协作:一张表说清定位差异

5.1 三个协议的主要对比

维度MCPACPLSP
全称Model Context ProtocolAgent Client ProtocolLanguage Server Protocol
提出方AnthropicZedMicrosoft
关注对象AI 应用与外部工具/数据源的连接客户端与 AI Agent 的会话协作编辑器与语言服务器的语义通信
核心抽象Tools / Resources / PromptsSession / Task / Turn / SignalDocument / Diagnostic / Completion
典型传输stdio、Streamable HTTPstdiostdio、TCP/IP 等
角色关系Host 内 Client ↔ Server客户端 ↔ Agent Server编辑器 Client ↔ Language Server
一句话定位让 Agent 能调用世界让人能管理 Agent让编辑器能读懂代码

这张表建议收藏。每次有人问“这三个到底啥关系”,直接甩这张表就行。

5.2 在同一个工作流里它们如何分工

光看协议定义还有点抽象,把它们放进同一段运行中的工作流会更清晰。假设我在 Zed 编辑器里让 Codex Agent 实现一个功能:

  1. 编辑器通过 LSP 获取当前文件的符号表、类型信息和已有诊断,这些数据会作为该任务的初始上下文;
  2. 我通过 ACP 启动一个新的 Task,Codex 开始处理;
  3. Agent 规划时发现需要查数据库的表结构,于是它作为 MCP Client 连接数据库 MCP server,拿到 schema;
  4. 准备改文件前,Agent 通过 ACP 向我发送权限请求,我点击允许;
  5. 代码写入后,编辑器里的 LSP 立刻给出类型错误或 lint 提示;
  6. Agent 感知到这些诊断,可能再次通过 MCP 调工具或直接修正,最终通过 ACP 报告任务完成。

在这个流程里,三个协议分别在“语义层”“工具层”“会话层”工作,缺一个都会造成明显的体验断档。

5.3 常见的错误认知和它们错在哪

最典型的三个误区是:MCP 会取代 LSP、ACP 是 MCP 的升级替代、Agent 时代不需要 LSP。三个结论都经不起推敲。MCP 重在工具与上下文接入,LSP 重在代码语义分析,两者解决的问题不同,即便 MCP 可以通过 resource 读取文件内容,也替代不了语言服务器对类型、作用域、符号引用的精确建模;ACP 管理的是人机会话生命周期,MCP 管理的是外部能力接入,层级不同,谈不上替代;Agent 生成完代码后比任何时候都更需要静态校验和语义索引,所以 LSP 在现代 AI IDE 里的角色不是被弱化,而是被放大了。

6. 工程视角:如何在真实项目里理解和应用三大协议

6.1 按你的角色决定学习优先级

很多开发者问我,这三个协议时间有限,到底先学哪个。我的回答是看你的工程角色。

如果你在做 AI 应用或业务 Agent,最该先学 MCP。因为你要解决的核心问题是“让模型拿到企业内部数据、调通业务工具”,MCP 的工具设计、资源管理、鉴权与超时控制,直接决定你的 Agent 能力上限。如果你在做 IDE 插件、代码编辑器或 Agent 客户端产品,ACP 会是你绕不开的协议,因为能不能把 Agent 的思考过程、权限请求和任务进度清晰地呈现在界面上,依赖的就是这一层。如果你在搭语言工具链、做静态分析平台或自定义 linter,LSP 是基本功,你需要掌握如何开发、调试、分发一个 language server。

6.2 顺带说说“Agent Skill 和 MCP 有什么区别”

最近这个问题挺热门,顺手一起聊了。Agent Skill 可以理解为模型侧封装好的“技能包”,它通常是一组精心编写的提示词和可执行脚本,比如“按团队规范生成 commit message”“用 Jira 模板创建需求”。技能决定的是 Agent 自身如何思考和行动。而 MCP 提供的是外部工具和资源的统一入口。两者不是二选一的关系:Skill 更靠近模型内在能力,MCP 更靠近外部世界接入。一个 Agent 完全可以既携带内部 Skill,又连接多个 MCP server,二者在不同环节发挥不同作用。

6.3 从零搭建一个最小可用的 MCP 实例

如果你想用一个晚上跑通 MCP,我建议按下面步骤操作。先安装依赖:

pip install fastmcp

写一个最简单的 server(前面订单服务的代码就可以),然后找一个支持 MCP 的客户端。用 Claude Desktop 最省事,直接配置 JSON 即可;如果你在 VS Code 里用 Cline,也能在 MCP 配置页面注册。跑起来后,给模型一句“查询订单 2024020 状态”,观察调用日志。成功时会看到完整的 JSON-RPC 往返。

我推荐用 stdio 传输做本地开发,因为它没有网络鉴权、端口占用这些额外变量,排错链路最短。等本地稳定了,再考虑用 Streamable HTTP 把能力部署成远程服务,供多个客户端共享。远程部署时有一点要提前想清楚:MCP server 必须有身份验证机制,否则所有能访问该地址的 AI 应用都能调用你的工具,权限模型一定要在设计阶段就定好。

6.4 协议分层的设计哲学

最终落到工程实践上,我觉得最重要的一条纪律就是:不要在错误的层级上乱用力。我见过有人想把 MCP 的 business rule 通过 LSP 的诊断通道推到编辑器,结果类型错误和业务错误混在一起,无从排查;也有人试图用 MCP 的工具调用去管理 Agent 的连续会话,导致上下文丢失后任务直接断掉。正确做法是明确每一层该承载什么:LSP 承载代码语义相关的事件,MCP 承载工具与上下文相关的能力,ACP 承载会话与任务状态。这个边界一旦守住,系统扩展起来会非常舒服。

7. 集成实践:我在 MCP/ACP/LSP 落地时踩过的坑

7.1 MCP 初始化报错:重复 initialize 的灾难现场

热搜里有个很典型的报错:failed to initialize acp session. error: internal error: "already initialize"。我第一次看到类似错误时差点在配置文件里原地杜撰了一个根因,后来才发现问题出在重复初始化上。

MCP 和 ACP 的会话建立过程都有一个硬约束:客户端连上后第一消息必须是 initialize,server 返回能力后进入已初始化状态。如果你在客户端代码里不小心对同一个连接重复发送 initialize,server 就会直接拒绝并报类似错误。排查思路很简单:检查连接管理代码里是否遗漏了状态判断,确认每次新建连接只调用一次 initialize;如果用了某些封装好的 SDK,还要确认框架有没有自动初始化,避免和自己手动初始化成双份。

7.2 stdio 模式下的一系列潜规则

MCP 的 stdio 模式看着简单,踩坑点一点也不少。第一个坑是子进程工作目录。如果你的 MCP server 需要读取相对路径的配置文件,但启动它的客户端设置错了 cwd,你会很困惑地发现代码里读不到文件。解决方案是 server 启动时统一用绝对路径,或显式打印当前工作目录方便排查。

第二个坑是 JSON-RPC 的帧格式。很多人自研 MCP client 时想当然地直接传 JSON 字符串,结果无法通信。因为基于 stdio 的 JSON-RPC 使用 Content-Length 头进行消息分帧,格式大致是:

Content-Length: 123\r\n\r\n {"jsonrpc":"2.0","id":1,"method":"tools/list"}

少一个换行符都可能导致消息滞留。建议直接用官方 SDK 处理消息边界,别自己手写传输层。

第三个坑是工具名冲突。同一个 MCP server 里如果两个 tool 重名,能力协商阶段就会出现冲突,模型拿到两个重复名字的工具后行为会变得不可预测。命名时建议带上前缀,比如 order_get、order_update,既清晰又避免碰撞。

7.3 LSP 集成的三个隐蔽问题

LSP 看起来成熟,但集成时仍要留意几个隐蔽点。第一个是 capability 协商不一致,有的 language server 没有完整实现新版 LSP 特性,但客户端却默认对方支持,两者之间就会出现静默失败。稳妥做法是客户端在启动时记录 server 返回的 capabilities,并针对缺失特性做降级,而不是直接不展示相关 UI。

第二个问题是大型 monorepo 下的性能。语言服务器在全项目索引阶段可能吃掉几个 GB 内存,尤其是在同时打开多个 workspace 的时候。需要合理配置 maxNumberOfProblems、关闭不必要的诊断能力,也可以利用 workspace/diagnostic 这种批量拉取接口来减少消息风暴。第三个问题是多语言服务器并存时的文档所有权冲突。当一个文档同时被 Python 扩展和 AI 插件的某个语义服务监听时,两边都尝试发布诊断,会出现波浪线闪烁。解决方法是建立清晰的文档所有权规则,让同一个 document 只由一个 server 负责主要诊断,其他 server 只做补充。

7.4 ACP 会话与 MCP 工具调用之间的时序陷阱

最有意思也最隐蔽的坑,是 ACP 的会话和 MCP 工具调用在时间尺度上的矛盾。ACP 的任务模型认为 agent 应该在合理时间内完成一个 turn,编辑器 UI 也会根据 turn 状态显示进度;但如果这个 turn 内部的某个 MCP 工具调用耗时很长,比如去查一个数据仓库、渲染一次大设计图,就会超出客户端的等待阈值,导致整个 task 被误判为失败并中止。

我实际遇到过 agent 调一个数据仓库 MCP server,查询结果迟迟不返回,ACP 侧判断任务卡死直接取消,然后整个 task 被标记成 failed。事后看,问题出在工具设计上没有考虑耗时边界。后来我把慢工具改成“异步任务 + 进度查询”模式:MCP server 先返回一个 task_id,Agent 轮询进度,ACP 侧同时开启超时保护。这样既不阻塞会话,又能拿到最终结果。这个经验也适用于普通业务系统的长任务设计,只是放在协议层时容错成本更高,更要提前规划。

7.5 时刻保持边界感,别把层与层搅在一起

最后一个经验是关于人的。遇到问题先想清楚这是哪一层的责任,再去调那一层的代码。之前我在自研插件里为了让 Agent 业务校验结果直接显示在编辑器中,把数据通过 LSP 的诊断通道推送过去,结果用户把业务错误当成类型错误,反馈了好几个“假 bug”。后来我把业务校验结果改由 MCP 返回给模型、由 Agent 在 ACP 会话消息中自然呈现,才彻底解决。协议的边界本质上也是系统的边界,尊重边界,就是在给自己的项目减少隐性债务。

我在实际项目里最终形成的原则是:遇到集成问题,先判断问题出在“拿不到外部工具”,还是“无法管理多轮会话”,还是“代码语义能力弱”。定位对了层,再选对应的协议去解决,思路会清晰很多。这三个协议不是竞争对手,而是 AI 时代工具链不同环节的基础设施。理清它们,才能在正确的层级上做正确的设计。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询