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 目前定义了两套标准传输机制:
传输方式支持通过标准输入和输出流进行通信。这对于本地集成和命令行工具尤为有用。
使用标准输入输出当:
- 构建命令行工具
- 实现本地集成
- 需要简单的进程通信
- 使用 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进行客户端-服务器通信
- 需要状态化会话
- 支持多个并发客户端
- 实现可恢复连接
工作原理
- 客户端到服务器通信: 从客户端到服务器的每条 JSON-RPC 消息都以新的 HTTP POST 请求形式发送至 MCP 端点
- 服务器响应: 服务器可以响应以下任一形式:
- 单一JSON响应 (
Content-Type: application/json)
- 一个SSE流 (
Content-Type: text/event-stream),用于多条消息
- 服务器与客户端通信: 服务器可以通过以下方式向客户端发送请求/通知:
- 由客户端请求发起的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 支持有状态的会话,以在多个请求之间维护上下文:
- 会话初始化: 服务器可能在初始化过程中通过包含一个
Mcp-Session-Id头部来分配会话ID
- 会话持久化: 客户端必须在所有后续请求中通过
Mcp-Session-Id 标头包含会话 ID
- 会话终止: 可以通过发送带有会话 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 提供了:
- 事件ID: 服务器可以为SSE事件附加唯一ID用于追踪
- 从最后事件恢复: 客户端可以通过发送
Last-Event-ID 标头来恢复
- 消息重放: 服务器可以从断开连接点重放错过的消息
这确保了即使网络连接不稳定也能实现可靠的消息传递。
安全性考量
实施可流式HTTP传输时,请遵循以下安全最佳实践:
- 验证 Origin 头部: 始终验证所有传入连接的
Origin 头部,以防止 DNS 重绑定攻击
- 绑定到本地主机: 在本地运行时,仅绑定到本地主机 (127.0.0.1),而不是所有网络接口 (0.0.0.0)
- 实施认证: 对所有连接使用适当的认证机制
- 使用HTTPS: 生产环境部署时务必使用TLS/HTTPS协议
- 验证会话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;
}
错误处理
传输实现应处理各种错误场景:
- 连接错误
- 消息解析错误
- 协议错误
- 网络超时
- 资源清理
错误处理示例:
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传输时:
- 正确处理连接生命周期
- 实施适当的错误处理
- 连接关闭时清理资源
- 使用合适的超时设置
- 发送前验证消息
- 记录传输事件用于调试
- 在合适时实现重新连接逻辑
- 处理消息队列中的背压
- 监控连接健康状况
- 实施适当的安全措施
安全注意事项
在执行传输时:
身份验证与授权
- 实施适当的身份验证机制
- 验证客户端凭证
- 使用安全的令牌处理
- 实施授权检查
数据安全
- 对网络传输使用TLS
- 对敏感数据进行加密
- 验证消息完整性
- 实现消息大小限制
- 清理输入数据
网络安全
- 实施限流机制
- 使用适当的超时设置
- 处理拒绝服务场景
- 监控异常模式
- 实施适当的防火墙规则
- 对于基于HTTP的传输(包括可流式HTTP),验证Origin头部以防止DNS重绑定攻击
- 对于本地服务器,仅绑定到本地主机(127.0.0.1)而非所有接口(0.0.0.0)
调试传输
调试传输问题的技巧:
- 启用调试日志
- 监控信息流
- 校验连接状态
- 验证消息格式
- 测试错误场景
- 使用网络分析工具
- 实现健康检查
- 监控资源使用情况
- 测试边界情况
- 使用适当的错误追踪
向后兼容性
为确保不同协议版本之间的兼容性:
针对支持旧客户端的服务器
希望支持使用已废弃的HTTP+SSE传输方式的客户端的服务器应当:
- 同时托管旧版SSE和POST端点以及新版MCP端点
- 在两个端点上处理初始化请求
- 为每种传输类型维护独立的处理逻辑
针对支持旧服务器的客户端
想要支持使用已弃用传输方式的服务器的客户端应:
- 接受可使用任一传输协议的服务器URL
- 尝试使用正确的
Accept头向InitializeRequest发送POST请求:
- 如果成功,使用可流式 HTTP 传输
- 如果失败并返回4xx状态,则回归到传统SSE传输模式
- 对传统服务器发出带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");
}