前言

第三篇中,我理清了 MCP、Skills、Hooks 三者的关系。如果把 MCP 比作一座桥梁,那么传输协议就是桥上的通行规则——决定了消息如何从一端传递到另一端,以及桥梁能承载多重的负荷、能跨越多远的距离。

本文将深入剖析 MCP 的三种传输机制:stdioSSEStreamable HTTP,理解它们的技术原理、架构差异,以及为什么官方在 2025 年 3 月用 Streamable HTTP 替代了传统的 HTTP+SSE 方案。


一、MCP 传输层概览

1.1 消息格式:JSON-RPC 2.0

无论使用哪种传输方式,MCP 的消息格式统一采用 JSON-RPC 2.0

// 请求示例
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": 1,
  "params": {
    "name": "read_file",
    "arguments": {"path": "/src/main.py"}
  }
}

// 响应示例
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {"content": "print('Hello World')"}
}

关键规则

  • 所有消息必须使用 UTF-8 编码
  • 消息使用换行符 \n 分隔(不得包含嵌入式换行)
  • 这是三种传输方式的共同基础

1.2 三种传输方式的发展脉络

在这里插入图片描述


二、stdio 传输:最简单直接

2.1 工作原理

stdio(标准输入输出)传输是 MCP 最基础的通信方式,通过进程间通信实现:

  • 客户端将 MCP Server 作为子进程启动
  • Server 从 stdin 读取 JSON-RPC 消息
  • Server 通过 stdout 返回响应
  • Server 可选择通过 stderr 输出日志

类比:就像两个人面对面交流,一个人说话(stdout),另一个人听(stdin),直接、即时。

2.2 架构流程图

┌─────────────────────────────────────────────────────────────┐
│                    stdio 传输架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────┐    stdin     ┌──────────┐    stdout   ┌─────┐ │
│  │  父进程   │ ──────────► │  子进程   │ ──────────► │父进程│ │
│  │ (客户端)  │  写入JSON   │ (服务器)  │  返回JSON   │(客户端│ │
│  └──────────┘             └──────────┘            └─────┘ │
│        │                        │                          │
│        │  1. 启动子进程          │                          │
│        ├───────────────────────►│                          │
│        │                        │                          │
│        │  2. 发送 JSON-RPC 请求  │                          │
│        ├───────────────────────►│                          │
│        │                        │  3. 处理消息              │
│        │                        ├─────────────────────────►│
│        │                        │  4. 返回响应              │
│        │◄───────────────────────┤◄────────────────────────│
│        │                        │                          │
│        │  5. 关闭 stdin         │                          │
│        ├───────────────────────►│                          │
│        │                        │  6. 子进程退出            │
│        │◄───────────────────────┤                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.3 代码示例

客户端启动 Server

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

async def run_client():
    # 配置 Server 参数
    server_params = StdioServerParameters(
        command="python",                    # 启动命令
        args=["-m", "my_mcp_server"],       # 命令参数
        env={"PYTHONPATH": "/path/to/server"}  # 环境变量
    )
    
    # 建立连接
    async with stdio_client(server_params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            # 初始化会话
            await session.initialize()
            
            # 获取工具列表
            tools = await session.list_tools()
            print(f"可用工具: {[t.name for t in tools.tools]}")
            
            # 调用工具
            result = await session.call_tool("read_file", {"path": "/src/main.py"})
            print(result.content)

Server 端实现

from mcp.server import Server
from mcp.server.stdio import stdio_server

server = Server("my-server")

@server.list_tools()
async def list_tools():
    return [{"name": "read_file", "description": "读取文件"}]

async def main():
    async with stdio_server() as streams:
        await server.run(streams[0], streams[1])

2.4 优缺点分析

优势 劣势
✅ 实现最简单 ❌ 仅限本地通信
✅ 延迟极低(无网络开销) ❌ 无法跨机器
✅ 无需网络配置 ❌ Server 必须与 Client 同进程组
✅ 天然安全(无网络暴露) ❌ 难以做负载均衡
✅ 跨平台支持好 ❌ 不支持分布式部署

最佳适用场景:本地开发调试、桌面应用集成、单机部署


三、SSE(HTTP+SSE):传统网络方案

3.1 工作原理

SSE(Server-Sent Events)传输基于 HTTP 长连接实现服务器向客户端的推送:

  • 客户端通过 GET 请求建立 SSE 长连接
  • 服务器通过这条连接单向推送消息
  • 客户端通过 POST 请求发送消息到服务器
  • 通过 UUID 会话标识符管理连接

架构模式GET /sse 建立 SSE 流 + POST /messages/ 发送消息

3.2 架构流程图

┌─────────────────────────────────────────────────────────────┐
│                    SSE 传输架构                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   客户端                                 服务器              │
│     │                                     │                 │
│     │  1. GET /sse (建立 SSE 长连接)       │                 │
│     ├────────────────────────────────────►│                 │
│     │                                     │                 │
│     │◄────────────────────────────────────┤                 │
│     │  2. HTTP 200 + Content-Type:        │                 │
│     │     text/event-stream               │                 │
│     │     (长连接保持,等待推送)             │                 │
│     │                                     │                 │
│     │◄────────────────────────────────────┤                 │
│     │  3. 服务器推送消息                    │                 │
│     │     event: message                  │                 │
│     │     data: {"jsonrpc":"2.0"...}      │                 │
│     │                                     │                 │
│     │  4. POST /messages/ (发送消息)       │                 │
│     ├────────────────────────────────────►│                 │
│     │                                     │                 │
│     │◄────────────────────────────────────┤                 │
│     │  5. HTTP 202 Accepted               │                 │
│     │                                     │                 │
│     │  ... 持续双向通信 ...                │                 │
│     │                                     │                 │
└─────────────────────────────────────────────────────────────┘

3.3 代码示例

Server 端配置

from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route, Mount

sse = SseServerTransport("/messages/")

async def handle_sse(request):
    async with sse.connect_sse(
        request.scope, request.receive, request._send
    ) as streams:
        await app.run(streams[0], streams[1], app.create_initialization_options())
    return Response()

routes = [
    Route("/sse", endpoint=handle_sse, methods=["GET"]),
    Mount("/messages/", app=sse.handle_post_message),
]

app = Starlette(routes=routes)

Client 端连接

from mcp.client.sse import sse_client

async with sse_client(
    url="http://localhost:8000/sse",
    sse_read_timeout=300,  # SSE 读取超时,根据网络环境调整
    timeout=5              # HTTP 请求超时
) as (read_stream, write_stream):
    async with ClientSession(read_stream, write_stream) as session:
        await session.initialize()
        # ... 调用工具等操作

3.4 关键缺陷:为什么被替代?

2025 年 3 月 26 日,MCP 官方用 Streamable HTTP 替代了 HTTP+SSE,原因如下:

问题 具体表现
连接不可恢复 SSE 连接断开后,无法"从断点继续",只能重新开始,之前的上下文丢失
服务器压力大 必须维持高可用的长连接,一旦断开通信就中断
通信单向限制 服务器只能通过 SSE 通道推送消息,无法灵活响应
端点分离 需要 /sse/messages/ 两个端点,架构复杂

这些缺陷在生产环境中会导致:

  • 网络波动时用户体验差
  • 服务器资源消耗大
  • 难以做高可用部署

四、Streamable HTTP:新一代传输方案

4.1 核心设计思想

Streamable HTTP 不是传统意义上的"流式 HTTP",而是一种兼具以下特性的传输机制:

┌─────────────────────────────────────────────────────────────┐
│              Streamable HTTP 核心特性                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 统一端点:只需要一个 /mcp 端点,同时处理 POST 和 GET     │
│                                                             │
│  2. 灵活响应:服务器可选择返回普通 JSON 或升级为 SSE 流      │
│                                                             │
│  3. 无状态支持:无需维持长连接,适合无服务器架构              │
│                                                             │
│  4. 会话管理:可选的会话 ID 机制,支持断点恢复               │
│                                                             │
│  5. 事件重放:通过 Last-Event-ID 实现断线后消息重放          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.2 有状态 vs 无状态模式

无状态模式(Stateless):

请求流程:
POST /mcp (ToolListRequest)
        ↓
服务器直接返回 JSON 响应
        ↓
完成,无需保持任何状态

适用场景:简单工具查询、一次性请求

有状态模式(Stateful):

请求流程:
POST /mcp (InitializeRequest)
        ↓
服务器生成唯一会话 ID,通过 mcp-session-id 头返回
        ↓
客户端后续请求携带会话 ID
        ↓
服务器根据会话 ID 维护状态、实现断点恢复

适用场景:长时间交互、需要上下文的复杂任务

4.3 架构流程图

┌─────────────────────────────────────────────────────────────┐
│              Streamable HTTP 传输架构                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   客户端                                    服务器           │
│     │                                        │              │
│     │  1. POST /mcp (初始化)                 │              │
│     ├───────────────────────────────────────►│              │
│     │                                        │              │
│     │◄───────────────────────────────────────┤              │
│     │  2. 返回 mcp-session-id: abc123        │              │
│     │     (或直接返回 JSON,无会话)            │              │
│     │                                        │              │
│     │  3. POST /mcp (带会话 ID 发送请求)      │              │
│     │     Header: mcp-session-id: abc123     │              │
│     ├───────────────────────────────────────►│              │
│     │                                        │              │
│     │◄───────────────────────────────────────┤              │
│     │  4. 可选:返回 SSE 流(支持多消息)       │              │
│     │     或直接返回 JSON                     │              │
│     │                                        │              │
│     │  5. GET /mcp (可选,启动 SSE 流)        │              │
│     │     Header: Last-Event-ID: 5           │              │
│     ├───────────────────────────────────────►│              │
│     │                                        │              │
│     │◄───────────────────────────────────────┤              │
│     │  6. 从事件 6 开始重放                   │              │
│     │                                        │              │
│     │  7. DELETE /mcp (终止会话)             │              │
│     │     Header: mcp-session-id: abc123     │              │
│     ├───────────────────────────────────────►│              │
│     │                                        │              │
│     │◄───────────────────────────────────────┤              │
│     │  8. 会话清理完成                       │              │
│     │                                        │              │
└─────────────────────────────────────────────────────────────┘

4.4 会话恢复机制

这是 Streamable HTTP 最强大的特性之一:

# 客户端记录最后收到的事件 ID
last_event_id = "event-5"

# 连接断开后重连
headers = {
    "mcp-session-id": "abc123",
    "Last-Event-ID": last_event_id  # 告诉服务器从哪里继续
}

# 服务器重放事件 6、7、8...(不会重放其他流的消息)
# 这意味着断线期间的消息不会丢失

4.5 为什么不用 WebSocket?

官方团队曾探讨是否使用 WebSocket,但最终决定暂不采用:

考虑因素 WebSocket 的问题
RPC 风格需求 WebSocket 会引入不必要的连接维护开销
浏览器限制 无法像 HTTP 那样附加 Authorization 等请求头
升级复杂性 只有 GET 可升级为 WebSocket,POST 需要额外两步
兼容性碎片化 可选支持会导致客户端与服务器间兼容性组合爆炸

五、三种传输方式全面对比

5.1 架构差异

维度 stdio SSE Streamable HTTP
通信方式 进程间管道 HTTP 长连接 灵活 HTTP 请求
端点数量 2 个(/sse + /messages/ 1 个(/mcp
连接约束 本地进程 长连接必须保持 无状态可选
会话管理 通过 UUID 可选会话 ID
断点恢复 不支持 不支持 ✅ 支持

5.2 性能与成本

维度 stdio SSE Streamable HTTP
延迟 最低(无网络) 中(网络+长连接) 中(网络)
服务器资源 进程级 每连接独占 可共享
并发支持 一般 受长连接数限制 灵活扩展
负载均衡 不支持 复杂 ✅ 容易

5.3 安全机制

安全措施 stdio SSE Streamable HTTP
环境变量过滤 - -
DNS 重绑定保护 -
会话 ID 验证 -
Origin 头验证 - ✓ (必须)
CORS 配置 -

5.4 适用场景

传输方式 最佳适用场景 推荐指数
stdio 本地开发、桌面应用、单机部署 ⭐⭐⭐⭐⭐ 简单场景首选
SSE 兼容旧客户端、浏览器集成 ⭐⭐ 已逐步淘汰
Streamable HTTP 云端部署、分布式架构、微服务 ⭐⭐⭐⭐⭐ 生产环境首选

六、实战选型指南

6.1 决策流程图

你的应用场景是什么?
        │
        ├──► 本地开发、桌面工具
        │         │
        │         ▼
        │    使用 stdio ✓
        │
        ├──► 云端部署、需要网络通信
        │         │
        │         ▼
        │    需要高可用、断点恢复?
        │         │
        │         ├── 否 → 无状态 Streamable HTTP
        │         │
        │         └── 是 → 有状态 Streamable HTTP
        │
        └──► 兼容旧版客户端
                  │
                  ▼
             SSE(过渡方案)

6.2 常见配置示例

本地开发(stdio)

// Claude Desktop 配置
{
  "mcpServers": {
    "my-local-server": {
      "command": "python",
      "args": ["-m", "my_server"],
      "env": {"DEBUG": "true"}
    }
  }
}

云端部署(Streamable HTTP)

# Server 端
from mcp.server.streamable_http import StreamableHTTPServerTransport

transport = StreamableHTTPServerTransport(
    mcp_session_id=uuid4().hex,  # 可选,有状态模式
    event_store=my_event_store,   # 支持事件重放
)

# Client 端
from mcp.client.streamable_http import streamablehttp_client

# 注意:具体 API 以官方 SDK 版本为准
async with streamablehttp_client("http://api.example.com/mcp") as client:
    await client.initialize()
    # ...

6.3 迁移建议

如果你正在使用旧的 SSE 方案,建议逐步迁移:

  1. 新项目直接使用 Streamable HTTP
  2. 现有项目添加新端点 /mcp,保留旧端点供兼容
  3. 客户端优先尝试新端点,失败后 fallback 到旧端点
  4. 完成迁移后下线旧端点

总结

核心要点回顾

  1. stdio 最简单:本地进程通信,延迟最低,适合开发调试
  2. SSE 有缺陷:长连接脆弱、断线无法恢复,已被官方淘汰
  3. Streamable HTTP 是未来:灵活、健壮、支持断点恢复,生产环境首选

技术演进的意义

从 HTTP+SSE 到 Streamable HTTP 的演进,体现了 MCP 协议从"能用"到"好用"的转变:

  • 无状态服务器支持 → 降低部署成本
  • 统一端点 → 简化架构
  • 会话管理与断点恢复 → 提升用户体验

给开发者的建议

  1. 本地开发用 stdio:最简单,开箱即用
  2. 生产环境用 Streamable HTTP:高可用、可扩展
  3. 理解底层原理:遇到问题时能快速定位
  4. 关注协议更新:MCP 还在快速发展中

参考资料


本文是 MCP 学习系列第四篇,首发于 CSDN。理解传输协议是深入掌握 MCP 的关键一步,希望本文能帮你建立扎实的技术基础。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐