JSON-RPC 2.0 协议深度解析:从原理到 MCP 传输层实现

深入解析 JSON-RPC 2.0 协议规范,手把手实现 Node.js 服务端与客户端,对比 REST/gRPC/WebSocket,揭秘 MCP 协议底层传输原理。含完整代码示例与生产避坑指南。

API 设计 2026-06-06 12 分钟

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 定义了标准化的错误对象,包含 codemessage 和可选的 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 的基础上增加了以下约定:

  1. 能力协商:客户端和服务器在 initialize 请求中交换各自支持的能力
  2. 双向通信:虽然 JSON-RPC 本身是单向的,但 MCP 通过 SSE(Server-Sent Events)实现了服务器到客户端的通知
  3. 工具调用: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:虽然规范允许,但 null id 的语义不明确,容易导致客户端混淆
  • 不要在 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 是一个值得认真考虑的选择。本文的实现代码可以直接作为项目起点,根据实际需求扩展中间件、认证、限流等功能。


相关工具推荐:

📚 相关文章