“MCP 是一种开放协议,它规范了应用程序如何向 LLM 提供上下文信息。可以将 MCP 理解为 AI 应用的 USB-C 接口。正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 也提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。”

——人类学

太长不看

我希望这只是我的技术问题,希望是我遗漏了什么。

过去一个月,MCP(模型上下文协议)迅速走红,该协议旨在使LLM(生命周期模型)能够成为智能体并与世界互动。其理念很简单:为LLM/智能体制定一个标准化的API,用于与世界互动以及如何将信息传递给LLM/智能体。

事情发展得非常快,IBM 最近发布了他们自己的与 MCP 正交的标准,称为代理通信协议 (ACP),紧随其后的是 Google 宣布推出Agent2Agent (A2A)

MCP 服务器和客户端每天都在构建和发布,可以在mcp.sopulsemcp.com等网站上找到。

然而,令我震惊的是,成熟的工程实践似乎严重不足。所有主要厂商都花费数十亿美元用于训练和调优模型,但据我观察,他们却让实习生编写文档,提供的SDK质量低下,而且几乎没有提供任何实现方面的指导。

这种趋势似乎在 MCP 中延续了下来,导致了一些非常奇怪的设计决策、糟糕的文档,以及更糟糕的实际协议规范。

我的结论是,应该抛弃目前建议的 HTTP 传输设置(SSE+HTTP 和 Streamable HTTP),并用模仿标准输入输出 (stdio) 的 Websockets 来代替。

背景

大约三周前,我决定尝试一下MCP,看看它在我们自己的环境中能发挥怎样的作用。我这个人喜欢先弄明白底层原理,然后再去使用抽象概念。现在我们有了一个可以跨不同传输方式运行的新协议——真是太棒了!

Anthropic 是 MCP 标准化工作的幕后推手,而 MCP 似乎是 Anthropic CEO 认为一年左右大部分代码将由 LLM 编写的主要原因之一。尤其值得一提的是,基于使用体验来看,对编码工具的重视似乎是此次标准化工作的指导原则。

协议

简而言之,它是一个预定义方法/端点的 JSON-RPC 协议,旨在与 LLM 配合使用。这并非本文的重点,但该协议本身也存在一些值得商榷之处。

运输

与 2005 年之后的许多应用程序一样,它们标榜“本地优先”(颇具讽刺意味),而 MCP 似乎正是如此。看看传输协议,你就能明白他们的出发点——如果他们的目的是为笔记本电脑上的编码构建 LLM 工具。他们可能正在研究本地 IDE(或者更实际地说,是 Cursor 或 Windsurf),以及如何让 LLM 与本地文件系统、数据库、编辑器、语言服务器等等进行交互。

主要有两种(或三种)传输协议:

  1. 标准排版
  2. “通过HTTP协议传输数据,似乎是我们应该支持的某种技术。”

工作室

使用标准输入输出 (stdio) 本质上意味着启动一个本地 MCP 服务器,建立服务器到客户端的管道连接,并开始发送 JSON 数据用于stdout日志记录。这在某种程度上打破了 Unix/Linux 管道的范式,因为它使用这些流进行双向通信。当需要双向通信时,我们通常会使用套接字、Unix 套接字甚至网络套接字。stdin``stderr

然而,它简单易懂,在所有操作系统中都能开箱即用,无需处理套接字等等。所以即使有人批评它,我也能理解。

HTTP+SSE / Streamable HTTP

HTTP传输又是另一回事。同样的错误有两种版本:HTTP+SSE(服务器发送事件)传输,它正被“可流式HTTP”(一个自造术语)所取代,后者使用SSE的REST语义。但除此之外,它还带来了许多额外的混乱和特殊情况。

可以概括为:“由于我们喜欢使用 SSE 进行 LLM 流式传输,所以我们不使用 WebSocket。相反,我们实际上是在 SSE 之上实现了 WebSocket,并将其称为‘Streamable HTTP’,让人们认为这是一种公认/已知的实现方式。”

他们在 PR 中讨论了 WebSocket 的问题(以及 Streamable HTTP 的原因):modelcontextprotocol/pull/206,并提出了一些非常奇怪的论点和稻草人谬论,反对使用WebSocket。至少该讨论串中还有一位用户似乎同意我的观点:modelcontextprotocol/pull/206#issuecomment-2766559523

坠入疯狂

我尝试用 Go 语言实现一个 MCP 服务器。由于没有官方的 Go SDK,我想先了解一下协议。结果证明,这对我的心理健康来说是个错误……

警告信号……

查看https://modelcontextprotocol.io后发现,其文档编写得非常糟糕(所有 LLM 供应商似乎都在竞相编写令人费解的文档)。该规范对协议的重要方面一带而过,甚至完全忽略,并且没有提供任何会话流程示例。事实上,整个网站似乎并非旨在让用户阅读标准文档;相反,它引导用户去学习如何实现他们的 SDK。

所有示例服务器均使用 Python 或 JavaScript 实现,旨在供您下载后使用标准输入输出 (stdio) 在本地运行。Python 和 JavaScript 可能是在他人计算机上运行程序时最糟糕的语言选择之一。作者似乎也意识到了这一点,因为所有示例都以 Docker 容器的形式提供。

说实话……你上一次运行程序pip install而没有陷入依赖地狱是什么时候?

我这样想是不是太自以为是/太武断了:人工智能领域的人似乎只懂Python,而“在我电脑上运行正常”这种做法仍然被认为是可接受的?任何尝试过在Hugging Face上运行程序的人都应该对此深信不疑。

如果要在本地运行 MCP,难道不应该选择像 Rust、Go 这样的可移植语言,甚至是像 Java 或 C# 这样的基于虚拟机的选项吗?

问题

当我开始实现这个协议时,我立刻觉得必须对其进行逆向工程。SSE 部分的重要细节在文档中缺失,而且似乎还没有人实现过“可流式 HTTP”;甚至连他们自己的工具都没有npx @modelcontextprotocol/inspector@latest。(公平地说,这可能是我技术上的问题,拉取了错误的版本,因为几周后我再次检查时,正确的版本已经可用了。你也可以在 inspect.mcp.garden上找到该版本,这可能更方便。)

一旦你掌握了架构,就会很快意识到实现一个 MCP 服务器或客户端可能是一项巨大的工程。问题在于,SSE/Streamable HTTP 实现试图像套接字一样工作,模拟标准 I/O,但它本身并不是套接字,并且试图一次性完成所有事情

HTTP+SSE 模式

modelcontextprotocol.io/specification/2024-11-05/basic/transports

HTTP+SSE 模式下,为了实现全双工通信,客户端会建立一个 SSE 会话(例如,GET /sse用于读取操作)。首次读取操作会提供一个 URL,用于发布写入操作。然后,客户端会使用该 URL 进行写入操作,例如,向该 URL 发送请求 POST /a-endpoint?session-id=1234。服务器会返回一个不包含响应体的 202 Accepted 响应,客户端需要从之前在 URL 上建立的 SSE 连接中读取该请求的响应/sse

“可流式HTTP”模式

modelcontextprotocol.io/specification/2025-03-26/basic/transports

在**“可流式 HTTP”模式**下,他们意识到,与其在第一个请求中提供新的端点,不如使用 HTTP 标头来存储会话 ID,并使用 REST 语义来定义端点。例如,GET可以POST /mcp打开一个 SSE 会话并返回一个mcp-session-id=1234HTTP 标头。要发送数据,客户端向该 SSE 会话发送请求POST /mcp并添加 HTTP 标头 mcp-session-id=1234。响应可能如下:

  • 新建一个 SSE 流并发布回复
  • 返回 200 状态码,并在正文中显示回复内容。
  • 返回 202,表示回复将写入任何预先存在的 SSE 流之一。

要结束会话,客户端可以发送DELETE /mcp带有特定标头的会话结束请求,也可以不发送mcp-session-id=1234。服务器必须维护会话状态,但除非客户端以适当的方式结束会话,否则服务器无法清楚地知道客户端何时放弃了会话。

SSE模式有哪些影响?

这个设计问题太多,我都不知道该从何说起。

虽然 SSE 模式的一些关键特性没有文档说明,但只要进行逆向工程,理解起来就相当简单。然而,这仍然给服务器实现带来了巨大且不必要的负担,因为服务器需要在不同的调用之间“连接”连接。任何实际应用几乎都会迫使你使用消息队列来响应所有请求。例如,以任何冗余的方式运行服务器都意味着 SSE 流可能来自一个服务器,而客户端收到的请求却被发送到完全不同的服务器。

“可流式HTTP”会带来哪些影响?

Streamable HTTP方法将问题提升到了另一个层次,带来了一系列安全隐患和混淆的控制流。虽然它保留了 SSE 模式的所有缺点,但 Streamable HTTP 似乎比 SSE 模式更加混乱。

就实现层面而言,我只是略知皮毛,但根据我从文档中了解到的内容……

可以通过以下 3 种方式创建新会话:

  • GET请求
  • POST请求
  • POST包含 RPC 调用的请求

SSE 可以通过 4 种不同的方式打开:

  • AGET初始化
  • AGET参加之前的会议
  • APOST用于初始化会话
  • POST包含请求和带有 SSE 的答案的A

请求可以通过以下三种方式中的任何一种得到答复:

  • POST作为对 RPC 调用的HTTP 响应
  • 作为响应POSTRPC 号召而开启的 SSE 中的一项活动
  • 对于任何先前已开放的SSE而言,这都是一个值得关注的事件。

一般意义

由于它提供了多种发起会话、打开 SSE 连接和响应请求的方式,因此引入了相当大的复杂性。这种复杂性会带来以下几个方面的影响:

  • 复杂性增加:实现同一目标(会话创建、SSE 打开、响应传递)的多种方式增加了开发人员的认知负荷。代码更难理解、调试和维护。
  • 潜在的不一致性:由于实现同一结果的方法多种多样,不同服务器和客户端之间实现不一致的风险较高。这可能导致互操作性问题和意外行为。客户端和服务器可能只会实现它们认为必要的部分。
  • 可扩展性问题:虽然 Streamable HTTP 旨在提高效率,但从积极的角度来看,其复杂性会引入可扩展性瓶颈,而这些瓶颈需要克服。服务器可能难以管理大量机器上不同的连接状态和响应机制。

安全隐患

Streamable HTTP 的“灵活性”带来了一些安全隐患,以下仅列举其中几项:

  • 状态管理漏洞:跨不同连接类型(HTTP 和 SSE)管理会话状态非常复杂。这可能导致会话劫持、重放攻击或拒绝服务攻击等漏洞,因为攻击者需要在服务器上创建状态,并对其进行管理和维护,以等待会话恢复。
  • 攻击面扩大:会话创建和SSE连接的多个入口点扩大了攻击面。每个入口点都代表着攻击者可能利用的潜在漏洞。
  • 混淆和掩蔽:发起会话和传递响应的多种方式可用于混淆恶意活动。

授权

最新版本的协议对授权方式提出了一些非常主观的要求。

modelcontextprotocol.io/specification/2025-03-26/basic/authorization

  • 使用基于 HTTP 传输的实现应该符合本规范。
  • 使用 STDIO 传输的实现不应遵循此规范,而应从环境中检索凭据。

我的理解是,对于标准输入输出(stdio),随便你怎么做都行。但对于HTTP,你最好乖乖地走完这些OAuth2流程。如果我用的是HTTP作为传输协议,为什么需要实现OAuth2?而标准输入输出只需要一个API密钥就够了。

应该怎么做?

我不知道,就是觉得有点难过……感觉整个行业现在都慌了神——现在感觉很好,但以后会很难应对。

JSON RPC 协议只有一个,而 Stdio 显然是首选的传输协议。因此,我们应该尽可能使 HTTP 传输协议与 Stdio 保持一致,只有在万不得已的情况下才应该做出改变。

  • 在标准输入输出 (STDIO) 中,我们有环境变量;在 HTTP 中,我们有 HTTP 标头。
  • 在标准输入输出 (Stdio) 中,我们有类似套接字的行为,支持输入和输出流;而在 HTTP 中,我们有 WebSocket。

就是这样。我们应该能够在 WebSocket 上实现与在标准输入输出 (Stdio) 上相同的功能。WebSocket 是 HTTP 传输的理想选择。我们可以省去复杂的跨服务器会话状态管理,还可以避免许多特殊情况等等。

当然,有些事情,比如授权,在某些情况下可能会更复杂一些(在某些情况下则更容易);有些防火墙可能会阻止 WebSocket;小型会话可能会有额外的开销;恢复中断的会话可能会更困难。但正如 他们所说:

客户端和服务器可以根据自身特定需求实现额外的自定义传输机制。该协议与传输方式无关,可以在任何支持双向消息交换的通信信道上实现。

modelcontextprotocol.io/specification/2025-03-26/basic/transports#custom-transports

作为行业从业者,我们应该针对最常见的用例进行优化,而不是针对特殊情况。

附注:替代方案和补充

如上所述,似乎有更多协议正在涌现。MCP 实际上是“一种将 API 暴露给 LLM(可以创建代理)的协议”。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** 协议的共同之处在于,它们提供了一个合理的传输层和一种发现代理的方法。

14个相互竞争的标准

编辑

2025年5月12日: 这篇文章在 Hacker News 和 Reddit 上引起了一些关注。相关讨论可在此处查看: