2026 年,随着 Model Context Protocol(MCP)成为 AI Agent 与外部工具交互的事实标准,JSON-RPC 2.0 这个诞生于 2005 年的协议再次回到开发者视野。全球最大的智能合约平台 Ethereum 每天处理超过 100 万次 JSON-RPC 调用,VS Code 的语言服务协议(LSP)底层同样依赖 JSON-RPC 2.0 进行通信。然而,大多数开发者对这个协议的理解仅停留在「用 HTTP 发 JSON」的层面。本文将从协议规范出发,手写一个完整的 JSON-RPC 服务器与客户端,并深入剖析 MCP 协议如何将 JSON-RPC 2.0 作为其核心传输层。
🔧 一、JSON-RPC 2.0 协议规范详解
JSON-RPC 2.0 是一种无状态的远程过程调用(Remote Procedure Call)协议,使用 JSON 作为数据格式。它的设计哲学极其简洁:一个请求只需要 method 和 params,一个响应只需要 result 或 error。
1.1 请求与响应格式
一个标准的 JSON-RPC 2.0 请求包含四个字段:
// JSON-RPC 2.0 标准请求格式
{
"jsonrpc": "2.0",
"method": "getUserById",
"params": { "id": 42 },
"id": 1
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
jsonrpc |
string | ✅ | 固定值 "2.0",用于协议版本校验 |
method |
string | ✅ | 要调用的方法名,以 rpc. 开头的方法名保留给协议内部使用 |
params |
array/object | ❌ | 方法参数,可以是位置参数数组或命名参数对象 |
id |
string/number/null | ✅ | 请求标识符,用于匹配请求与响应。null 或省略表示通知 |
成功响应的格式:
// JSON-RPC 2.0 成功响应
{
"jsonrpc": "2.0",
"result": { "name": "张三", "email": "zhangsan@example.com" },
"id": 1
}
📌 记住:
id字段是 JSON-RPC 的核心设计。它使得多个请求可以并发发送,客户端通过id将响应与请求一一对应。这与 HTTP 的请求-响应模型完全不同——在 JSON-RPC 中,请求和响应的顺序不需要保持一致。
1.2 通知(Notification)
当请求的 id 字段被省略时,该请求就是一个通知(Notification)。通知是「发送即忘」(fire-and-forget)的——服务器不需要返回响应。
// 通知示例:记录日志,不需要返回值
{
"jsonrpc": "2.0",
"method": "logEvent",
"params": { "level": "info", "message": "用户登录成功" }
}
⚠️ **警告:**通知没有
id字段,因此即使服务器处理失败,客户端也无法得知。在需要可靠性的场景中,永远不要使用通知来传递关键业务逻辑,应该使用带id的普通请求并检查响应中的错误。
1.3 批量调用(Batch)
JSON-RPC 2.0 支持在单个 HTTP 请求中发送多个调用,服务器以数组形式返回所有响应。这是 JSON-RPC 相比 REST API 的一个显著优势——一次网络往返即可完成多个操作。
// 批量请求:一次调用获取用户信息和权限
[
{ "jsonrpc": "2.0", "method": "getUser", "params": [42], "id": 1 },
{ "jsonrpc": "2.0", "method": "getPermissions", "params": [42], "id": 2 },
{ "jsonrpc": "2.0", "method": "logAccess", "params": { "userId": 42 } }
]
响应同样是一个数组(顺序不必与请求一致):
// 批量响应
[
{ "jsonrpc": "2.0", "result": { "name": "张三" }, "id": 1 },
{ "jsonrpc": "2.0", "result": ["read", "write"], "id": 2 }
]
注意第三个请求是通知(无 id),服务器不会为其生成响应。
1.4 错误处理机制
JSON-RPC 2.0 定义了标准化的错误对象,包含 code、message 和可选的 data 字段:
// JSON-RPC 2.0 错误响应
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found",
"data": { "method": "unknownMethod" }
},
"id": 1
}
协议预定义了以下错误码:
| 错误码 | 名称 | 说明 |
|---|---|---|
-32700 |
Parse error | JSON 解析失败 |
-32600 |
Invalid Request | 请求不符合 JSON-RPC 2.0 规范 |
-32601 |
Method not found | 方法不存在 |
-32602 |
Invalid params | 参数无效 |
-32603 |
Internal error | 内部错误 |
-32000 至 -32099 |
Server error | 保留给服务器自定义错误 |
💡 **提示:**自定义业务错误码应使用
-32000到-32099的保留范围。例如-32001表示「用户不存在」,-32002表示「权限不足」。这比 REST API 的 HTTP 状态码更加灵活——你可以定义任意数量的业务错误类型。
🚀 二、从零实现 JSON-RPC 2.0 服务器
下面我们用 Node.js 从零构建一个符合规范的 JSON-RPC 2.0 服务器。完整实现包括请求解析、方法路由、批量调用支持和错误处理。
2.1 核心服务器实现
// json-rpc-server.js — 完整的 JSON-RPC 2.0 服务器实现
import http from 'node:http';
class JsonRpcServer {
constructor() {
this.methods = new Map();
this.errorCodes = {
PARSE_ERROR: { code: -32700, message: 'Parse error' },
INVALID_REQUEST: { code: -32600, message: 'Invalid Request' },
METHOD_NOT_FOUND: { code: -32601, message: 'Method not found' },
INVALID_PARAMS: { code: -32602, message: 'Invalid params' },
INTERNAL_ERROR: { code: -32603, message: 'Internal error' },
};
}
// 注册 RPC 方法
register(name, handler) {
this.methods.set(name, handler);
}
// 处理单个请求
async handleRequest(request) {
// 校验基本结构
if (!request || typeof request !== 'object' || request.jsonrpc !== '2.0') {
return this.createError(null, this.errorCodes.INVALID_REQUEST);
}
const { method, params, id } = request;
// 校验 method 字段
if (typeof method !== 'string') {
return this.createError(id, this.errorCodes.INVALID_REQUEST);
}
// 查找注册的方法
const handler = this.methods.get(method);
if (!handler) {
return this.createError(id, this.errorCodes.METHOD_NOT_FOUND);
}
// 通知(无 id)不需要返回响应
const isNotification = id === undefined;
try {
const result = await handler(params);
if (isNotification) return null;
return { jsonrpc: '2.0', result, id };
} catch (err) {
if (isNotification) return null;
// 支持业务错误码
if (err.code) {
return { jsonrpc: '2.0', error: { code: err.code, message: err.message, data: err.data }, id };
}
return this.createError(id, this.errorCodes.INTERNAL_ERROR, err.message);
}
}
createError(id, { code, message }, data) {
const error = { code, message };
if (data) error.data = data;
return { jsonrpc: '2.0', error, id };
}
// 启动 HTTP 服务器
listen(port = 3000) {
const server = http.createServer(async (req, res) => {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Only POST method allowed' }));
return;
}
let body = '';
for await (const chunk of req) body += chunk;
let parsed;
try {
parsed = JSON.parse(body);
} catch {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(this.createError(null, this.errorCodes.PARSE_ERROR)));
return;
}
// 批量请求处理
const isBatch = Array.isArray(parsed);
const requests = isBatch ? parsed : [parsed];
const results = await Promise.all(requests.map(r => this.handleRequest(r)));
const responses = results.filter(Boolean); // 过滤掉通知的 null 响应
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(isBatch ? responses : responses[0] || ''));
});
server.listen(port, () => console.log(`JSON-RPC server on :${port}`));
return server;
}
}
export { JsonRpcServer };
2.2 注册业务方法并启动服务
// server.js — 注册业务方法并启动
import { JsonRpcServer } from './json-rpc-server.js';
const rpc = new JsonRpcServer();
// 注册一个简单的求和方法
rpc.register('add', (params) => {
const [a, b] = Array.isArray(params) ? params : [params.a, params.b];
if (typeof a !== 'number' || typeof b !== 'number') {
const err = new Error('参数必须是数字');
err.code = -32602;
throw err;
}
return a + b;
});
// 注册异步方法:模拟数据库查询
rpc.register('getUser', async (params) => {
const { id } = params;
// 模拟数据库查询延迟
await new Promise(r => setTimeout(r, 50));
if (id === 999) {
const err = new Error('用户不存在');
err.code = -32001;
err.data = { userId: id };
throw err;
}
return { id, name: `用户${id}`, email: `user${id}@example.com` };
});
// 注册通知方法:无需返回值
rpc.register('logEvent', (params) => {
console.log(`[LOG] ${params.level}: ${params.message}`);
});
rpc.listen(3000);
2.3 客户端实现与批量调用
// rpc-client.js — JSON-RPC 2.0 客户端,支持批量调用
class JsonRpcClient {
constructor(url) {
this.url = url;
this.id = 0;
}
async call(method, params) {
const request = {
jsonrpc: '2.0',
method,
params,
id: ++this.id,
};
const response = await this.send(request);
if (response.error) {
const err = new Error(response.error.message);
err.code = response.error.code;
err.data = response.error.data;
throw err;
}
return response.result;
}
// 发送通知(无返回值)
notify(method, params) {
this.send({ jsonrpc: '2.0', method, params });
}
// 批量调用:一次 HTTP 请求执行多个操作
async batch(calls) {
const requests = calls.map(({ method, params }, i) => ({
jsonrpc: '2.0',
method,
params,
id: ++this.id,
}));
const responses = await this.send(requests);
return responses.map(res => {
if (res.error) throw new Error(`${res.error.message} (code: ${res.error.code})`);
return res.result;
});
}
async send(payload) {
const res = await fetch(this.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
return res.json();
}
}
// 使用示例
const client = new JsonRpcClient('http://localhost:3000');
// 单个调用
const sum = await client.call('add', { a: 10, b: 20 });
console.log('10 + 20 =', sum); // 30
// 批量调用:一次请求获取多个结果
const [user, sum2] = await client.batch([
{ method: 'getUser', params: { id: 42 } },
{ method: 'add', params: [100, 200] },
]);
console.log(user); // { id: 42, name: '用户42', ... }
console.log(sum2); // 300
⚠️ **警告:**批量调用中如果某个请求失败,不会影响其他请求的执行。每个请求独立处理,只有失败的那个会在响应中包含
error字段。客户端需要逐个检查响应结果,不能假设「如果有一个失败就全部失败」。
📊 三、JSON-RPC vs REST vs gRPC vs WebSocket 全面对比
在选择 API 协议时,很多开发者会纠结。下表从实际工程角度进行对比:
| 维度 | JSON-RPC 2.0 | REST | gRPC | WebSocket |
|---|---|---|---|---|
| 数据格式 | JSON | JSON/XML | Protobuf(二进制) | 任意 |
| 传输协议 | HTTP/任意 | HTTP | HTTP/2 | WebSocket |
| 批量调用 | ✅ 原生支持 | ❌ 需自定义 | ✅ 流式支持 | ✅ 自定义消息 |
| 类型安全 | ❌ 无内置 | ❌ 需 JSON Schema | ✅ Proto 定义 | ❌ 无内置 |
| 双向通信 | ❌ 需轮询 | ❌ 需轮询 | ✅ 双向流 | ✅ 原生支持 |
| 浏览器支持 | ✅ 原生 | ✅ 原生 | ⚠️ 需 gRPC-Web | ✅ 原生 |
| 学习曲线 | ⭐ 低 | ⭐ 低 | ⭐⭐⭐ 高 | ⭐⭐ 中 |
| 调试难度 | ⭐ 低(纯文本) | ⭐ 低 | ⭐⭐⭐ 高(二进制) | ⭐⭐ 中 |
| 典型应用 | MCP、Ethereum、LSP | 通用 Web API | 微服务间通信 | 实时应用 |
⚡ 关键结论:JSON-RPC 2.0 的最大优势在于简洁性和批量调用。当你的 API 是面向「动作/操作」(RPC 风格)而非「资源」(REST 风格)时,JSON-RPC 是更自然的选择。MCP 协议选择 JSON-RPC 2.0 作为传输层,正是因为它足够简洁,让 AI Agent 可以轻松构造和解析请求。
🔐 四、JSON-RPC 2.0 在 MCP 协议中的应用
Model Context Protocol(MCP)是 2025-2026 年最热门的 AI 基础设施协议之一。它定义了 AI Agent(客户端)与外部工具/资源(服务器)之间的通信标准,而底层传输层正是 JSON-RPC 2.0。
4.1 MCP 如何使用 JSON-RPC 2.0
MCP 协议在 JSON-RPC 2.0 的基础上增加了以下约定:
- 能力协商:客户端和服务器在
initialize请求中交换各自支持的能力 - 双向通信:虽然 JSON-RPC 本身是单向的,但 MCP 通过 SSE(Server-Sent Events)实现了服务器到客户端的通知
- 工具调用:AI Agent 通过
tools/call方法调用服务器暴露的工具
一个典型的 MCP 工具调用流程:
// MCP 工具调用的 JSON-RPC 消息流
// 1. 客户端发送 initialize 请求
const initRequest = {
jsonrpc: '2.0',
method: 'initialize',
params: {
protocolVersion: '2025-03-26',
capabilities: { tools: {} },
clientInfo: { name: 'MyAgent', version: '1.0.0' }
},
id: 1
};
// 2. 服务器返回能力
const initResponse = {
jsonrpc: '2.0',
result: {
protocolVersion: '2025-03-26',
capabilities: { tools: { listChanged: true } },
serverInfo: { name: 'WeatherServer', version: '1.0.0' }
},
id: 1
};
// 3. 客户端列出可用工具
const listToolsRequest = {
jsonrpc: '2.0',
method: 'tools/list',
params: {},
id: 2
};
// 4. AI Agent 调用具体工具
const toolCallRequest = {
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'get_weather',
arguments: { city: '北京' }
},
id: 3
};
// 5. 工具执行结果
const toolCallResponse = {
jsonrpc: '2.0',
result: {
content: [{ type: 'text', text: '北京今日天气:晴,25°C,湿度 45%' }]
},
id: 3
};
4.2 MCP 的 Streamable HTTP 传输
MCP 最新的 Streamable HTTP 传输方式使用 HTTP POST 发送 JSON-RPC 请求,服务器可以通过 SSE 流式返回响应。这意味着单个 HTTP 连接可以承载多次请求-响应交互,甚至服务器主动推送通知:
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"search","arguments":{"query":"TypeScript 6"}},"id":1}
服务器响应(SSE 流):
event: message
data: {"jsonrpc":"2.0","result":{"content":[{"type":"text","text":"搜索结果..."}]},"id":1}
💡 **提示:**理解 JSON-RPC 2.0 是理解 MCP 协议的基础。如果你正在开发 MCP Server 或 Client,掌握本文介绍的 JSON-RPC 规范(请求/响应格式、批量调用、错误处理)将让你更高效地调试和优化 MCP 通信。
⚡ 五、生产环境最佳实践与避坑指南
在生产环境中使用 JSON-RPC 2.0,以下是经过实战验证的关键建议:
✅ 推荐做法
- 使用命名参数而非位置参数:命名参数
{ "a": 1, "b": 2 }比位置参数[1, 2]更易读、更易扩展,且不易出错。MCP 协议也推荐使用命名参数 - 为每个请求设置唯一的
id:使用递增整数或 UUID,确保客户端能正确匹配响应 - 实现请求超时机制:JSON-RPC 规范没有定义超时,需要在客户端自行实现。建议默认超时 30 秒
- 记录所有 RPC 调用日志:包括 method、params(脱敏后)、耗时、错误码,这对排查问题至关重要
- 使用 JSON Schema 校验参数:在方法执行前校验参数类型和格式,提前返回
-32602错误
❌ 避免做法
- ❌ 不要在通知中传递关键业务逻辑:通知没有确认机制,如果服务器处理失败,客户端无从得知
- ❌ 不要忽略批量调用中的单个错误:批量响应中每个结果独立,必须逐个检查
- ❌ 不要使用
null作为id:虽然规范允许,但nullid 的语义不明确,容易导致客户端混淆 - ❌ 不要在
method名中使用特殊字符:保持方法名为合法的标识符(字母、数字、下划线、点号)
⚠️ 安全注意事项
// ❌ 危险:直接将 method 映射到对象方法,可能导致原型链污染
const dangerousHandler = (method, params) => {
return targetObject[method](...params); // 如果 method 是 "__proto__" 呢?
};
// ✅ 安全:使用白名单校验方法名
const safeHandler = (method, params) => {
const allowed = new Set(['add', 'getUser', 'listUsers']);
if (!allowed.has(method)) {
throw { code: -32601, message: 'Method not found' };
}
return methods[method](params);
};
⚠️ **警告:**JSON-RPC 的
method字段是客户端可控的字符串,永远不要直接将其用作动态代码执行的入口(如eval()或无限制的方法派发)。必须使用白名单机制限制可调用的方法范围,防止远程代码执行(RCE)漏洞。
✅ 总结
JSON-RPC 2.0 是一个被低估的协议。它的设计简洁到几乎不需要学习成本,但功能上足以支撑 MCP、Ethereum、LSP 等重量级应用场景。相比 REST 的资源导向设计,JSON-RPC 更适合动作导向的 API;相比 gRPC 的高学习曲线,JSON-RPC 几乎零门槛。
选择 JSON-RPC 2.0 的场景:
- 🎯 构建 MCP Server,需要实现标准的 JSON-RPC 传输层
- 🎯 API 以「操作/动作」为主,而非 CRUD 资源
- 🎯 需要批量调用以减少网络往返
- 🎯 需要跨语言兼容(JSON 是所有语言都能处理的格式)
- 🎯 需要快速原型开发,不想引入复杂的 IDL(接口定义语言)
不推荐 JSON-RPC 2.0 的场景:
- ❌ 需要双向实时通信(应选择 WebSocket)
- ❌ 需要极致性能和强类型(应选择 gRPC + Protobuf)
- ❌ 面向公开开发者的 Web API(REST 的认知度更高)
如果你正在开发 MCP Server,或者需要构建一个轻量级的微服务通信层,JSON-RPC 2.0 是一个值得认真考虑的选择。本文的实现代码可以直接作为项目起点,根据实际需求扩展中间件、认证、限流等功能。
相关工具推荐:
- 🔧 jsjson.com JSON 格式化工具 — 在线格式化和校验 JSON-RPC 消息
- 🔧 jsjson.com JSON Schema 校验 — 使用 JSON Schema 校验 JSON-RPC 参数
- 🔧 jsjson.com JSON 转 TypeScript — 从 JSON-RPC 响应生成 TypeScript 类型定义