MCP项目笔记十(客户端 MCPClient)

C++ 实现MCP 客户端

Model Context Protocol(MCP) 是 Anthropic 推出的开放协议,旨在让 AI 模型能够标准化地调用外部工具、读取资源、执行 Prompt。本文结合 C++ 实现代码,讲解 MCP 客户端的架构设计与核心机制。


一、什么是 MCP?

MCP 是一套 AI 与外部工具之间的通信协议。它定义了三种核心能力:

  • Tools(工具):AI 可以调用的函数,例如"查询天气"、"执行代码"
  • Prompts(提示模板):预定义的提示词模板,支持参数化
  • Resources(资源):AI 可以读取的外部数据,例如文件、数据库条目

这套协议使用 JSON-RPC 2.0 作为消息格式,保证了跨语言、跨平台的互操作性。


二、整体架构

这份代码包含三个核心类,分工明确:

复制代码
MCPServiceIntegrator          ← 顶层集成器,统一对外暴露
    └── MCPClient             ← 传输层,负责与 MCP Server 通信
    └── MCPToolManager        ← 工具管理层,缓存工具列表、处理调用

传输层:两种通信模式

MCPClient 支持两种传输方式,由 MCPTransportType 枚举区分:

模式 适用场景 实现方式
STDIO 本地进程 fork() + pipe() + execv()
SSE 远程 HTTP 服务 libcurl + Server-Sent Events

三、STDIO 模式:如何与本地进程通信

核心流程

复制代码
父进程(MCPClient)          子进程(MCP Server)
       │                           │
       │──── stdin_pipe ──────────>│  写入 JSON-RPC 请求
       │                           │
       │<─── stdout_pipe ──────────│  读取 JSON-RPC 响应/通知

启动子进程(startMCPServer()

cpp 复制代码
// 1. 创建两对管道
pipe(stdin_pipe_fd);    // 父写 → 子读
pipe(stdout_pipe_fd);   // 子写 → 父读

// 2. fork 出子进程
server_pid_ = fork();

// 3. 子进程内:重定向 stdin/stdout 到管道
dup2(stdin_pipe_fd[0], STDIN_FILENO);
dup2(stdout_pipe_fd[1], STDOUT_FILENO);
execv(server_path_.c_str(), argv.data());

// 4. 父进程内:保存可用端,设置非阻塞读
stdin_pipe_ = stdin_pipe_fd[1];   // 可写端
stdout_pipe_ = stdout_pipe_fd[0]; // 可读端
fcntl(stdout_pipe_, F_SETFL, O_NONBLOCK);

通过 dup2() 将标准输入输出"嫁接"到管道,子进程完全感知不到自己在被远程控制------这正是 STDIO 传输的精妙之处。

后台读取线程(processNotificationsStdio()

连接建立后,主线程启动一个后台线程持续从 stdout_pipe_ 读取数据:

cpp 复制代码
notification_thread_ = std::thread([this]() {
    processNotificationsStdio();
});

读到完整的 \n 分隔行后,解析 JSON-RPC,判断是响应 还是通知,分别处理:

  • 响应 (有 id 字段)→ 推入 response_queue_,唤醒等待的调用方
  • 通知method == "notifications/message")→ 调用用户注册的回调

四、SSE 模式:如何与远程服务通信

SSE(Server-Sent Events)是一种基于 HTTP 的单向推送协议,非常适合服务端主动推送事件流。

整体设计

复制代码
MCPClient
  ├── HTTP POST ────────────────→  /message  (发送 JSON-RPC 请求)
  └── SSE 长连接 ←────────────────  /sse      (接收响应和通知)

请求与响应走两条独立通道:

  1. 通过 sendRequestSSE() 发一个普通 POST 请求
  2. 后台 SSE 监听线程(processNotificationsSSE())持续接收服务端推送的事件流
  3. sseWriteCallback() 解析 data: 行中的 JSON,分发到响应队列或通知回调

CURL 回调机制

cpp 复制代码
// CURL 每收到一段数据就调用这个静态函数
size_t MCPClient::sseWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
    MCPClient* client = static_cast<MCPClient*>(userdata);
    // 追加到缓冲区,寻找 "\n\n" 作为事件分隔符
    // 解析 "data: {...}" 行,提取 JSON 数据
    // 判断是响应还是通知,分别处理
    return size * nmemb;
}

五、请求-响应同步机制

无论哪种传输方式,请求和响应之间的同步都依赖同一套生产者-消费者模型:

cpp 复制代码
// 生产者(后台读取线程)
{
    std::lock_guard<std::mutex> lock(queue_mutex_);
    response_queue_.push(response);
    queue_cv_.notify_one();  // 唤醒等待方
}

// 消费者(调用方线程)
std::unique_lock<std::mutex> lock(queue_mutex_);
queue_cv_.wait_for(lock, std::chrono::seconds(30), [this] {
    return !response_queue_.empty();
});
MCPResponse response = response_queue_.front();
response_queue_.pop();

这是经典的 条件变量 + 互斥锁 模式,保证线程安全的同时,避免了忙等(busy-waiting)带来的 CPU 浪费。


六、JSON-RPC 消息构造

所有请求统一通过 buildJSONRPCRequest() 序列化:

json 复制代码
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "id": "call_tool_1712345678",
  "params": {
    "name": "search",
    "arguments": { "query": "MCP protocol" }
  }
}

响应通过 parseJSONRPCResponse() 反序列化,区分成功结果(result 字段)与错误(error.message 字段)。


std::atomic<bool> 的使用

connected_running_ 均使用原子类型,避免多线程读写时的数据竞争:

cpp 复制代码
std::atomic<bool> connected_{false};
std::atomic<bool> running_{false};

七、工具调用的完整链路

以调用一个工具为例,梳理完整的数据流:

复制代码
用户代码
  │
  ├─→ MCPToolManager::executeTool("search", "{\"query\":\"MCP\"}")
  │         │
  │         ├─→ MCPClient::callTool()
  │         │         │
  │         │         ├─→ 构造 MCPRequest {method="tools/call", ...}
  │         │         ├─→ sendRequest() → sendRequestStdio() → write(stdin_pipe_)
  │         │         │
  │         │         └─→ receiveResponse()  ← 阻塞等待(最多 30s)
  │         │                   ↑
  │         │      [后台线程] processNotificationsStdio()
  │         │                   │
  │         │              read(stdout_pipe_) → parseJSONRPCResponse()
  │         │                   │
  │         │              response_queue_.push() + queue_cv_.notify_one()
  │         │
  │         └─→ 返回 MCPResponse {result: "搜索结果..."}
  │
  └─→ 使用工具执行结果

八、总结

这份 C++ MCP 客户端实现涵盖了以下核心工程要点:

  • 进程间通信fork + pipe + dup2 + execv 构成 STDIO 模式的基础
  • HTTP 长连接:libcurl + SSE 事件流实现远程传输
  • 线程同步:条件变量 + 互斥锁保证响应的安全传递
  • 协议设计:JSON-RPC 2.0 统一请求/响应/通知的消息格式
  • 接口抽象IMCPClient 纯虚接口使传输层可替换、可测试

MCP 协议的魅力在于它把 AI 能力的扩展标准化了------无论是本地脚本还是远程服务,都能通过同一套接口被 AI 模型调用。随着 MCP 生态的成熟,这类客户端实现将成为 AI 应用开发的基础设施之一。

相关推荐
一只旭宝2 小时前
【C++ 入门精讲2】函数重载、默认参数、函数指针、volatile | 手写笔记(附完整代码)
c++·笔记
旖-旎2 小时前
哈希表(存在重复元素||)(4)
数据结构·c++·算法·leetcode·哈希算法·散列表
John.Lewis3 小时前
C++进阶(8)智能指针
开发语言·c++·笔记
yuhulkjv3353 小时前
AI导出的Excel公式失效
人工智能·ai·chatgpt·excel·豆包·deepseek·ai导出鸭
無限進步D3 小时前
蓝桥杯赛前刷题
c++·算法·蓝桥杯·竞赛
小贾要学习3 小时前
【Linux】应用层自定义协议与序列化
linux·服务器·c++·json
CoderCodingNo3 小时前
【GESP】C++二级真题 luogu-B4497, [GESP202603 二级] 数数
开发语言·c++·算法
weixin_395772473 小时前
计算机网络学习笔记】初始网络之网络发展和OSI七层模型
笔记·学习·计算机网络
FIT2CLOUD飞致云3 小时前
智能体能力持续扩展,文件管理与模型能力增强,1Panel v2.1.8版本发布
ai·开源·1panel