MCP 传输层与协议生命周期:stdio/HTTP/SSE 传输与 JSON-RPC 2.0

深入解析 MCP 的三种传输方式(stdio、SSE、Streamable HTTP)的选型对比,以及 initialize 握手、capability 协商、三类消息模型、协议状态机、错误码与重连机制,配合 JSON-RPC 2.0 消息样例与鉴权流式传输实战。

1. 传输层:MCP 的「血管」

如果把 MCP 比作 AI 应用的操作系统,那么传输层就是血管——它决定了客户端与服务器之间的消息如何流动,也决定了部署拓扑与安全边界。MCP 目前定义了三种传输方式:stdio、HTTP + SSE 与 Streamable HTTP。三者的共同底层是 JSON-RPC 2.0,但各自适合截然不同的场景。

一句话:传输层只解决「字节怎么走」,协议层才决定「消息怎么懂」;选错传输方式,等于在正确的协议上跑错了路。

1.1 三种传输方式一览

  • stdio(标准输入输出):客户端作为父进程启动服务器子进程,通过 stdin 发送请求、从 stdout 读取响应,stderr 专门留给日志。零网络依赖,启动快,安全性天然隔离。
  • HTTP + SSE(Server-Sent Events):客户端通过 GET /sse 建立服务端到客户端的事件流,再通过 POST /messages 反向提交消息。适合跨机器、跨网络部署,但双端点为治理带来负担。
  • Streamable HTTP(流式 HTTP):MCP 规范演进后的新传输,用单个 POST 端点统一请求与响应,响应可退化为 SSE 流。兼容性最好,正成为远程部署的事实标准。

2. 三种传输方式对比与选型

选型之前先看清差异。下表从开发、部署、安全三个维度做了完整对比:

维度stdioHTTP + SSEStreamable HTTP
通信通道stdin/stdoutGET /sse + POST /messages单 POST 端点
部署形态本地子进程独立 HTTP 服务独立 HTTP 服务
跨机器不支持支持支持
鉴权难度无(进程级信任)需 OAuth/Bearer需 OAuth/Bearer
流式输出原生支持SSE 支持SSE 支持
连接生命周期随进程长连接长连接
典型场景桌面客户端、CLI企业内网服务云托管、公网服务
复杂程度最低较高(双端点)中等

2.1 选型决策树

  • 只在本地跑(如 Claude Desktop、VS Code 插件):无脑选 stdio,进程隔离即安全边界。
  • 需要多台服务器共享一个 Agent:选 SSE 或 Streamable HTTP,把 MCP Server 部署为独立服务。
  • 面向公网或云托管:优先 Streamable HTTP,单端点配合鉴权中间件最简单。

一句话:本地用 stdio、内网用 SSE、公网用 Streamable HTTP——这是一条可以闭眼走的路。

3. JSON-RPC 2.0 消息模型

MCP 的所有传输都承载同一种消息格式:JSON-RPC 2.0。理解三类消息是理解整个协议生命周期的钥匙。

3.1 请求(Request)

带 id 的消息是请求,对方必须回 Response。method 可以是 tools/call、resources/read、initialize 等:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "city": "Beijing",
      "units": "celsius"
    }
  }
}

3.2 通知(Notification)

不带 id 的消息是通知,发送方不期待任何响应。典型的如握手后的 notifications/initialized,以及日志通知 notifications/message:

{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}

3.3 结果(Result)

响应由 id 关联到对应的请求。成功返回 result,失败返回 error:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "type": "text", "text": "{\"city\":\"Beijing\",\"temperature\":22}" }
    ]
  }
}
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32002,
    "message": "Tool not found: get_weathr"
  }
}

一句话:带 id 的是「提问必须等回答」,不带 id 的是「说完就走不回头」——这是 JSON-RPC 的两条铁律。

4. initialize 握手与 capability 协商

MCP 会话不是「连上就干活」。连接建立后的第一件事,永远是 initialize 握手:双方互相亮明身份与能力,随后客户端发送 notifications/initialized 通知,会话才算正式进入工作态。

4.1 握手消息示例

客户端先发 initialize:

{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": {
      "name": "my-app",
      "version": "1.0.0"
    }
  }
}

服务器回应自己的能力:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools": { "listChanged": true },
      "resources": { "subscribe": true },
      "prompts": {}
    },
    "serverInfo": { "name": "weather-server", "version": "1.0.0" }
  }
}

4.2 capability 协商规则

  • 双方以较低版本的 protocolVersion 作为会话版本,避免断崖式不兼容。
  • 服务器必须声明自己实现了哪些能力(tools、resources、prompts、logging、experimental),客户端只能调用被声明的能力。
  • 若服务器没有声明 resources,客户端再发 resources/read 就会收到 -32601(方法不存在)。
// TypeScript SDK 客户端在握手后自动完成协商
const client = new Client({ name: "test", version: "1.0" });
await client.connect(transport);

const serverCapabilities = client.getServerCapabilities();
console.log("服务器能力:", serverCapabilities);

// 判断后再决定是否调用资源能力
if (serverCapabilities.resources) {
  const { resources } = await client.listResources();
  console.log(resources.map((r) => r.uri));
}

一句话:握手是「自我介绍 + 亮出菜单」,菜单上没有的菜,点了也是 404。

5. 协议状态机

MCP 会话的生命周期可以抽象成一个小的状态机,几乎每种异常都能在状态迁移中找到根因。

状态触发事件关键约束
created传输连接建立尚不允许业务消息
initializing发出 initialize等待服务器 result
ready收到握手 result + 发送 notifications/initialized业务消息全开
disconnected传输中断需重新走握手
failed协议版本不兼容 / 非法消息记录原因,不可自动恢复

重连策略:所有传输都要求「重连即重新握手」。因此服务器端不能假设会话状态在重连后仍然有效;客户端也必须重新拉取工具列表,因为服务器可能在重启后换了一批工具。

6. 错误码与错误传播

JSON-RPC 2.0 预定义了通用错误码,MCP 在其之上补充了语义更细的错误码:

错误码名称含义应对
-32700Parse ErrorJSON 无法解析检查消息格式
-32600Invalid Request缺字段或类型错误校验参数
-32601Method Not Found方法不存在核对 method 拼写
-32602Invalid Params参数不合法用 schema 校验
-32603Internal Error服务器内部异常查日志堆栈
-32002Tool Not Found工具不存在检查工具列表
-32003Prompt Not Found提示词不存在检查 prompts 列表
-32004Resource Not Found资源不存在检查资源 URI
// 客户端对错误的统一处理
try {
  const result = await client.callTool({ name: "get_weather", arguments: { city: "Beijing" } });
  if (result.isError) {
    // 工具逻辑层错误,业务可感知
    console.warn("工具执行失败:", result.content);
  }
} catch (error) {
  // 协议层错误,如方法不存在、参数非法
  const code = (error as { code?: number }).code;
  if (code === -32602) {
    console.error("参数校验失败,请检查输入 schema");
  } else if (code === -32002) {
    console.error("工具不存在,请刷新工具列表");
  } else {
    console.error("未分类协议错误:", error);
  }
}

7. 鉴权与流式传输

远程传输把 MCP 服务暴露到了网络上,鉴权就不可避免。

7.1 鉴权方案

  • 本地信任(stdio):无需鉴权,进程启动权限即边界。
  • Bearer Token(SSE/HTTP):最简单,适合内部服务。
  • OAuth 2.0:官方推荐用于公网,通过授权端点换取访问令牌。

7.2 Streamable HTTP 流式响应

流式传输让工具可以「边算边给」。服务器对 tools/call 返回 Content-Type: text/event-stream,客户端逐行消费:

# Python 客户端读取流式响应
import requests
import json

with requests.post(
    "https://mcp.example.com/mcp",
    json={
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {"name": "stream_logs", "arguments": {}},
    },
    headers={"Authorization": "Bearer <token>"},
    stream=True,
) as resp:
    for line in resp.iter_lines():
        if line:
            event = json.loads(line.removeprefix("data: "))
            # 每行都是 JSON-RPC 消息或流片段
            print(event)

8. 传输层实战:一个可复用的选型模板

把上面的决策固化为配置文件,团队照表填写即可:

# mcp-transport-config.yaml
service:
  name: my-mcp-server
  version: "1.0.0"

transport:
  mode: streamable-http   # stdio | sse | streamable-http
  endpoint: /mcp
  port: 3000

security:
  auth: oauth2            # none | bearer | oauth2
  token_url: /oauth/token

streaming:
  enabled: true
  heartbeat_interval_ms: 15000

9. 总结

传输层是 MCP 工程化的第一道选择题,也是排查线上问题时的第一现场。全文核心收束如下:

主题关键结论常见误区
传输选型本地 stdio / 内网 SSE / 公网 Streamable HTTP认为 SSE 是唯一远程方案
消息模型带 id 必回、无 id 不回在 notification 上等响应
握手协商低版本兼容、能力先声明调用未声明的能力
状态机重连必须重新握手假设会话状态可延续
错误码协议层与业务层分开处理把所有失败都当业务错误
远程鉴权公网强制 OAuth/Bearer裸奔暴露到公网

掌握传输层后,下一步自然是把多个服务器接入同一个客户端——这正是「多服务器编排」要解决的问题。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「AI工程」更多文章

  1. MCP 生态全景:官方服务器、云托管与 A2A 对比
  2. MCP 可观测性与调试:从 mcp-inspector 到生产链路
  3. MCP 上下文工程与提示词资源管理