Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

MCP 协议入门:JSON RPC 的实现

作者:oasis


根据 JSON RPC 的规范,一个完整的 JSON RPC 实现可以分为三个核心步骤:

  1. 服务端注册:服务端维护一个方法注册表,将可调用的函数名和对应的处理函数进行映射
  2. 客户端发现:客户端在启动时通过特定的方法(如 listMethods)从服务端拉取所有已注册的函数列表
  3. 动态代理:客户端根据拉取到的函数列表,动态创建对应的代理函数,使得可以像调用本地函数一样调用远程函数

这种设计实现了服务端和客户端的解耦,客户端无需预先知道服务端提供了哪些函数,而是通过运行时发现机制动态获取。基本流程的代码如下:

// 服务端
class Server {
  // 用于函数的注册
  methods = new Map();

  // 注册函数
  registry(name, handler) {
    this.methods.set(name, handler);
  }

  // 处理 JSON RPC 请求
  async handleRequest(request) {
    const { method, params, id } = request;

    if (!this.methods.has(method)) {
      return {
        jsonrpc: "2.0",
        error: { code: -32601, message: "Method not found" },
        id,
      };
    }

    try {
      const handler = this.methods.get(method);
      const result = await handler(...params);

      return {
        jsonrpc: "2.0",
        result,
        id,
      };
    } catch (error) {
      return {
        jsonrpc: "2.0",
        error: { code: -32603, message: error.message },
        id,
      };
    }
  }
}

const server = new Server();

server.registry("functioncall", async (param1, param2) => {
  // 函数实现
  return { result: `处理 ${param1}${param2}` };
});
// 客户端
class Client {
  // 构造函数,接收服务端地址
  constructor(url) {
    this.url = url;
    this.methods = new Set(); // 存储已拉取的函数名
  }

  // 获取注册函数
  async pull() {
    // 调用服务端的 listMethods 方法获取所有注册的函数
    const response = await this.send("listMethods", []);

    if (response.result) {
      // 将拉取到的函数名注册到客户端
      response.result.forEach((methodName) => {
        this.registry(methodName);
      });
    }
  }

  // 动态注册函数
  registry(methodName) {
    if (this.methods.has(methodName)) {
      return; // 已注册,跳过
    }

    this.methods.add(methodName);

    // 动态创建可调用函数
    this[methodName] = async (...params) => {
      return await this.send(methodName, params);
    };
  }

  // 包裹函数
  // 用于发送请求
  async send(method, params) {
    const request = {
      jsonrpc: "2.0",
      method,
      params,
      id: Date.now() + Math.random(), // 生成唯一 ID
    };

    const response = await fetch(this.url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(request),
    });

    const data = await response.json();

    if (data.error) {
      throw new Error(data.error.message);
    }

    return data;
  }
}

const client = new Client("http://localhost:3000/rpc");

// 启动时拉取远程注册的函数
await client.pull();

// 动态调用函数
const result = await client.functioncall("param1", "param2");

参考

JSON-RPC 2.0 Transport: HTTP