对 MCP 的深入剖析:一次批判性的审视
对 MCP 的深入剖析:一次批判性的审视
-
* [TL;DR](https://raz.sh/blog/<#toc_0>)
"MCP 是一个开放协议,用于标准化应用程序如何为 LLM 提供上下文。可以将 MCP 视为 AI 应用程序的 USB-C 端口。正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。" ― Anthropic
TL;DR
我希望这最终能证明是我自己的技术问题,并希望我遗漏了什么。 在过去的一个月里,MCP (Model Context Protocol) 非常火爆,它可以使 LLM 成为代理并与世界互动。这个想法很简单:让我们标准化一个 API,以便 LLM/Agents 与世界互动以及如何告知 LLM/Agent 关于它的信息。 事情进展非常迅速,IBM 最近发布了他们自己的与 MCP “正交的标准”,称为 Agent Communication Protocol (ACP),紧随其后的是 Google 宣布的 Agent2Agent (A2A)。 MCP 服务器和客户端每天都在构建和发布,可以在 mcp.so 和 pulsemcp.com 等网站上找到。 然而,我惊讶于明显缺乏成熟的工程实践。所有主要的参与者都花费数十亿美元来训练和调整他们的模型,然后转过身来,据我所知,让实习生编写文档,提供低于标准的 SDK,并且在实施指导方面几乎没有提供任何内容。 这种趋势似乎在 MCP 中仍在继续,导致了一些非常奇怪的设计决策、糟糕的文档,甚至更糟糕的是实际协议的规范。 我的结论是,整个 HTTP 传输建议的设置(SSE+HTTP 和可流式 HTTP)应该被抛弃,并替换为模仿 stdio 的东西...... WebSockets。
背景
大约三周前,我决定加入 MCP 的行列,尝试一下看看它如何在我们的环境中使用。我非常想在开始使用抽象之前了解事物在底层是如何实际运作的人。这里我们有一个新的协议,它可以通过不同的传输方式工作——多么令人兴奋! Anthropic 是 MCP 标准化工作的幕后公司,而且 MCP 似乎是 Anthropic 的 CEO 认为大多数代码将在一年左右的时间内由 LLM 编写的主要原因之一。基于使用它的感觉,对编码工具的押注似乎是标准化工作的指导原则。
协议
简而言之,它是一个具有预定义方法/端点的 JSON-RPC 协议,旨在与 LLM 结合使用。 这不是这篇文章的重点,但协议本身也有一些值得批评的地方。
传输
与 2005 年后的许多应用程序一样,它们据称是“本地优先的”(具有讽刺意味),并且 MCP 似乎非常符合这种情况。 查看传输协议,您可以了解他们的想法——如果他们的目的是为您的笔记本电脑上的编码构建 LLM 工具。 他们可能正在查看本地 IDE(或更实际的 Cursor 或 Windsurf),以及如何让 LLM 与本地文件系统、数据库、编辑器、语言服务器等进行交互。 基本上有两种主要的传输协议(或者三种):
- stdio
- “通过 HTTP 进行的一些操作,Web 似乎是我们可能应该支持的东西。”
Stdio
使用 stdio 本质上意味着启动本地 MCP 服务器,将服务器的 stdout
和 stdin
管道连接到客户端,然后开始发送 JSON,并使用 stderr
进行日志记录。 它有点打破了使用这些流进行双向通信的 Unix/Linux 管道范例。 当需要双向通信时,我们通常会使用套接字、unix 套接字甚至网络套接字。
然而,它简单易懂,可以在所有操作系统中开箱即用,无需处理套接字等等。 因此,即使有一些批评,我也可以理解。
HTTP+SSE / 可流式 HTTP
HTTP 传输是另一回事。 同一个错误有两个版本:HTTP+SSE(服务器发送事件)传输,它正在被使用带有 SSE 的 REST 语义的“可流式 HTTP”(一个自创的术语)所取代。 但最重要的是,还有很多额外的混乱和角落情况。 它可以总结为:“由于我们喜欢 SSE 进行 LLM 流式传输,因此我们不使用 WebSockets。 相反,我们实际上是在 SSE 之上实现 WebSockets,并将其称为“可流式 HTTP”,以使人们认为这是一种可以接受/已知的方式。” 他们在 modelcontextprotocol/pull/206 中讨论了 WebSockets 的问题(以及使用 Streamable HTTP 的原因),进行了一些非常奇怪的扭曲和稻草人论证来不使用 WebSockets。 至少线程中的另一个人似乎同意我的观点:modelcontextprotocol/pull/206#issuecomment-2766559523。
步入疯狂
我开始在 Golang 中实现一个 MCP 服务器。 没有官方的 Go SDK,我想了解该协议。 事实证明,这对心理健康来说是个错误......
警告信号...
查看 https://modelcontextprotocol.io,文档写得很差(所有 LLM 供应商似乎都在进行编写令人困惑的文档的内部竞争)。 该规范忽略或忽略了协议的重要方面,并且没有提供对话流程的示例。 事实上,整个网站似乎不是为了阅读标准而设计的; 相反,它会将您推向有关如何实现其 SDK 的教程。 所有示例服务器均以 Python 或 JavaScript 实现,目的是让您使用 stdio 在本地下载并运行它们。 对于您希望在其他人的计算机上运行的任何东西,Python 和 JavaScript 可能是最糟糕的语言选择之一。 作者似乎意识到了这一点,因为所有示例都以 Docker 容器的形式提供。
老实说……你上次运行
pip install
并且没有陷入依赖地狱是什么时候? 我认为 AI 领域的人只真正了解 Python,并且“好吧,它在我的计算机上工作”的方法仍然被认为是可接受的,我是否自命不凡/妄加评判? 任何尝试从 Hugging Face 运行任何东西的人都应该清楚地看到这一点。 如果您想在本地运行 MCP,您难道不希望使用 Rust、Go 甚至基于 VM 的选项(例如 Java 或 C#)等可移植语言吗?
问题所在
当我开始实现该协议时,我立即感到必须对其进行逆向工程。 SSE 部分的重要方面在文档中缺失,并且似乎还没有人实现“可流式 HTTP”; 甚至他们自己的工具(如 npx @modelcontextprotocol/inspector@latest
)也没有。(公平地说,这可能是我自己的技术问题,拉取了错误的版本,因为几周后我再次检查时它已经可用。您还可以在 inspect.mcp.garden 找到版本,这可能更方便。)
一旦掌握了架构,您很快就会意识到实现 MCP 服务器或客户端可能是一项巨大的工作。 问题在于 SSE/Streamable HTTP 实现试图像套接字一样工作,模拟 stdio,但实际上不是,并且试图 一次性完成所有事情。
HTTP+SSE 模式
modelcontextprotocol.io/specification/2024-11-05/basic/transports
在 HTTP+SSE 模式 中,为了实现全双工,客户端建立一个到(例如)GET /sse
的 SSE 会话以进行读取。 第一次读取提供了一个可以发布写入的 URL。 然后,客户端继续使用给定的端点进行写入,例如,对 POST /a-endpoint?session-id=1234
的请求。 服务器返回 202 Accepted,没有正文,并且请求的响应应该从 /sse
上预先存在的开放 SSE 连接中读取。
“可流式 HTTP” 模式
modelcontextprotocol.io/specification/2025-03-26/basic/transports
在 “可流式 HTTP” 模式 中,他们意识到,与其在第一个请求中提供一个新的端点,不如使用 HTTP 标头作为会话 ID,并使用 REST 语义作为端点。 例如,GET
或 POST /mcp
可以打开一个 SSE 会话并返回一个 mcp-session-id=1234
HTTP 标头。 为了发送数据,客户端向 POST /mcp
发出请求,并添加 HTTP 标头 mcp-session-id=1234
。 响应可能:
- 打开一个新的 SSE 流并发布回复
- 返回 200,回复在正文中
- 返回 202,表示回复将被写入到任何预先存在的 SSE 流之一
要结束会话,客户端可以发送也可以不发送带有标头 mcp-session-id=1234
的 DELETE /mcp
。 服务器必须维护状态,并且没有明确的方法知道客户端何时放弃会话,除非客户端很好地正确结束了它。
这对 SSE 模式意味着什么?
这是一个如此有问题的设计,我不知道从哪里开始。 虽然 SSE 模式的一些关键功能没有记录,但一旦您对其进行逆向工程,它就相当简单了。 但这仍然给服务器实现带来了巨大且不必要的负担,服务器实现需要“加入”跨调用的连接。 做任何实际的事情几乎都会迫使您使用消息队列来回复任何请求。 例如,以任何冗余方式运行服务器将意味着 SSE 流可能来自一个服务器到客户端,而请求被发送到完全不同的服务器。
这对 “可流式 HTTP” 意味着什么?
可流式 HTTP 方法将其提升到另一个层次,带来了一系列安全问题和模糊的控制流程。 虽然保留了 SSE 模式的所有不良部分,但 Streamable HTTP 似乎是 SSE 模式之上的一系列混乱的超集。 就实施而言,我只是触及了表面,但从我在文档中了解到的... 可以通过 3 种方式创建一个新会话:
- 一个空的
GET
请求 - 一个空的
POST
请求 - 一个包含 RPC 调用的
POST
请求
可以通过 4 种不同的方式打开 SSE:
- 用于初始化的
GET
- 用于加入早期会话的
GET
- 用于初始化会话的
POST
- 包含请求并使用 SSE 回答的
POST
可以通过 3 种不同的方式回复一个请求:
- 作为对带有 RPC 调用的
POST
的 HTTP 响应 - 作为对作为
POST
RPC 调用的响应而打开的 SSE 中的事件 - 作为对在较早时间打开的任何 SSE 的事件
一般影响
凭借其发起会话、打开 SSE 连接和响应请求的多种方式,这引入了显著的复杂性。 这种复杂性具有以下几个一般影响:
- 增加的复杂性:做同一件事(会话创建、SSE 打开、响应传递)的多种方式增加了开发人员的认知负荷。 理解、调试和维护代码变得更加困难。
- 潜在的不一致性:通过各种方式实现相同的结果,不同服务器和客户端之间存在更高风险的不一致实现。 这可能导致互操作性问题和意外行为。 客户端和服务器只实现他们认为必要的部分。
- 可伸缩性问题:虽然可流式 HTTP 旨在提高效率,但以慈善的解释,这种复杂性将引入需要克服的可伸缩性瓶颈。 服务器可能难以管理大量机器上的各种连接状态、响应机制。
安全影响
可流式 HTTP 的“灵活性”引入了几个安全问题,这里只是一些:
- 状态管理漏洞:跨不同连接类型(HTTP 和 SSE)管理会话状态非常复杂。 这可能导致诸如会话劫持、重放攻击或 DoS 攻击等漏洞,方法是在服务器上创建需要管理和保留的状态,等待会话恢复。
- 增加的攻击面:用于会话创建和 SSE 连接的多个入口点扩大了攻击面。 每个入口点都代表攻击者可能利用的潜在漏洞。
- 混淆和模糊:发起会话和传递响应的多种方式可用于模糊恶意活动。
授权
该协议的最新版本包含一些关于如何进行授权的非常主观的要求。 modelcontextprotocol.io/specification/2025-03-26/basic/authorization
- 使用基于 HTTP 的传输的实现应该符合此规范。
- 使用 STDIO 传输的实现不应遵循此规范,而应从环境中检索凭据。
我的理解是,对于 stdio,随便做什么。 对于 HTTP,您最好跳过这些 OAuth2 步骤。 如果我使用 HTTP 作为传输方式,为什么我需要实现 OAuth2,而 API 密钥对于 stdio 来说就足够了?
应该怎么做
我不知道,只是对此感到有点悲伤... 似乎整个行业都在尿裤子——现在感觉很棒,但以后会很难处理。 只有一个 JSON RPC 协议,并且 Stdio 显然是首选的传输协议。 然后,我们应该尝试使 HTTP 传输尽可能地像 Stdio,并且只有在我们真正、真正需要的时候才真正偏离。
- 在 Stdio 中,我们有环境变量,在 HTTP 中,我们有 HTTP 标头
- 在 Stdio 中,我们有 类似套接字 的行为,带有输入和输出流,在 HTTP 中,我们有 WebSockets
真的就这些。 我们应该能够在 WebSockets 上完成与在 Stdio 上完成的相同的事情。 WebSockets 是通过 HTTP 进行传输的合适选择。 我们可以摆脱复杂的跨服务器会话状态管理。 我们可以摆脱大量的角落情况,等等。 当然,有些事情(例如授权)在某些情况下可能会有点复杂(并且在某些情况下更容易); 一些防火墙可能会阻止 WebSockets; 小会话可能会有额外的开销; 恢复中断的会话可能更难。 但正如_他们_所说:
客户端和服务器可以实现额外的自定义传输机制以满足其特定需求。 该协议与传输无关,可以在支持双向消息交换的任何通信通道上实现 modelcontextprotocol.io/specification/2025-03-26/basic/transports#custom-transports 作为一个行业,我们应该针对最常见的用例进行优化,而不是针对角落用例进行优化。
旁注:替代方案和补充
如上所述,似乎出现了更多的协议。 MCP 实际上是“一种向 LLM 公开 API 的协议”(它可以创建一个代理)。 来自 IBM 和 Google 的最新协议(ACP 和 A2A)实际上是“一种向 LLM 公开代理的协议”(它可以创建代理的代理)。 浏览 A2A 规范,似乎对它们的需求非常有限。 尽管他们声称是正交的,但 A2A 中的大多数事情都可以通过 MCP 按原样或通过少量添加来完成。 归结为两个完整的协议,它们也可以是 MCP 服务器中的工具。 甚至 IBM 似乎也承认他们的协议不是真正必要的:
“代理可以被视为 MCP 资源,并进一步作为 MCP 工具调用。 这种 ACP 代理的视图允许 MCP 客户端发现和运行 ACP 代理......” ― IBM / agentcommunicationprotocol.dev/ecosystem/mcp-adapter 我的最初感觉是,ACP 协议主要像是 IBM 试图推广他们的“代理构建工具”BeeAI A** 协议带给我们的都是一个理智的传输层和一种发现代理的方式。