第2章:MCP协议栈分析
好,咱们直接进入正题。MCP协议栈,说白了就是一套通信的规矩。你想想看,两个程序要对话,总得有个共同语言吧?MCP就是那个翻译官。我这些年做逆向分析,见过太多协议实现上的坑,MCP的设计其实挺巧妙的,但理解不透照样翻车。
2.1 MCP协议分层模型
MCP协议栈分四层,从下往上分别是:传输层、会话层、表示层、应用层。嗯,这里要注意,它跟OSI七层模型不太一样,MCP更轻量,专为AI工具链设计。
核心分层结构:
- 传输层:负责原始字节流的收发。我习惯用WebSocket或HTTP/2作为底层载体,实际项目中WebSocket更常见。
- 会话层:管理连接的生命周期。包括握手、心跳、重连。我曾经遇到过一个坑,会话超时时间设得太短,导致频繁断连。
- 表示层:数据序列化与反序列化。MCP默认用JSON,但支持Protobuf扩展。我个人建议生产环境用Protobuf,性能差不少。
- 应用层:业务逻辑。比如工具调用、资源访问、提示模板管理。
下面这张图是我手绘的分层模型,你一看就明白:
2.2 MCP协议数据包结构
MCP的数据包结构其实不复杂。每个包都是一个JSON对象,包含三个核心字段:type、id、payload。我刚开始逆向MCP流量时,就是靠这三个字段定位问题的。
{
"type": "request", // 消息类型:request/response/notification/error
"id": "msg-001", // 消息唯一标识,用于请求-响应配对
"payload": { // 具体业务数据
"method": "tools/call",
"params": {
"name": "calculator",
"arguments": {
"a": 10,
"b": 20
}
}
}
}
我的小技巧:做逆向分析时,先抓包看type字段。如果是notification,说明是单向消息,不需要回复。如果是request,一定要找到对应的response,否则说明连接有问题。
数据包头部还有几个隐藏字段,在传输层会附加:
| 字段 | 长度 | 说明 |
|---|---|---|
| magic | 4字节 | 固定为0x4D4350(即"MCP") |
| version | 2字节 | 主版本号+次版本号,当前为1.0 |
| length | 4字节 | payload长度(大端序) |
| checksum | 2字节 | CRC16校验,可选字段 |
注意:我曾经在分析一个MCP实现时,发现对方把length字段解析错了——用了小端序。结果整个数据包解析全乱套。所以做协议逆向时,字节序一定要先确认清楚。
2.3 MCP协议握手流程
握手是MCP连接的第一步,说白了就是双方互相确认身份和能力。我见过不少开发者跳过握手直接发业务请求,结果被服务端无情拒绝。
标准握手流程分三步:
- 客户端发送初始化请求:包含协议版本、客户端名称、支持的能力列表。
- 服务端返回初始化响应:确认版本、返回服务端能力、分配会话ID。
- 客户端发送初始化完成通知:告诉服务端"我准备好了"。
// 步骤1:客户端 -> 服务端
{
"type": "request",
"id": "init-1",
"payload": {
"method": "initialize",
"params": {
"protocolVersion": "1.0",
"clientInfo": {
"name": "my-ai-agent",
"version": "1.0.0"
},
"capabilities": {
"tools": true,
"resources": true,
"prompts": false
}
}
}
}
// 步骤2:服务端 -> 客户端
{
"type": "response",
"id": "init-1",
"payload": {
"protocolVersion": "1.0",
"serverInfo": {
"name": "mcp-server",
"version": "2.1.0"
},
"capabilities": {
"tools": true,
"resources": false
},
"sessionId": "sess-abc123"
}
}
// 步骤3:客户端 -> 服务端
{
"type": "notification",
"payload": {
"method": "initialized"
}
}
握手完成后,双方才能开始正常的业务通信。如果版本不匹配,服务端会返回错误,客户端需要降级或断开连接。
2.4 MCP协议状态机
MCP连接的状态机其实不复杂,但理解它对于排查问题特别重要。我画了个状态转换图,你一看就懂:
状态说明:
- CLOSED:初始状态,连接未建立。此时任何业务请求都会被丢弃。
- HANDSHAKE:正在握手。客户端发送
initialize后进入此状态。我遇到过一个问题:客户端发了握手请求但没收到响应,一直卡在这里。后来发现是防火墙把WebSocket升级请求拦截了。 - ACTIVE:正常通信状态。可以发送工具调用、资源请求等。这是最常用的状态。
- CLOSING:正在关闭连接。收到
close通知或发生超时后进入。此时不再处理新请求,但会尽力完成正在处理的请求。
避坑指南:我曾经在分析一个MCP客户端时,发现它从HANDSHAKE状态直接跳到了CLOSED,中间没有经过CLOSING。查了半天,原来是握手超时处理写错了,直接抛异常关闭了socket。正确的做法是先发一个error通知,再优雅关闭。
状态机里还有一个隐藏规则:在ACTIVE状态下,如果连续3次心跳没有收到回复,应该自动进入CLOSING状态。这个机制是为了防止死连接占用资源。我建议你在实现时把这个阈值做成可配置的,不同网络环境需求不一样。
好了,MCP协议栈的核心内容就这些。分层模型让你知道每一层干什么,数据包结构让你能读懂网络流量,握手流程确保双方能正常沟通,状态机帮你理解连接的生命周期。把这四个点吃透了,MCP协议对你来说就没有秘密了。