- MCP 底层消息协议是怎么实现的?
从 MCP 的官方文档可以看到
All transports use JSON-RPC 2.0 to exchange messages.
MCP 底层使用了 JSON-RPC 2.0 协议,它是一种轻量级的 RPC(远程过程调用)协议,使用 JSON(JavaScript Object Notation)作为数据格式。它的规范非常简单,www.jsonrpc.org/specificati… ,只有几个关键字,这也是 MCP 选择此协议的原因,在原来的基础上只需要很少的适配即可使用,更复杂的协议例如 protobuf,虽然效率更高,但是适配起来大量工作,调试也不如 Json 方便,在大模型发展日新月异的时代,MCP 还需要不断完善,它需要一个足够简单和通用的协议来快速推广。
然而直接使用 Json 容易出错,它过于灵活,需要封装一下,让它用起来和 protobuf 一样简单。MCP 官方开源了多种语言的 SDK,golang 有 mcp-go,常用的语言中 cpp 没有好用的 SDK,为了弥补这个空缺,我们使用 cpp 来实现JSON-RPC 2.0 协议
基本结构
JSON-RPC 2.0 的消息结构非常简单,主要分为请求(Request object)和响应(Response object)两个部分,多个请求和多个响应又组成批量(Batch)请求和批量(Batch)响应
请求
请求包含四个字段:
jsonrpc:指定 JSON-RPC 的版本,必须是字符串 "2.0"。method:要调用的方法名称,字符串类型。params:传递给方法的参数,可以是数组或对象。id:请求的唯一标识符,可以是字符串、数字或 null。服务器在响应时会返回相同的 id。
如果请求不包含id字段,那么它就是一个通知(Notification),通知不需要响应,定义请求
class Request {
public:
Request(std::string jsonrpc_version, std::string method, Parameter params, Identifier id);
Status ParseJson(const Json& json);
[[nodiscard]] Json ToJson() const;
[[nodiscard]] bool IsInternalMethod() const;
[[nodiscard]] bool IsNotification() const;
private:
std::string jsonrpc_version_ = kJsonRpcVersion;
std::string method_;
Parameter params_;
Identifier id_;
};
响应
用于服务端返回结果,分为正常和错误结果两种
正常结果
jsonrpc:协议版本,必须是字符串 "2.0"。result:调用方法的返回结果。id:与请求对象中的id一致,用于匹配响应。
错误结果
jsonrpc:协议版本,必须是字符串 "2.0"。error:包含错误信息的对象。code:标准错误码。message:错误描述信息。data:可选的额外错误信息。
id:如果无法解析请求或无法响应,则id设为null。
class Response {
public:
explicit Response(Identifier id) : id_(std::move(id)) {};
[[nodiscard]] Json ToJson() const;
void SetError(Error error) {
error_ = std::move(error);
}
void SetResult(Json result) {
result_ = std::move(result);
}
void SetId(Identifier id) {
id_ = std::move(id);
}
private:
std::string jsonrpc_version_ = kJsonRpcVersion;
Json result_;
Error error_;
Identifier id_;
};
可以看到响应和结果都必须包含协议版本等公共的字段,并且有特定的格式,封装可以减少很多重复工作,关键字可以定义一些常量,避免使用字符串可能用错不一致的问题
constexpr auto kJsonRpcVersionName = "jsonrpc";
constexpr auto kMethodName = "method";
constexpr auto kParamsName = "params";
constexpr auto kIdName = "id";
constexpr auto kResultName = "result";
constexpr auto kErrorName = "error";
constexpr auto kCodeName = "code";
constexpr auto kMessageName = "message";
constexpr auto kDataName = "data";