.Bryce.头像
关注
MCP传输方式详解:stdio、HTTP、WebSocket到底该选哪个?我做了个对比测试封面图

MCP传输方式详解:stdio、HTTP、WebSocket到底该选哪个?我做了个对比测试

MCP传输方式详解:stdio、HTTP、WebSocket到底该选哪个?我做了个对比测试

写在前面

前两篇文章讲MCP的时候,例子用的都是stdio传输——也就是客户端通过标准输入输出跟Server进程通信。很多读者跟着跑通了Demo,但也有人问:“我的Server要部署在另一台机器上,stdio还能用吗?”

答案是不能。stdio只能在同一台机器上用,因为它依赖进程间的管道通信。如果你的MCP Server要部署在远程服务器上,或者要被多个客户端同时访问,就需要换一种传输方式。

这篇文章我就把MCP支持的三种传输方式——stdio、HTTP/SSE、WebSocket——全部讲清楚,包括它们的原理、优缺点、适用场景,以及我自己做对比测试时的一些发现。

一、先搞清楚:传输层在MCP里是什么位置

在深入具体传输方式之前,先回顾一下MCP的架构。MCP协议本身是跟传输方式无关的——它定义的是消息格式(JSON-RPC 2.0)和交互流程(握手、工具发现、调用),但不规定这些消息通过什么通道传递。

这就好比HTTP协议:它定义了请求响应的格式,但可以跑在TCP之上,也可以跑在TLS之上,甚至可以跑在QUIC之上。传输方式变了,协议本身不变。

MCP目前官方支持三种传输方式:

  1. stdio:通过标准输入输出传递消息,适合本地单进程场景
  2. HTTP + SSE:通过HTTP发送请求,通过SSE(Server-Sent Events)接收推送,适合Web场景
  3. WebSocket:全双工通信,适合需要双向实时通信的场景

下面我一个一个说。

二、stdio传输:最简单也最常用

stdio是MCP最基础的传输方式,也是官方示例里用得最多的。它的原理很简单:

  • 客户端启动Server作为子进程
  • 客户端通过Server的stdin发送JSON-RPC请求
  • Server通过stdout返回JSON-RPC响应
  • stderr用于输出日志和错误信息

代码示例

服务端:

# server_stdio.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio

server = Server("demo-server")

@server.list_tools()
async def list_tools():
    return [Tool(name="hello", description="打招呼", inputSchema={"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]})]

@server.call_tool()
async def call_tool(name, arguments):
    return [TextContent(type="text", text=f"你好,{arguments['name']}!")]

if __name__ == "__main__":
    asyncio.run(server.run_stdio())

客户端:

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command="python", args=["server_stdio.py"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("hello", {"name": "张三"})
            print(result.content[0].text)

优点

  • 配置最简单:不需要开端口、不需要配URL,指定启动命令就行
  • 安全性好:Server只跟启动它的客户端通信,不会暴露到网络上
  • 性能不错:本地进程间通信,延迟很低
  • 生命周期管理方便:客户端退出时,子进程自动终止

缺点

  • 只能本地使用:Server必须跟客户端在同一台机器上
  • 一对一限制:一个Server进程只能跟一个客户端通信,多个客户端需要启动多个Server实例
  • 不适合长驻服务:每次客户端连接都要启动新进程,启动有开销
  • 调试不方便:stdio的消息不像HTTP那样可以用抓包工具看

适用场景

  • 本地开发和测试
  • 桌面应用(比如Claude Desktop、Cursor)
  • 工具跟Agent绑定部署、不需要远程访问的场景

我自己的体会是:开发阶段用stdio最省心,不用管网络和端口的问题。但一旦要部署到生产环境,尤其是多客户端共享一个Server的场景,stdio就不够用了。

三、HTTP + SSE传输:Web场景的首选

HTTP + SSE是MCP为Web场景设计的传输方式。它的原理是:

  • 客户端通过HTTP POST发送JSON-RPC请求
  • 客户端在初始化时建立一个SSE连接,用于接收Server的推送(比如通知、进度更新)
  • Server对HTTP请求的响应通过SSE通道返回

这里可能有人会问:为什么不直接用HTTP请求响应?为什么还要SSE?

因为MCP支持Server主动推送消息(比如通知、进度更新),而HTTP是单向的——只能客户端问、Server答,Server不能主动找客户端。SSE正好弥补了这个不足,它让Server可以主动向客户端推送消息。

代码示例

服务端(用FastAPI):

# server_http.py
from mcp.server import Server
from mcp.types import Tool, TextContent
from mcp.server.fastmcp import FastMCP
import uvicorn

mcp = FastMCP("demo-server")

@mcp.tool()
def hello(name: str) -> str:
    """打招呼"""
    return f"你好,{name}!"

if __name__ == "__main__":
    uvicorn.run(mcp.app(), host="0.0.0.0", port=8000)

客户端:

from mcp import ClientSession
from mcp.client.sse import sse_client

async def main():
    async with sse_client("http://localhost:8000/sse") as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("hello", {"name": "李四"})
            print(result.content[0].text)

优点

  • 支持远程访问:Server可以部署在任何地方,客户端通过URL连接
  • 多客户端共享:一个Server可以同时服务多个客户端
  • 适合Web应用:浏览器原生支持SSE,跟前端集成方便
  • 可扩展性好:可以加负载均衡、限流、认证等中间件

缺点

  • 配置稍复杂:需要启动HTTP服务、配置端口和路由
  • SSE有一些限制:比如浏览器对同一域名的SSE连接数有限制(通常6个)
  • 延迟比stdio高:毕竟走了网络栈
  • 需要处理跨域问题:如果前端跟Server不同域,需要配置CORS

适用场景

  • Web应用中的Agent工具接入
  • Server需要远程部署的场景
  • 多客户端共享同一个Server的场景
  • 需要跟现有微服务架构集成的场景

我在项目里用HTTP+SSE的时候,最大的感受是"终于可以把Server部署到服务器上了"。以前用stdio的时候,每个Agent实例都要启动一堆Server子进程,资源浪费严重。换成HTTP之后,Server集中部署,Agent通过网络调用,资源利用率高多了。

四、WebSocket传输:实时双向通信

WebSocket是第三种传输方式,它提供全双工的通信通道——客户端和Server可以随时互相发消息,不需要请求响应的配对。

原理

WebSocket的原理大家应该比较熟悉了:

  • 客户端通过HTTP握手升级到WebSocket连接
  • 连接建立后,双方可以随时发送消息
  • MCP的JSON-RPC消息直接通过WebSocket帧传递

代码示例

服务端:

# server_ws.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import asyncio
import websockets
import json

server = Server("demo-server")

@server.list_tools()
async def list_tools():
    return [Tool(name="hello", description="打招呼", inputSchema={"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]})]

@server.call_tool()
async def call_tool(name, arguments):
    return [TextContent(type="text", text=f"你好,{arguments['name']}!")]

async def handle_connection(websocket):
    async def read():
        message = await websocket.recv()
        return json.loads(message)
    async def write(message):
        await websocket.send(json.dumps(message))
    await server.run(read, write, server.create_initialization_options())

async def main():
    async with websockets.serve(handle_connection, "0.0.0.0", 8765):
        await asyncio.Future()

if __name__ == "__main__":
    asyncio.run(main())

优点

  • 全双工通信:双方可以随时发消息,延迟最低
  • 连接复用:一个连接上可以同时处理多个请求
  • 适合实时场景:比如需要Server持续推送数据的场景
  • 比HTTP+SSE更简洁:不需要分开维护HTTP请求和SSE连接

缺点

  • 生态相对不成熟:MCP的WebSocket传输支持还在完善中
  • 代理和防火墙问题:有些企业网络会拦截WebSocket连接
  • 调试不如HTTP方便:没有像浏览器开发者工具那样直观的调试界面
  • 需要自己处理连接管理:断线重连、心跳检测等都要自己实现

适用场景

  • 需要低延迟双向通信的场景
  • 实时数据推送(比如日志流、监控数据)
  • 长连接场景(比如Agent跟Server需要持续交互)

说实话,WebSocket传输我用得不多,因为大部分场景下HTTP+SSE已经够用了。WebSocket更适合那种"Server需要持续不断地给客户端推数据"的场景,比如实时日志流、代码执行的实时输出等。

五、三种方式的对比

我做了一张对比表,把三种传输方式的关键维度放在一起:

维度stdioHTTP + SSEWebSocket
通信范围本地进程间网络网络
连接模式一对一多对一多对一
双向通信是(通过stdin/stdout)半双工(HTTP请求+SSE推送)全双工
延迟极低中低
配置复杂度低中中高
多客户端支持不支持支持支持
远程部署不支持支持支持
浏览器兼容不支持原生支持原生支持
安全性高(不暴露网络)需自行配置需自行配置
适合阶段开发/本地生产/Web实时/长连接

六、我的选型建议

说了这么多,到底该选哪个?我根据自己的经验给一些建议:

如果你是在本地开发或者做Demo:选stdio。配置最简单,不用管网络,出问题也好排查。

如果你要做Web应用,Server需要远程部署:选HTTP + SSE。这是目前最成熟的方案,生态最好,跟现有架构集成也方便。

如果你需要Server持续推送实时数据:选WebSocket。比如代码执行的实时输出、日志流、监控数据等场景。

如果你不确定:先用stdio开发,部署的时候再考虑换成HTTP+SSE。MCP的传输层是可替换的,你的业务代码不用改,只需要换传输方式就行。

这里有一个很重要的点:MCP的传输层是抽象出来的,你的工具逻辑跟传输方式无关。 你写的Tool函数,不管用stdio还是HTTP还是WebSocket,都是一样的代码。这意味着你可以在开发阶段用stdio,生产环境用HTTP,不需要改业务逻辑。

我自己的项目就是这么做的:开发的时候所有Server都用stdio跑,调试方便;部署到服务器上的时候换成HTTP+SSE,加个Nginx做反向代理和负载均衡。工具代码一行都没改,只改了启动方式。

七、踩坑记录

最后说几个我在切换传输方式时踩的坑:

坑1:HTTP模式下的CORS问题

把Server从stdio换成HTTP之后,前端调用一直报CORS错误。原因是FastMCP默认没有开启CORS,需要手动配置:

from fastapi.middleware.cors import CORSMiddleware

app = mcp.app()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 生产环境要限制域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

坑2:SSE连接被代理服务器截断

部署在Nginx后面的时候,SSE连接经常断开。查了半天才发现是Nginx的缓冲设置问题,需要加这些配置:

proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_read_timeout 86400s;

坑3:stdio模式下Server的日志输出会干扰通信

stdio模式下,Server的stdout是用来传MCP消息的。如果你在Server里用print输出日志,日志会混进stdout里,导致客户端解析失败。解决办法是把日志输出到stderr,或者写到文件里:

import logging
logging.basicConfig(level=logging.INFO, stream=sys.stderr)

这个坑我踩过两次,每次都调试了半天才发现是print搞的鬼。

写在最后

MCP的传输方式设计得很灵活,三种方式各有适用场景。理解它们的区别,能帮你在不同阶段选择最合适的方案。

我的体会是:不要一开始就纠结选哪种传输方式,先把工具逻辑写好,跑通stdio版本。等需要部署、需要多客户端共享的时候,再换成HTTP+SSE也不迟。MCP的传输层抽象让这种切换成本很低。

当然,传输方式只是MCP的一部分。下一篇我们会聊更实际的话题——怎么在智能体中真正用好MCP工具,包括工具选择策略、调用错误处理、多工具协作等。

你们在项目里用的是哪种传输方式?有没有遇到什么奇怪的问题?欢迎在评论区聊聊。


下一篇预告:在智能体中使用MCP工具。我们会把MCP跟Agent真正结合起来,看看大模型是怎么选择工具、调用工具、处理结果的,以及一些让工具调用更稳定的技巧。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_89160889/article/details/167495587

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--