MCP 为什么不用 HTTP 或 gRPC?
作者:程序员马丁
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
问题的本质:MCP 要解决什么问题?
在讨论技术选型之前,我们需要先明确 MCP(Model Context Protocol)要解决的核心问题。
MCP 的目标是让大模型能够以统一、标准、可互操作的方式调用外部工具能力。这意味着:
- 不同语言实现的 MCP Server(Python、Java、Node.js)都能被同一个 MCP Client 调用
- 不同厂商的大模型(OpenAI、Anthropic、本地模型)都能用相同方式调用工具
- 工具的调用方式应该是明确、可预测的,而不是各自为政
要实现这个目标,MCP 需要的不是如何传输数据,而是如何表达一次调用。
HTTP、gRPC 和 JSON-RPC 2.0 的定位差异
1. HTTP:传输协议,不是调用协议
HTTP 是一个传输层协议,它定义了:
- 如何建立连接(TCP/TLS)
- 如何发送请求(GET、POST、PUT、DELETE)
- 如何返回响应(状态码、Header、Body)
但 HTTP 不定义:
- 方法名怎么表示?(是放在 URL 路径?还是 Body 里?)
- 参数怎么传递?(Query String?JSON Body?Form Data?)
- 错误怎么表达?(HTTP 状态码?还是 Body 里的错误对象?)
- 通知消息(不需要响应的调用)怎么处理?
这导致基于 HTTP 的 API 设计千差万别:
- RESTful API:
POST /users、GET /users/123 - RPC-style API:
POST /api+{"method": "getUser", "params": {...}} - GraphQL:
POST /graphql+ Query DSL
每种风格都有自己的约定,缺乏统一标准。
2. gRPC:强类型 RPC 框架,但过于重量级
gRPC 是一个完整的 RPC 框架,它提供了:
- 基于 Protocol Buffers 的强类型接口定义
- HTTP/2 传输层
- 流式调用支持
- 多语言代码生成
gRPC 的优势在于性能和类型安全,但它也带来了额外的复杂度:
- 需要预先定义 .proto 文件:每个工具都要写 Protocol Buffers 定义,增加了开发成本
- 强依赖代码生成:客户端和服务端都需要生成代码,动态调用不方便
- 二进制协议不易调试:无法直接用 curl 或浏览器测试,必须用专门工具
- HTTP/2 依赖:部分环境(如浏览器、某些代理)对 HTTP/2 支持不完善
对于 MCP 这种需要轻量、灵活、易于调试的场景,gRPC 显得过于重量级。
3. JSON-RPC 2.0:协议层标准,专注消息格式
JSON-RPC 2.0 的定位与 HTTP、gRPC 都不同,它是一个消息格式规范,只定义:
- 一次调用的请求结构:
{"jsonrpc": "2.0", "method": "...", "params": {...}, "id": 1} - 成功响应的结构:
{"jsonrpc": "2.0", "result": {...}, "id": 1} - 错误响应的结构:
{"jsonrpc": "2.0", "error": {...}, "id": 1} - 通知消息的结构:
{"jsonrpc": "2.0", "method": "...", "params": {...}}(无id)
它不绑定传输层,可以跑在:
- HTTP 之上(最常见)
- WebSocket 之上(实时通信)
- stdio 之上(进程间通信)
- 任何能传输 JSON 的通道
MCP 选择 JSON-RPC 2.0 的核心原因
1. 统一的消息格式
JSON-RPC 2.0 提供了一套明确、标准、无歧义的消息结构:
// 请求
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {"city": "Beijing"}
},
"id": 1
}
// 成功响应
{
"jsonrpc": "2.0",
"result": {
"content": [{"type": "text", "text": "北京今天晴,25°C"}]
},
"id": 1
}
// 错误响应
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": "city is required"
},
"id": 1
}
无论是 Python 实现的 MCP Server,还是 Java 实现的 MCP Client,都能按照完全相同的方式解析和构造消息。
2. 轻量且易于实现
JSON-RPC 2.0 的规范非常简洁(完整规范只有几页),核心概念只有 4 个:
- Request Object
- Response Object
- Notification
- Error Object
任何语言只需要:
- 能解析 JSON
- 能构造 JSON
- 理解 4 种消息结构
就能实现一个完整的 JSON-RPC 2.0 客户端或服务端。不需要代码生成、不需要复杂的框架、不需要学习新的 IDL。
3. 人类可读,易于调试
JSON-RPC 2.0 使用纯文本 JSON 格式,可以直接:
- 用
curl测试:curl -X POST http://localhost:3000 -d '{"jsonrpc":"2.0","method":"ping","id":1}' - 在浏览器 DevTools 中查看请求和响应
- 用任何文本编辑器编辑和调试
- 在日志中直接阅读,无需反序列化
相比之下,gRPC 的 Protocol Buffers 是二进制格式,必须用专门工具才能查看。
4. 传输层无关,灵活性高
MCP 的使用场景多样:
- 本地工具调用:大模型通过 stdio 调用本地进程(如 Python 脚本)
- 远程工具调用:大模型通过 HTTP 调用远程服务
- 实时通信:大模型 通过 WebSocket 与工具保持长连接
JSON-RPC 2.0 不绑定传输层,可以适配所有这些场景。而 gRPC 强依赖 HTTP/2,无法用于 stdio 场景。
5. 支持通知消息(Notification)
MCP 中有些场景不需要响应,例如:
- 日志上报:工具向大模型发送日志,不需要等待确认
- 进度更新:工具向大模型报告任务进度,不需要响应
JSON-RPC 2.0 原生支持 Notification(不带 id 的请求),服务端不会返回响应,减少了不必要的网络开销。
HTTP 和 gRPC 都没有原生的单向消息概念,需要额外设计。
6. 批量调用支持
JSON-RPC 2.0 支持 Batch Request,可以一次发送多个请求:
[
{"jsonrpc": "2.0", "method": "tools/list", "id": 1},
{"jsonrpc": "2.0", "method": "resources/list", "id": 2},
{"jsonrpc": "2.0", "method": "prompts/list", "id": 3}
]
服务端可以并发处理,减少网络往返次数。这对于大模型一次性获取多个工具信息非常有用。
JSON-RPC 2.0 的局限性
当然,JSON-RPC 2.0 也不是完美的,它有一些明确的局限性:
1. 不包含传输层细节
JSON-RPC 2.0 只定义消息格式,不管:
- 如何建立连接(TCP?WebSocket?)
- 如何处理超时和重试
- 如何做负载均衡
- 如何做服务发现
这些需要在 MCP 的实现层或基础设施层补齐。
2. 不包含鉴权机制
JSON-RPC 2.0 不定义如何鉴权,需要在传输层(如 HTTP Header)或应用层(如在 params 中加 auth 字段)自行实现。
3. 不包含类型定义
JSON-RPC 2.0 不强制类型检查,参数和返回值都是动态的 JSON。这意味着:
- 需要在运行时校验参数类型
- 无法在编译期发现类型错误
- 需要额外的文档或 Schema 定义(如 JSON Schema)
MCP 通过在协议层定义 Schema(如工具的 inputSchema)来弥补这一点。比如:
{
"name": "get_weather",
"description": "获取天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
}
4. 性能不如二进制协议
JSON 是文本格式,序列化和反序列化开销比 Protocol Buffers 大。但对于 MCP 的使用场景(大模型调用工具),这点性能差异通常可以忽略。
总结:为什么是 JSON-RPC 2.0?
MCP 选择 JSON-RPC 2.0,本质上是在标准化、简单性、灵活性之间做了权衡:
| 维度 | HTTP | gRPC | JSON-RPC 2.0 |
|---|---|---|---|
| 定位 | 传输协议 | 完整 RPC 框架 | 消息格式规范 |
| 消息格式标准化 | ❌ 无统一标准 | ✅ Protocol Buffers | ✅ 统一 JSON 结构 |
| 实现复杂度 | ⚠️ 需自行设计消息格式 | ⚠️ 需 .proto + 代码生成 | ✅ 简单,只需解析 JSON |
| 人类可读性 | ⚠️ 取决于设计 | ❌ 二进制格式 | ✅ 纯文本 JSON |
| 传输层灵活性 | ⚠️ 仅 HTTP | ❌ 强依赖 HTTP/2 | ✅ 传输层无关 |
| 通知消息支持 | ❌ 需自行设计 | ⚠️ 需用流式 API | ✅ 原生支持 Notification |
| 批量调用支持 | ❌ 需自行设计 | ⚠️ 需用流式 API | ✅ 原生支持 Batch |
| 类型安全 | ❌ 无 | ✅ 强类型 | ❌ 动态类型(需额外 Schema) |
| 性能 | ⚠️ 取决于实现 | ✅ 高性能 | ⚠️ JSON 序列化开销 |
对于 MCP 这种需要跨语言、跨环境、易于调试、快速迭代的协议,JSON-RPC 2.0 是最合适的选择。
它不是最快的(gRPC 更快),也不是最灵活的(HTTP 可以自由设计),但它是最标准、最简单、最容易互操作的。
这正是 MCP 需要的:让不同实现之间能够无缝通信,而不是追求极致性能或极致灵活性。