Model Context Protocol (MCP) 中的传输层为客户端和服务器之间的通信提供了基础。传输层处理消息发送和接收的底层机制。

消息格式

MCP 使用 JSON-RPC 2.0 作为其数据传输格式。传输层负责将 MCP 协议消息转换为 JSON-RPC 格式进行传输,并将接收到的 JSON-RPC 消息转换回 MCP 协议消息。 有三类使用的 JSON-RPC 消息:

请求

{
  jsonrpc: "2.0",
  id: number | string,
  method: string,
  params?: object
}

响应

{
  jsonrpc: "2.0",
  id: number | string,
  result?: object,
  error?: {
    code: number,
    message: string,
    data?: unknown
  }
}

通知

{
  jsonrpc: "2.0",
  method: string,
  params?: object
}

内建传输类型

MCP 目前定义了两套标准传输机制:

Standard Input/Output (stdio)

传输方式支持通过标准输入和输出流进行通信。这对于本地集成和命令行工具尤为有用。 使用标准输入输出当:
  • 构建命令行工具
  • 实现本地集成
  • 需要简单的进程通信
  • 使用 Shell 脚本

服务器

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);

客户端

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new StdioClientTransport({
  command: "./server",
  args: ["--option", "value"],
});
await client.connect(transport);

可流式HTTP

可流式HTTP传输使用HTTP POST请求进行客户端到服务器的通信,并可选使用服务器发送事件(SSE)流进行服务器到客户端的通信。 在以下情况下使用可流式HTTP:
  • 构建基于Web的集成
  • 需要通过HTTP进行客户端-服务器通信
  • 需要状态化会话
  • 支持多个并发客户端
  • 实现可恢复连接

工作原理

  1. 客户端到服务器通信: 从客户端到服务器的每条 JSON-RPC 消息都以新的 HTTP POST 请求形式发送至 MCP 端点
  2. 服务器响应: 服务器可以响应以下任一形式:
    • 单一JSON响应 (Content-Type: application/json)
    • 一个SSE流 (Content-Type: text/event-stream),用于多条消息
  3. 服务器与客户端通信: 服务器可以通过以下方式向客户端发送请求/通知:
    • 由客户端请求发起的SSE流
    • 来自HTTP GET请求的SSE流发送至MCP端点

服务器

import express from "express";

const app = express();

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

// MCP endpoint handles both POST and GET
app.post("/mcp", async (req, res) => {
  // Handle JSON-RPC request
  const response = await server.handleRequest(req.body);

  // Return single response or SSE stream
  if (needsStreaming) {
    res.setHeader("Content-Type", "text/event-stream");
    // Send SSE events...
  } else {
    res.json(response);
  }
});

app.get("/mcp", (req, res) => {
  // Optional: Support server-initiated SSE streams
  res.setHeader("Content-Type", "text/event-stream");
  // Send server notifications/requests...
});

app.listen(3000);

客户端

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new HttpClientTransport(new URL("http://localhost:3000/mcp"));
await client.connect(transport);

会话管理

可流式 HTTP 支持有状态的会话,以在多个请求之间维护上下文:
  1. 会话初始化: 服务器可能在初始化过程中通过包含一个Mcp-Session-Id头部来分配会话ID
  2. 会话持久化: 客户端必须在所有后续请求中通过 Mcp-Session-Id 标头包含会话 ID
  3. 会话终止: 可以通过发送带有会话 ID 的 HTTP DELETE 请求来明确终止会话
示例会话流程:
// Server assigns session ID during initialization
app.post("/mcp", (req, res) => {
  if (req.body.method === "initialize") {
    const sessionId = generateSecureId();
    res.setHeader("Mcp-Session-Id", sessionId);
    // Store session state...
  }
  // Handle request...
});

// Client includes session ID in subsequent requests
fetch("/mcp", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Mcp-Session-Id": sessionId,
  },
  body: JSON.stringify(request),
});

可恢复性与重传递

为了支持恢复中断的连接,Streamable HTTP 提供了:
  1. 事件ID: 服务器可以为SSE事件附加唯一ID用于追踪
  2. 从最后事件恢复: 客户端可以通过发送 Last-Event-ID 标头来恢复
  3. 消息重放: 服务器可以从断开连接点重放错过的消息
这确保了即使网络连接不稳定也能实现可靠的消息传递。

安全性考量

实施可流式HTTP传输时,请遵循以下安全最佳实践:
  1. 验证 Origin 头部: 始终验证所有传入连接的 Origin 头部,以防止 DNS 重绑定攻击
  2. 绑定到本地主机: 在本地运行时,仅绑定到本地主机 (127.0.0.1),而不是所有网络接口 (0.0.0.0)
  3. 实施认证: 对所有连接使用适当的认证机制
  4. 使用HTTPS: 生产环境部署时务必使用TLS/HTTPS协议
  5. 验证会话ID: 确保会话ID具有加密安全性并得到正确验证
没有这些保护措施,攻击者可能利用DNS重绑定从远程网站与本地MCP服务器进行交互。

服务器推送事件(SSE)- 已弃用

从协议版本2024-11-05起,SSE作为独立传输方式已被弃用。 它已被Streamable HTTP所取代,后者将SSE作为可选的流式传输机制。 关于向后兼容性的信息,请参见下方的 向后兼容性章节。
传统SSE传输通过HTTP POST请求实现服务器到客户端的数据流,用于客户端到服务器的通信。 之前的使用场景:
  • 仅需服务器到客户端的流式传输
  • 处理受限制的网络
  • 实施简单更新

传统安全考虑

已弃用的SSE传输协议与可流式HTTP传输具有类似的安全考虑,尤其是在DNS重绑定攻击方面。当在可流式HTTP传输中使用SSE流时,应应用这些相同的保护措施。

服务器

import express from "express";

const app = express();

const server = new Server(
  {
    name: "example-server",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

let transport: SSEServerTransport | null = null;

app.get("/sse", (req, res) => {
  transport = new SSEServerTransport("/messages", res);
  server.connect(transport);
});

app.post("/messages", (req, res) => {
  if (transport) {
    transport.handlePostMessage(req, res);
  }
});

app.listen(3000);

客户端

const client = new Client(
  {
    name: "example-client",
    version: "1.0.0",
  },
  {
    capabilities: {},
  },
);

const transport = new SSEClientTransport(new URL("http://localhost:3000/sse"));
await client.connect(transport);

自定义传输方式

MCP使针对特定需求实现自定义传输变得非常简单。任何传输实现只需符合传输接口: 您可以为以下内容实现自定义传输:
  • 自定义网络协议
  • 专用通信通道
  • 与现有系统集成
  • 性能优化
interface Transport {
  // Start processing messages
  start(): Promise<void>;

  // Send a JSON-RPC message
  send(message: JSONRPCMessage): Promise<void>;

  // Close the connection
  close(): Promise<void>;

  // Callbacks
  onclose?: () => void;
  onerror?: (error: Error) => void;
  onmessage?: (message: JSONRPCMessage) => void;
}

错误处理

传输实现应处理各种错误场景:
  1. 连接错误
  2. 消息解析错误
  3. 协议错误
  4. 网络超时
  5. 资源清理
错误处理示例:
class ExampleTransport implements Transport {
  async start() {
    try {
      // Connection logic
    } catch (error) {
      this.onerror?.(new Error(`Failed to connect: ${error}`));
      throw error;
    }
  }

  async send(message: JSONRPCMessage) {
    try {
      // Sending logic
    } catch (error) {
      this.onerror?.(new Error(`Failed to send message: ${error}`));
      throw error;
    }
  }
}

最佳实践

当实现或使用MCP传输时:
  1. 正确处理连接生命周期
  2. 实施适当的错误处理
  3. 连接关闭时清理资源
  4. 使用合适的超时设置
  5. 发送前验证消息
  6. 记录传输事件用于调试
  7. 在合适时实现重新连接逻辑
  8. 处理消息队列中的背压
  9. 监控连接健康状况
  10. 实施适当的安全措施

安全注意事项

在执行传输时:

身份验证与授权

  • 实施适当的身份验证机制
  • 验证客户端凭证
  • 使用安全的令牌处理
  • 实施授权检查

数据安全

  • 对网络传输使用TLS
  • 对敏感数据进行加密
  • 验证消息完整性
  • 实现消息大小限制
  • 清理输入数据

网络安全

  • 实施限流机制
  • 使用适当的超时设置
  • 处理拒绝服务场景
  • 监控异常模式
  • 实施适当的防火墙规则
  • 对于基于HTTP的传输(包括可流式HTTP),验证Origin头部以防止DNS重绑定攻击
  • 对于本地服务器,仅绑定到本地主机(127.0.0.1)而非所有接口(0.0.0.0)

调试传输

调试传输问题的技巧:
  1. 启用调试日志
  2. 监控信息流
  3. 校验连接状态
  4. 验证消息格式
  5. 测试错误场景
  6. 使用网络分析工具
  7. 实现健康检查
  8. 监控资源使用情况
  9. 测试边界情况
  10. 使用适当的错误追踪

向后兼容性

为确保不同协议版本之间的兼容性:

针对支持旧客户端的服务器

希望支持使用已废弃的HTTP+SSE传输方式的客户端的服务器应当:
  1. 同时托管旧版SSE和POST端点以及新版MCP端点
  2. 在两个端点上处理初始化请求
  3. 为每种传输类型维护独立的处理逻辑

针对支持旧服务器的客户端

想要支持使用已弃用传输方式的服务器的客户端应:
  1. 接受可使用任一传输协议的服务器URL
  2. 尝试使用正确的Accept头向InitializeRequest发送POST请求:
    • 如果成功,使用可流式 HTTP 传输
    • 如果失败并返回4xx状态,则回归到传统SSE传输模式
  3. 对传统服务器发出带SSE流期望获取endpoint事件的GET请求
示例兼容性检测:
async function detectTransport(serverUrl: string): Promise<TransportType> {
  try {
    // Try Streamable HTTP first
    const response = await fetch(serverUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json, text/event-stream",
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        method: "initialize",
        params: {
          /* ... */
        },
      }),
    });

    if (response.ok) {
      return "streamable-http";
    }
  } catch (error) {
    // Fall back to legacy SSE
    const sseResponse = await fetch(serverUrl, {
      method: "GET",
      headers: { Accept: "text/event-stream" },
    });

    if (sseResponse.ok) {
      return "legacy-sse";
    }
  }

  throw new Error("Unsupported transport");
}