仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
作者|RasmusHolm
译者|王强
策划|褚杏娟
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和StreamableHTTP)都应该被抛弃,取而代之的是模仿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、Unixsocket,甚至是网络socket。
不过,socket有简单易懂,在所有操作系统中开箱即用,不需要额外处理等优势。所以即使有人提出批评,我也理解。
HTTP+SSE/SteamableHTTP
HTTP传输则是另一回事。同样的一个错误有两个版本:HTTP+SSE(服务器发送事件)传输,它正在被“SteamableHTTP”(一个杜撰的术语)取代,后者将REST语义与SSE结合使用。但这又带来了许多额外的困惑和极端情况。
总结下来就是:“由于我们喜欢用SSE进行LLM流式传输,所以我们不使用WebSocket。相反,我们实际上是在SSE之上实现WebSocket,并将其称为‘SteamableHTTP’,让人们认为这是一种被接受/已知的处理方式。”
陷入疯狂
我打算用Golang实现一个MCP服务器。目前没有官方的GoSDK,而且我想了解这个协议。结果证明,这是个会影响心理健康的错误……
警告信号……
我查看了https ://modelcontextprotocol. io,发现其文档写得很糟糕(所有LLM供应商似乎都在内部竞争,争相编写令人困惑的文档)。该规范掩盖或忽略了协议的一些重要方面,并且没有提供任何对话流程的示例。事实上,整个网站似乎都不是用来阅读标准规范的;相反,它引导你去学习如何实现他们的SDK的教程。
所有示例服务器都是用Python或JavaScript实现的,目的是让你下载并使用stdio在本地运行它们。如果你想在别人的电脑上运行程序,Python和JavaScript可能是最糟糕的语言选择之一。作者似乎意识到了这一点,因为所有示例都以Docker容器的形式提供。
说实话……你上次运行pipinstall而没有陷入依赖地狱是什么时候?我是不是太自命不凡/武断了,认为人工智能领域的从业者只懂Python,而且“嗯,它在我的电脑上能用就行”这种做法仍被认为是可接受的?对于任何尝试过运行HuggingFace某一功能的用户来说,这一点都应该是显而易见的。
如果你想在本地运行MCP,难道你不想选择Rust、Go这样的可移植语言,或者像Java或C#这样基于虚拟机的语言吗?
问题
当我开始实现该协议时,我立刻就觉得必须对其进行逆向工程。文档中缺少SSE部分的重要内容,而且似乎还没有人实现“SteamableHTTP”;甚至连他们自己的工具,例如npx@modelcontextprotocol/inspector@latest,都没有。(公平地说,这可能是我自己搞出来的技术问题,可能是拉错了版本,因为几周后我再次检查时它已经可用了。你也可以在inspect. mcp.garden上找到版本,这可能更方便。)
一旦你掌握了架构,很快就会意识到实现MCP服务器或客户端可能是一项繁重的工作。问题在于SSE/StreamableHTTP实现试图像socket一样工作,模拟标准输入输出(stdio),但实际上并非如此,它们会试图同时在任何地方执行所有操作。
HTTP+SSE模式
modelcontextprotocol. io/specification/2024-11-05/basic/transports
在HTTP+SSE模式下,为了实现全双工,客户端会建立一个SSE会话,例如通过GET/sse来读取。第一次读取会提供一个可以提交写入操作的URL。然后,客户端会继续使用指定的端点进行写入操作,例如发送POST/a-endpoint?session-id=1234的请求。服务器会返回202Accepted状态码,且不包含任何正文,此时应该从/sse上预先存在的打开的SSE连接读取该请求的响应。
“StreamableHTTP”模式
modelcontextprotocol. io/specification/2025-03-26/basic/transports
打开一个新的SSE流并发布回复
返回200状态码,并在正文中包含回复
返回202状态码,表示回复将被写入任意一个现有的SSE流
要结束会话,客户端可以发送或不发送带有mcp-session-id=1234标头的DELETE/mcp消息。服务器必须维护状态,除非客户端正常结束会话,否则无法明确知道客户端何时放弃了会话。
SSE模式会有什么影响?
这是一个问题百出的设计,我都不知道该从何说起。
虽然SSE模式的一些关键特性尚未编写文档,但只要对其进行逆向工程,就会发现它相当简单。但这仍然会给服务器实现带来巨大且不必要的负担,服务器实现需要在调用之间“join”连接。实际操作几乎都会迫使你使用消息队列来回复任何请求。例如,以任何冗余方式运行服务器都意味着SSE流可能从一个服务器发送到客户端,而请求却被发送到完全不同的服务器。
“SteamableHTTP”会有什么影响?
SteamableHTTP方法将其提升到了另一个层次,带来了一系列安全问题和令人困惑的控制流。StreamableHTTP虽然保留了SSE模式的所有缺陷,但它似乎更像是SSE模式的复杂扩展集。
就实现而言,我只是略知皮毛,但根据我对文档的理解……
可以通过3种方式创建一个新会话:
一个空的GET请求
一个空的POST请求
一个包含RPC调用的POST请求
一个SSE可以通过4种不同方式打开:
一个用于初始化的GET请求
一个用于加入先前会话的GET请求
一个用于初始化会话的POST请求
一个包含请求并使用SSE响应的POST请求
一个请求可以通过3种不同方式响应:
一个作为对包含RPC调用的POST请求的HTTP响应
一个作为对POSTRPC调用的响应而打开的SSE中的事件
一个作为之前打开的任何SSE的事件
总体影响
由于它有多种方式来发起会话、打开SSE连接和响应请求,因此带来了显著的复杂性。这种复杂性有几个普遍的影响:
复杂性提升:执行同一件事(会话创建、SSE开启、响应传递)的多种方式增加了开发人员的认知负担。代码的理解、调试和维护变得更加困难。
不一致的可能性:由于实现同一结果的方式多种多样,不同服务器和客户端之间实现不一致的风险更高。这可能导致互操作性问题和意外行为。客户端和服务器只会实现它们认为必要的部分。
可扩展性问题:虽然StreamableHTTP旨在提高效率,但从另一方面来说,其复杂性将带来需要克服的可扩展性瓶颈。服务器可能难以管理大量机器上的各种连接状态和响应机制。
安全隐患
StreamableHTTP的“灵活性”带来了一些安全隐患,以下仅列举其中几项:
状态管理漏洞:跨不同连接类型(HTTP和SSE)管理会话状态非常复杂。这可能导致会话劫持、重放攻击或DoS攻击等漏洞,因为服务器会创建需要管理并保存的状态,直到会话恢复。
扩大攻击面:会话创建和SSE连接的多个入口点扩大了攻击面。每个入口点都代表着一个攻击者可能利用的潜在漏洞。
困惑和混淆:发起会话和传递响应的多种方式可用于混淆恶意活动。
授权
最新版本的协议包含一些关于如何进行授权的非常主观的要求。
modelcontextprotocol. io/specification/2025-03-26/basic/authorization
使用基于HTTP的传输方式的实现应该符合此规范。
使用STDIO传输的实现不应遵循此规范,而应从环境中获取凭据。
我理解的意思是,对于stdio,随便你。对于HTTP,你最好还是去实现OAuth2吧。如果我使用HTTP作为传输方式,为什么还需要实现OAuth2,而stdio用API密钥就够了?
应该怎样改进
我不知道,只是有点难过……目前业界似乎无计可施——“现在还凑合,但以后处理起来会很困难。”
JSONRPC协议只有一个,而Stdio显然是首选的传输协议。那么我们应该尽量让HTTP传输方式接近Stdio,只有在真的非常需要的情况下才引入变化。
在Stdio中我们有环境变量;在HTTP中我们有HTTP标头。
在Stdio中我们有类似socket的行为,包含输入和输出流;在HTTP中我们有WebSocket。
就是这样。我们应该能够在WebSocket上完成与在Stdio上相同的操作。WebSocket是通过HTTP进行传输的理想选择。我们可以省去会话中复杂的跨服务器状态管理。我们可以消除大量的极端情况,等等。
当然,有些事情,比如授权,在某些情况下可能会更复杂一些(而在某些情况下则更容易);有些防火墙可能会阻止WebSocket;小型会话可能会有额外的开销;恢复中断的会话可能会更困难。但正如人们所说:
客户端和服务器可以根据其特定需求实现额外的自定义传输机制。该协议与传输无关,可以在任何支持双向消息交换的通信通道上实现。
modelcontextprotocol. io/specification/2025-03-26/basic/transports#custom-transports
作为一个行业,我们应该针对最常见的用例进行优化,而不是只优化一些特殊情况。
附注:替代方案和补充
如上所述,似乎有更多协议正在涌现。MCP实际上是“向LLM(可以创建代理)公开API的协议”。IBM和谷歌的较新协议(ACP和A2A)实际上是“向LLM(可以创建代理的代理)公开代理的协议”。
纵观A2A规范,似乎对它们的需求非常有限。尽管它们声称是正交的,但A2A中的大多数功能都可以使用MCP原样或通过少量添加来实现。
归根结底,它们就是两个完整的协议,它们也可以作为MCP服务器中的工具。甚至IBM似乎也承认他们的协议并非真正必要:
“代理可以被视为MCP资源,并进一步作为MCP工具调用。这种ACP代理的视角允许MCP客户端发现并运行ACP代理……”
这两个A**协议都提供了一个合理的传输层和一种发现代理的方法。
原文链接:
https ://raz. sh/blog/2025-05-02_a_critical_look_at_mcp
活动推荐
6月27~28日的AICon北京站将继续聚焦AI技术的前沿突破与产业落地,围绕AIAgent构建、多模态应用、大模型推理性能优化、数据智能实践、AI产品创新等热门议题,深入探讨技术与应用融合的最新趋势。欢迎持续关注,和我们一起探索AI应用的无限可能!
今日荐文
别被MCP的包装骗了!重构系统、向智能体转型,CEO亲述:关键时刻还是RPA兜底?
王兴兴回应比赛风波:挣到钱了,但现在的机器人别指望它能干活
13年苦熬到170亿市值,一夜间被用户抛弃!一封“AI吹”全员信让网友“不喷不行”
天塌了,Claude全面断供Windsurf!CEO喊冤控诉也挡不住开发者退订,祸起OpenAI收购?
0粉丝狂卷数十亿播放,靠AI流量欺诈获利近亿!网友:这“刑”得离谱
你也「在看」吗?👇