MCP协议批判性审视:标准化AI接口的挑战与反思

作者批判性审视MCP协议,指出其在工程实践、文档质量和传输协议设计上存在问题,并建议使用更简洁的WebSockets方案。

原文标题:对 MCP 的批判性审视

原文作者:AI前线

冷月清谈:

本文作者对当前流行的模型上下文协议(MCP)进行了深入的批判性审视。MCP旨在标准化应用程序向大型语言模型(LLM)提供上下文的方式,类似于AI应用程序的USB-C端口。然而,作者发现MCP存在诸多问题,包括缺乏成熟的工程实践、文档质量差、SDK低劣以及协议规范不完善。文章重点批评了MCP的HTTP传输设置(SSE+HTTP和Streamable HTTP),认为其设计复杂、易出错,并可能引入安全隐患。作者建议采用更简洁的WebSockets方案,并呼吁业界优化最常见的用例,而非追求不必要的复杂性。此外,文章还对比了MCP与其他类似协议(如IBM的ACP和谷歌的A2A),认为它们在很大程度上是多余的。

怜星夜思:

1、文章中提到MCP协议的HTTP传输方式存在安全隐患,你认为在实际应用中,这些安全隐患可能导致哪些具体问题?
2、作者建议用WebSockets替代MCP的HTTP传输方式,你认为WebSockets在AI应用中相比HTTP+SSE或Steamable HTTP有哪些优势和劣势?
3、文章提到IBM的ACP和谷歌的A2A协议在很大程度上是多余的,你认为在构建AI Agent生态系统时,应该如何避免协议泛滥和重复造轮子的情况?

原文内容

作者 | Rasmus Holm
译者 | 王强
策划 | 褚杏娟

MCP 是一个开放协议,它标准化了应用程序向 LLM 提供上下文的方式。可以把 MCP 想象成 AI 应用程序的 USB-C 端口。正如 USB-C 提供了一种将设备连接到各种外设和配件的标准化方式一样,MCP 也提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。


—— Anthropic

我希望这到头来会是我自己的技能问题,也希望我遗漏了一些东西。

在过去的一段时间里,MCP(模型上下文协议)——它可以让 LLM 成为代理并与世界互动——真的火了起来。这个想法很简单:让我们为 LLM/ 代理标准化一个 API,让它们与世界互动,并定下将这些信息告知 LLM/ 代理的方式。

事情发展得非常快,IBM 最近发布了他们自己的 MCP“正交标准”,称为代理通信协议 (ACP),紧随其后,谷歌发布了 Agent2Agent (A2A)。

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

然而,让我惊讶的是成熟工程实践实在太少了。所有主要参与者都花费了数十亿美元来训练和微调他们的模型,但据我所知,他们最后却让实习生编写文档,提供的 SDK 质量低劣,而且几乎没有提供任何实现指导。

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

我的结论是,提案的整个 HTTP 传输设置(SSE+HTTP 和 Streamable HTTP)都应该被抛弃,取而代之的是模仿 stdio 的 Websockets。

背景

大约三周前,我决定投身 MCP 的潮流尝试一下,看看它在我们自己的环境中如何应用。我非常希望在使用抽象之前先了解底层工作原理。现在我们有了一个可以在不同传输协议上运行的新协议——真是令人兴奋!

Anthropic 是 MCP 标准化工作的幕后推手,而之所以 Anthropic 首席执行官认定大多数代码将在一年左右的时间内转由 LLM 编写,MCP 似乎就是主要原因之一。对编程工具的重视似乎是其标准化工作的指导原则,其核心在于使用体验。

协    议

简而言之,它是一个 JSON-RPC 协议,带有预定义的方法 / 端点,旨在与 LLM 结合使用。这并非本文的重点,但该协议本身确实存在一些需要批评的地方。

传   输

与 2005 年之后的许多应用程序号称是“本地优先”(很讽刺)一样,MCP 似乎也是如此。查看传输协议,你就能了解它们源自何方——大概他们的目的是构建在笔记本电脑上编程的 LLM 工具。他们可能正在研究本地 IDE(或者更现实地说,Cursor 或 Windsurf),以及如何让 LLM 与本地文件系统、数据库、编辑器、语言服务器等进行交互。

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

  • stdio

  • “得通过 HTTP 传输,Web 似乎是我们应该支持的东西。”

Stdio

使用 stdio 本质上意味着启动一个本地 MCP 服务器,连接从服务器到客户端的 stdout 和 stdin 管道,并开始发送 JSON 并使用 stderr 记录日志。这在某种程度上打破了使用这些流进行双向通信的 Unix/Linux 管道范式。当需要双向通信时,我们通常会使用 socket、Unix socket,甚至是网络 socket。

不过,socket 有简单易懂,在所有操作系统中开箱即用,不需要额外处理等优势。所以即使有人提出批评,我也理解。

HTTP+SSE / Steamable HTTP

HTTP 传输则是另一回事。同样的一个错误有两个版本:HTTP+SSE(服务器发送事件)传输,它正在被“Steamable HTTP”(一个杜撰的术语)取代,后者将 REST 语义与 SSE 结合使用。但这又带来了许多额外的困惑和极端情况。

总结下来就是:“由于我们喜欢用 SSE 进行 LLM 流式传输,所以我们不使用 WebSocket。相反,我们实际上是在 SSE 之上实现 WebSocket,并将其称为‘Steamable HTTP’,让人们认为这是一种被接受 / 已知的处理方式。”

他们在这个 PR:modelcontextprotocol/pull/206 中讨论了 WebSockets 的问题(以及 Steamable HTTP 的原因),并提出了一些非常奇怪的扭曲和不切实际的论点来反对使用 WebSockets。该帖子中至少还有一个人似乎同意我的观点:modelcontextprotocol/pull/206#issuecomment-2766559523。

陷入疯狂

我打算用 Golang 实现一个 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 部分的重要内容,而且似乎还没有人实现“Steamable HTTP”;甚至连他们自己的工具,例如 npx @modelcontextprotocol/inspector@latest,都没有。(公平地说,这可能是我自己搞出来的技术问题,可能是拉错了版本,因为几周后我再次检查时它已经可用了。你也可以在 inspect.mcp.garden 上找到版本,这可能更方便。)

一旦你掌握了架构,很快就会意识到实现 MCP 服务器或客户端可能是一项繁重的工作。问题在于 SSE/Streamable HTTP 实现试图像 socket 一样工作,模拟标准输入输出 (stdio),但实际上并非如此,它们会试图同时在任何地方执行所有操作。

HTTP+SSE 模式

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

在 HTTP+SSE 模式下,为了实现全双工,客户端会建立一个 SSE 会话,例如通过 GET /sse 来读取。第一次读取会提供一个可以提交写入操作的 URL。然后,客户端会继续使用指定的端点进行写入操作,例如发送 POST /a-endpoint?session-id=1234 的请求。服务器会返回 202 Accepted 状态码,且不包含任何正文,此时应该从 /sse 上预先存在的打开的 SSE 连接读取该请求的响应。

“Streamable HTTP” 模式

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

在 “Streamable HTTP”模式下,他们意识到无需在第一个请求中提供新的端点,而是可以使用 HTTP 标头作为会话 ID,并使用 REST 语义作为端点。例如,GET 或 POST /mcp 可以打开一个 SSE 会话并返回 mcp-session-id=1234 的 HTTP 标头。要发送数据,客户端会向 POST /mcp 发出请求,并添加 mcp-session-id=1234 的 HTTP 标头。响应可能:

  • 打开一个新的 SSE 流并发布回复

  • 返回 200 状态码,并在正文中包含回复

  • 返回 202 状态码,表示回复将被写入任意一个现有的 SSE 流

要结束会话,客户端可以发送或不发送带有 mcp-session-id=1234 标头的 DELETE /mcp 消息。服务器必须维护状态,除非客户端正常结束会话,否则无法明确知道客户端何时放弃了会话。

SSE 模式会有什么影响?

这是一个问题百出的设计,我都不知道该从何说起。

虽然 SSE 模式的一些关键特性尚未编写文档,但只要对其进行逆向工程,就会发现它相当简单。但这仍然会给服务器实现带来巨大且不必要的负担,服务器实现需要在调用之间“join”连接。实际操作几乎都会迫使你使用消息队列来回复任何请求。例如,以任何冗余方式运行服务器都意味着 SSE 流可能从一个服务器发送到客户端,而请求却被发送到完全不同的服务器。

“Steamable HTTP”会有什么影响?

Steamable HTTP 方法将其提升到了另一个层次,带来了一系列安全问题和令人困惑的控制流。 Streamable HTTP 虽然保留了 SSE 模式的所有缺陷,但它似乎更像是 SSE 模式的复杂扩展集。

就实现而言,我只是略知皮毛,但根据我对文档的理解……

可以通过 3 种方式创建一个新会话

  • 一个空的 GET 请求

  • 一个空的 POST 请求

  • 一个包含 RPC 调用的 POST 请求

一个 SSE 可以通过 4 种不同方式打开

  • 一个用于初始化的 GET 请求

  • 一个用于加入先前会话的 GET 请求

  • 一个用于初始化会话的 POST 请求

  • 一个包含请求并使用 SSE 响应的 POST 请求

一个请求可以通过 3 种不同方式响应

  • 一个作为对包含 RPC 调用的 POST 请求的 HTTP 响应

  • 一个作为对 POST RPC 调用的响应而打开的 SSE 中的事件

  • 一个作为之前打开的任何 SSE 的事件

总体影响

由于它有多种方式来发起会话、打开 SSE 连接和响应请求,因此带来了显著的复杂性。这种复杂性有几个普遍的影响:

  • 复杂性提升:执行同一件事(会话创建、SSE 开启、响应传递)的多种方式增加了开发人员的认知负担。代码的理解、调试和维护变得更加困难。

  • 不一致的可能性:由于实现同一结果的方式多种多样,不同服务器和客户端之间实现不一致的风险更高。这可能导致互操作性问题和意外行为。客户端和服务器只会实现它们认为必要的部分。

  • 可扩展性问题:虽然 Streamable HTTP 旨在提高效率,但从另一方面来说,其复杂性将带来需要克服的可扩展性瓶颈。服务器可能难以管理大量机器上的各种连接状态和响应机制。

安全隐患

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

  • 状态管理漏洞:跨不同连接类型(HTTP 和 SSE)管理会话状态非常复杂。这可能导致会话劫持、重放攻击或 DoS 攻击等漏洞,因为服务器会创建需要管理并保存的状态,直到会话恢复。

  • 扩大攻击面:会话创建和 SSE 连接的多个入口点扩大了攻击面。每个入口点都代表着一个攻击者可能利用的潜在漏洞。

  • 困惑和混淆:发起会话和传递响应的多种方式可用于混淆恶意活动。

授权

最新版本的协议包含一些关于如何进行授权的非常主观的要求。

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

使用基于 HTTP 的传输方式的实现应该符合此规范。


使用 STDIO 传输的实现不应遵循此规范,而应从环境中获取凭据。

我理解的意思是,对于 stdio,随便你。对于 HTTP,你最好还是去实现 OAuth2 吧。如果我使用 HTTP 作为传输方式,为什么还需要实现 OAuth2,而 stdio 用 API 密钥就够了?

应该怎样改进

我不知道,只是有点难过……目前业界似乎无计可施——“现在还凑合,但以后处理起来会很困难。”

JSON RPC 协议只有一个,而 Stdio 显然是首选的传输协议。那么我们应该尽量让 HTTP 传输方式接近 Stdio,只有在真的非常需要的情况下才引入变化。

  • 在 Stdio 中我们有环境变量;在 HTTP 中我们有 HTTP 标头。

  • 在 Stdio 中我们有类似 socket 的行为,包含输入和输出流;在 HTTP 中我们有 WebSocket。

就是这样。我们应该能够在 WebSocket 上完成与在 Stdio 上相同的操作。WebSocket 是通过 HTTP 进行传输的理想选择。我们可以省去会话中复杂的跨服务器状态管理。我们可以消除大量的极端情况,等等。

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

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


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

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

附注:替代方案和补充

如上所述,似乎有更多协议正在涌现。MCP 实际上是“向 LLM(可以创建代理)公开 API 的协议”。IBM 和谷歌的较新协议(ACP 和 A2A)实际上是“向 LLM(可以创建代理的代理)公开代理的协议”。

纵观 A2A 规范,似乎对它们的需求非常有限。尽管它们声称是正交的,但 A2A 中的大多数功能都可以使用 MCP 原样或通过少量添加来实现。

归根结底,它们就是两个完整的协议,它们也可以作为 MCP 服务器中的工具。甚至 IBM 似乎也承认他们的协议并非真正必要:

“代理可以被视为 MCP 资源,并进一步作为 MCP 工具调用。这种 ACP 代理的视角允许 MCP 客户端发现并运行 ACP 代理……”


—— IBM / agentcommunicationprotocol.dev/ecosystem/mcp-adapter我一开始的感觉是,ACP 协议更像是 IBM 推广其“代理构建工具”BeeAI 的一种尝试。

这两个 A** 协议都提供了一个合理的传输层和一种发现代理的方法。

原文链接:

https://raz.sh/blog/2025-05-02_a_critical_look_at_mcp

活动推荐

6 月 27~28 日的 AICon 北京站将继续聚焦 AI 技术的前沿突破与产业落地,围绕 AI Agent 构建、多模态应用、大模型推理性能优化、数据智能实践、AI 产品创新等热门议题,深入探讨技术与应用融合的最新趋势。欢迎持续关注,和我们一起探索 AI 应用的无限可能!


今日荐文

图片

你也「在看」吗?👇

同意楼上的分析,感觉这些安全隐患就像是潘多拉的魔盒,一旦被打开,后果不堪设想。想象一下,如果MCP被用于智能家居系统,攻击者利用会话劫持漏洞控制了你的智能门锁,那简直就是噩梦。而且,由于攻击面扩大,一些隐藏的漏洞也更容易被挖掘出来,比如缓冲区溢出、SQL注入等等。更可怕的是,这些攻击往往难以追踪,因为攻击者可以利用多种方式发起会话和传递响应,混淆视听。所以,我觉得对于MCP的安全问题,不能掉以轻心,需要从协议设计、开发实现到部署运维,全方位地加强安全防护。

我认为这个问题需要从两个层面来看:一是协议本身的安全性,这涉及到加密算法、身份验证机制等方面;二是实现层面的安全性,这涉及到代码质量、漏洞管理等方面。MCP的HTTP传输方式之所以存在安全隐患,很大程度上是因为它试图在HTTP协议之上模拟socket的行为,引入了过多的复杂性。这种复杂性不仅增加了开发难度,也增加了出错的可能性。因此,我认为解决MCP安全问题的根本之道,是回归简洁,采用更成熟、更安全的协议,比如WebSockets。当然,即使采用了WebSockets,也需要加强安全防护,比如使用TLS加密、实施访问控制等。

WebSockets的优势在于其全双工、实时的特性,这在AI应用中非常重要。想象一下一个实时的AI客服,如果使用HTTP+SSE,每次用户提问都需要建立一个新的连接,延迟高不说,服务器压力也大。但如果使用WebSockets,客服和用户之间可以建立一个长连接,实时交互,体验就好多了。而且,WebSockets协议本身也比较简单,易于实现和维护。当然,WebSockets也有一些劣势,比如需要服务器保持连接状态,对服务器资源消耗较大;另外,WebSockets的安全性也需要特别关注,需要使用TLS等加密手段来保护数据传输的安全。

我认为开源协作是一种有效的避免协议泛滥的方式。如果有一个大家都认可的开源协议,那么开发者就可以基于这个协议来开发自己的Agent,而不需要自己再去定义一套协议。而且,开源协议的透明性和开放性也有助于发现和修复潜在的问题,提高系统的安全性。当然,开源协作也需要一些组织和协调,比如成立一个专门的委员会来维护和管理协议,制定清晰的贡献指南和代码审查流程等。

避免协议泛滥,我觉得最重要的就是“顶层设计”。在AI Agent生态系统构建之初,就应该有一个清晰的架构和标准,明确各个组件之间的交互方式和数据格式。这样,开发者就可以基于这些标准来开发自己的Agent,而不需要重复造轮子。另外,可以借鉴一些成熟的互联网协议,比如HTTP、RESTful API等,尽量避免发明新的协议。当然,标准也不是一成不变的,需要根据实际情况不断演进和完善。不过,在演进的过程中,要尽量保持向后兼容,避免对现有系统造成影响。

我觉得WebSockets最大的优势是它的简洁性。HTTP+SSE和Steamable HTTP为了实现类似WebSockets的功能,引入了大量的复杂性,这不仅增加了开发难度,也增加了出错的可能性。而WebSockets则直接提供了一个双向通信的通道,开发者不需要关心底层复杂的实现细节,可以更专注于业务逻辑的实现。至于WebSockets的劣势,比如资源消耗大、安全性问题等,都可以通过一些技术手段来解决。比如可以使用连接池来减少资源消耗,使用WSS来保证数据传输的安全。

关于MCP的HTTP传输安全隐患,确实值得深思。从开发角度来说,最直接的风险是会话管理漏洞,攻击者可能通过伪造或劫持session-id来冒充合法用户,窃取数据或者执行恶意操作。想象一下,如果一个AI助手被会话劫持,攻击者就能以你的名义访问你的邮箱、文件,甚至执行转账等操作。此外,由于多种方式发起会话和传递响应,也为重放攻击提供了可能,攻击者可以截获并重复发送之前的请求,例如重复购买商品或者重复提交恶意指令。当然,DoS攻击也不能忽视,攻击者可以利用协议的复杂性,发送大量请求来消耗服务器资源,导致服务瘫痪。所以,在实际应用中,开发者需要格外关注这些安全问题,并采取相应的防护措施,比如加强会话管理、实施严格的身份验证和授权机制,以及采用流量限制和异常检测等手段。

从一个“云原生”的角度看,WebSockets可能面临一些挑战。比如,在复杂的微服务架构中,WebSockets连接的管理和维护可能会变得非常困难。另外,一些防火墙或者代理服务器可能不支持WebSockets协议,导致连接失败。相比之下,HTTP+SSE或者Steamable HTTP则可以更好地利用HTTP协议的特性,更容易穿透防火墙,也更容易与现有的HTTP基础设施集成。因此,选择哪种传输方式,需要根据具体的应用场景和技术架构来决定,不能一概而论。

从商业角度来看,协议之争也难以避免。每个公司都希望自己的协议成为事实标准,从而在AI Agent生态系统中占据主导地位。这种竞争虽然可能会导致一些重复造轮子的情况,但也会促进技术的创新和发展。因此,我认为应该以一种更加开放的心态来看待协议之争,鼓励不同的公司和组织提出自己的方案,并通过市场竞争来决定最终的胜者。当然,政府和行业协会也可以发挥一定的作用,引导行业朝着更加健康和有序的方向发展。