小智完整MCP交互流程(以调节音量为例)
1. 初始化阶段 - MCP工具注册
在 mcp_server.cc 中,音量控制工具在 AddCommonTools() 中注册:
AddTool("self.audio_speaker.set_volume", "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.",PropertyList({Property("volume", kPropertyTypeInteger, 0, 100)}), [&board](const PropertyList& properties) -> ReturnValue {auto codec = board.GetAudioCodec();codec->SetOutputVolume(properties["volume"].value<int>());return true;
2. 设备启动时的MCP服务初始化
// 在Application::Initialize()中
#if CONFIG_IOT_PROTOCOL_MCPMcpServer::GetInstance().AddCommonTools(); // 注册包括音量控制在内的所有工具
#endif
3. AI服务器获取工具列表
当小智AI连接到ESP32设备时,会发送工具列表请求:
AI发送请求:
{"jsonrpc": "2.0","id": 1,"method": "tools/list","params": {}
}
ESP32响应(基于mcp_server.cc的实现):
{"jsonrpc": "2.0","id": 1,"result": {"tools": [{"name": "self.get_device_status","description": "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\nUse this tool for: \n1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)","inputSchema": {"type": "object","properties": {}}},{"name": "self.audio_speaker.set_volume","description": "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.","inputSchema": {"type": "object","properties": {"volume": {"type": "integer","minimum": 0,"maximum": 100}},"required": ["volume"]}}]}
}
4. 用户语音输入处理
用户说话: "把音量调到80"↓
ESP32音频采集: Application::OnAudioInput()↓
音频处理: AfeAudioProcessor处理音频数据↓
Opus编码: OpusEncoderWrapper编码↓
发送到AI: protocol_->SendAudio(packet)
5. AI理解和决策过程
小智AI接收到音频后:
- ASR识别: “把音量调到80”
- 意图理解: 需要调节音量到80
- 工具选择:
1)首先可能调用 self.get_device_status 获取当前音量
2)然后调用 self.audio_speaker.set_volume 设置音量
6. 第一步:获取当前设备状态
AI发送状态查询:
{"jsonrpc": "2.0","id": 2,"method": "tools/call","params": {"name": "self.get_device_status","arguments": {}}
}
ESP32执行状态查询:
AddTool("self.get_device_status","Provides the real-time information of the device...",PropertyList(),[&board](const PropertyList& properties) -> ReturnValue {return board.GetDeviceStatusJson();});
ESP32返回设备状态:
{"jsonrpc": "2.0","id": 2,"result": {"audio_speaker": {"volume": 50},"screen": {"brightness": 100,"theme": "light"},"battery": {"level": 85,"charging": false},"network": {"type": "wifi","ssid": "MyWiFi","rssi": -45}}
}
7. 第二步:设置音量
AI发送音量设置命令:
{"jsonrpc": "2.0","id": 3,"method": "tools/call","params": {"name": "self.audio_speaker.set_volume","arguments": {"volume": 80}}
}
8. ESP32执行音量设置
基于 mcp_server.cc 中的实现:
[&board](const PropertyList& properties) -> ReturnValue {auto codec = board.GetAudioCodec();codec->SetOutputVolume(properties["volume"].value<int>());return true;
}
详细执行过程:
// 1. 从MCP参数中提取音量值
int volume = properties["volume"].value<int>(); // volume = 80// 2. 获取音频编解码器实例
auto codec = board.GetAudioCodec();// 3. 设置音量(实际硬件操作)
codec->SetOutputVolume(80);// 4. 可能的显示通知(如果设备有屏幕)
auto display = board.GetDisplay();
if (display) {display->ShowNotification("音量: 80");
}
9. ESP32返回执行结果
{"jsonrpc": "2.0","id": 3,"result": true
}
10. AI生成语音回复
AI根据工具执行结果生成回复:
工具调用结果:
- get_device_status: 当前音量50
- set_volume: 设置成功AI生成回复: "好的,已将音量从50调整到80"
11. TTS合成和音频传输
AI文字回复 → TTS合成 → Opus编码 → 发送到ESP32
12. ESP32播放AI回复
// 接收AI回复音频
protocol_->OnIncomingAudio([this](AudioStreamPacket&& packet) {std::lock_guard<std::mutex> lock(mutex_);if (device_state_ == kDeviceStateSpeaking) {audio_decode_queue_.emplace_back(std::move(packet));}
});// 播放音频回复
void Application::OnAudioOutput() {if (!audio_decode_queue_.empty()) {auto packet = std::move(audio_decode_queue_.front());audio_decode_queue_.pop_front();// Opus解码std::vector<int16_t> pcm_data;opus_decoder_->Decode(packet.payload, pcm_data);// 播放到扬声器auto codec = Board::GetInstance().GetAudioCodec();codec->WriteOutput(pcm_data);}
}
完整时序图
关键代码执行路径
MCP工具调用的核心路径:
- 工具注册: McpServer::AddTool() 注册音量控制工具
- 工具列表: AI查询时返回所有已注册工具
- 工具执行: 收到 tools/call 时,查找并执行对应的lambda函数
- 硬件操作: board.GetAudioCodec()->SetOutputVolume(volume)
- 结果返回: 返回执行结果给AI
音频处理的并行路径:
- 音频输入: Application::OnAudioInput() 持续采集用户语音
- 音频输出: Application::OnAudioOutput() 播放AI回复
- 状态管理: Application::SetDeviceState() 管理设备状态切换
性能分析
整个音量调节流程的延迟构成:
- 语音采集和编码: ~50ms
- 网络传输到AI: ~50-100ms
- AI语音识别: ~200-500ms
- AI意图理解: ~100-200ms
- MCP工具调用1(状态查询): ~10-20ms
- MCP工具调用2(音量设置): ~10-20ms
- AI回复生成和TTS: ~200-400ms
- 音频传输和播放: ~100-200ms
总延迟: ~720-1440ms
这个流程展示了ESP32本地MCP实现的高效性,特别是工具执行部分的延迟极低(10-20ms),这是本地实现相比远程服务器的主要优势。
MCP相关源码和类解析
mcp_server.cc
/** MCP Server Implementation* Reference: https://modelcontextprotocol.io/specification/2024-11-05*/
// MCP 服务器实现
// 参考: https://modelcontextprotocol.io/specification/2024-11-05#include "mcp_server.h" // MCP 服务器头文件
#include <esp_log.h> // ESP日志库
#include <esp_app_desc.h> // ESP应用程序描述库
#include <algorithm> // 标准算法库
#include <cstring> // 字符串操作库
#include <esp_pthread.h> // ESP Pthread库
#include <driver/gpio.h> // GPIO驱动
#include <driver/ledc.h> // LEDC (PWM) 驱动#include "application.h" // 应用程序头文件
#include "display.h" // 显示头文件
#include "board.h" // 板级支持包头文件#define TAG "MCP" // 日志标签#define DEFAULT_TOOLCALL_STACK_SIZE 6144 // 工具调用默认栈大小McpServer::McpServer() {
} // 构造函数,初始化McpServerMcpServer::~McpServer() {// 析构函数,释放工具列表中的内存for (auto tool : tools_) {delete tool; // 删除每个工具对象}tools_.clear(); // 清空工具列表
}void McpServer::AddCommonTools() {// To speed up the response time, we add the common tools to the beginning of// the tools list to utilize the prompt cache.// Backup the original tools list and restore it after adding the common tools.// 为了加快响应时间,我们将常用工具添加到工具列表的开头,以利用提示缓存。// 备份原始工具列表,并在添加常用工具后恢复。auto original_tools = std::move(tools_); // 备份原始工具列表auto& board = Board::GetInstance(); // 获取Board单例// 添加获取设备状态的工具AddTool("self.get_device_status","Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n""Use this tool for: \n""1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n""2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",PropertyList(),[&board](const PropertyList& properties) -> ReturnValue {return board.GetDeviceStatusJson(); // 返回设备状态的JSON字符串});// 添加设置音量工具AddTool("self.audio_speaker.set_volume","Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.",PropertyList({Property("volume", kPropertyTypeInteger, 0, 100) // 音量属性,整数类型,范围0-100}),[&board](const PropertyList& properties) -> ReturnValue {auto codec = board.GetAudioCodec(); // 获取音频编解码器codec->SetOutputVolume(properties["volume"].value<int>()); // 设置输出音量return true; // 返回成功});// === 屏幕亮度控制工具 ===// 如果设备支持背光控制,添加屏幕亮度设置工具auto backlight = board.GetBacklight();if (backlight) {AddTool("self.screen.set_brightness","Set the brightness of the screen.",PropertyList({Property("brightness", kPropertyTypeInteger, 0, 100) // 亮度属性,整数类型,范围0-100}),[backlight](const PropertyList& properties) -> ReturnValue {uint8_t brightness = static_cast<uint8_t>(properties["brightness"].value<int>()); // 获取亮度值backlight->SetBrightness(brightness, true); // 设置背光亮度return true; // 返回成功});}auto display = board.GetDisplay(); // 获取显示对象if (display && !display->GetTheme().empty()) { // 如果显示对象存在且主题不为空// 添加设置屏幕主题工具AddTool("self.screen.set_theme","Set the theme of the screen. The theme can be `light` or `dark`.",PropertyList({Property("theme", kPropertyTypeString) // 主题属性,字符串类型}),[display](const PropertyList& properties) -> ReturnValue {display->SetTheme(properties["theme"].value<std::string>().c_str()); // 设置显示主题return true; // 返回成功});}auto camera = board.GetCamera(); // 获取摄像头对象if (camera) { // 如果摄像头对象存在// 添加拍照并解释工具AddTool("self.camera.take_photo","Take a photo and explain it. Use this tool after the user asks you to see something.\n""Args:\n"" `question`: The question that you want to ask about the photo.\n""Return:\n"" A JSON object that provides the photo information.",PropertyList({Property("question", kPropertyTypeString) // 问题属性,字符串类型}),[camera](const PropertyList& properties) -> ReturnValue {if (!camera->Capture()) { // 如果捕获照片失败return "{\"success\": false, \"message\": \"Failed to capture photo\"}"; // 返回失败信息}auto question = properties["question"].value<std::string>(); // 获取问题return camera->Explain(question); // 解释照片并返回信息});}// Restore the original tools list to the end of the tools list// 将原始工具列表恢复到当前工具列表的末尾tools_.insert(tools_.end(), original_tools.begin(), original_tools.end());
}void McpServer::AddTool(McpTool* tool) {// Prevent adding duplicate tools// 防止添加重复的工具if (std::find_if(tools_.begin(), tools_.end(), [tool](const McpTool* t) { return t->name() == tool->name(); }) != tools_.end()) {ESP_LOGW(TAG, "Tool %s already added", tool->name().c_str()); // 记录警告日志:工具已存在return;}ESP_LOGI(TAG, "Add tool: %s", tool->name().c_str()); // 记录信息日志:添加工具tools_.push_back(tool); // 将工具添加到列表中
}void McpServer::AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback) {// 通过参数创建并添加新工具AddTool(new McpTool(name, description, properties, callback)); // 创建新的McpTool对象并添加
}void McpServer::ParseMessage(const std::string& message) {cJSON* json = cJSON_Parse(message.c_str()); // 解析JSON消息if (json == nullptr) {ESP_LOGE(TAG, "Failed to parse MCP message: %s", message.c_str()); // 记录错误日志:解析消息失败return;}ParseMessage(json); // 调用重载的ParseMessage函数处理cJSON对象cJSON_Delete(json); // 释放cJSON对象内存
}void McpServer::ParseCapabilities(const cJSON* capabilities) {// 解析客户端能力,特别是视觉(vision)相关的能力auto vision = cJSON_GetObjectItem(capabilities, "vision"); // 获取"vision"能力if (cJSON_IsObject(vision)) { // 如果"vision"是对象auto url = cJSON_GetObjectItem(vision, "url"); // 获取"url"auto token = cJSON_GetObjectItem(vision, "token"); // 获取"token"if (cJSON_IsString(url)) { // 如果"url"是字符串auto camera = Board::GetInstance().GetCamera(); // 获取摄像头对象if (camera) { // 如果摄像头对象存在std::string url_str = std::string(url->valuestring); // 获取url字符串std::string token_str;if (cJSON_IsString(token)) { // 如果"token"是字符串token_str = std::string(token->valuestring); // 获取token字符串}camera->SetExplainUrl(url_str, token_str); // 设置解释URL和token}}}
}void McpServer::ParseMessage(const cJSON* json) {// Check JSONRPC version// 检查JSONRPC版本auto version = cJSON_GetObjectItem(json, "jsonrpc"); // 获取"jsonrpc"版本if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) {ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null"); // 记录错误日志:无效的JSONRPC版本return;}// Check method// 检查方法auto method = cJSON_GetObjectItem(json, "method"); // 获取"method"if (method == nullptr || !cJSON_IsString(method)) {ESP_LOGE(TAG, "Missing method"); // 记录错误日志:缺少方法return;}auto method_str = std::string(method->valuestring); // 将方法转换为字符串if (method_str.find("notifications") == 0) { // 如果方法是通知return; // 直接返回,不处理通知}// Check params// 检查参数auto params = cJSON_GetObjectItem(json, "params"); // 获取"params"if (params != nullptr && !cJSON_IsObject(params)) {ESP_LOGE(TAG, "Invalid params for method: %s", method_str.c_str()); // 记录错误日志:方法的参数无效return;}auto id = cJSON_GetObjectItem(json, "id"); // 获取"id"if (id == nullptr || !cJSON_IsNumber(id)) {ESP_LOGE(TAG, "Invalid id for method: %s", method_str.c_str()); // 记录错误日志:方法的id无效return;}auto id_int = id->valueint; // 获取id的整数值if (method_str == "initialize") { // 如果方法是"initialize"if (cJSON_IsObject(params)) { // 如果参数是对象auto capabilities = cJSON_GetObjectItem(params, "capabilities"); // 获取"capabilities"if (cJSON_IsObject(capabilities)) { // 如果"capabilities"是对象ParseCapabilities(capabilities); // 解析能力}}auto app_desc = esp_app_get_description(); // 获取应用程序描述std::string message = "{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"" BOARD_NAME "\",\"version\":\"";message += app_desc->version; // 添加版本号message += "\"}}";ReplyResult(id_int, message); // 回复初始化结果} else if (method_str == "tools/list") { // 如果方法是"tools/list"std::string cursor_str = ""; // 初始化游标字符串if (params != nullptr) { // 如果参数不为空auto cursor = cJSON_GetObjectItem(params, "cursor"); // 获取"cursor"if (cJSON_IsString(cursor)) { // 如果"cursor"是字符串cursor_str = std::string(cursor->valuestring); // 获取游标字符串}}GetToolsList(id_int, cursor_str); // 获取工具列表} else if (method_str == "tools/call") { // 如果方法是"tools/call"if (!cJSON_IsObject(params)) { // 如果参数不是对象ESP_LOGE(TAG, "tools/call: Missing params"); // 记录错误日志:缺少参数ReplyError(id_int, "Missing params"); // 回复错误信息return;}auto tool_name = cJSON_GetObjectItem(params, "name"); // 获取工具名称if (!cJSON_IsString(tool_name)) { // 如果工具名称不是字符串ESP_LOGE(TAG, "tools/call: Missing name"); // 记录错误日志:缺少名称ReplyError(id_int, "Missing name"); // 回复错误信息return;}auto tool_arguments = cJSON_GetObjectItem(params, "arguments"); // 获取工具参数if (tool_arguments != nullptr && !cJSON_IsObject(tool_arguments)) { // 如果工具参数不为空且不是对象ESP_LOGE(TAG, "tools/call: Invalid arguments"); // 记录错误日志:参数无效ReplyError(id_int, "Invalid arguments"); // 回复错误信息return;}auto stack_size = cJSON_GetObjectItem(params, "stackSize"); // 获取栈大小if (stack_size != nullptr && !cJSON_IsNumber(stack_size)) { // 如果栈大小不为空且不是数字ESP_LOGE(TAG, "tools/call: Invalid stackSize"); // 记录错误日志:栈大小无效ReplyError(id_int, "Invalid stackSize"); // 回复错误信息return;}DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments, stack_size ? stack_size->valueint : DEFAULT_TOOLCALL_STACK_SIZE); // 执行工具调用} else {ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str()); // 记录错误日志:方法未实现ReplyError(id_int, "Method not implemented: " + method_str); // 回复错误信息}
}void McpServer::ReplyResult(int id, const std::string& result) {// 回复成功结果std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; // 构建JSONRPC响应载荷payload += std::to_string(id) + ",\"result\":";payload += result;payload += "}";Application::GetInstance().SendMcpMessage(payload); // 发送MCP消息
}void McpServer::ReplyError(int id, const std::string& message) {// 回复错误信息std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; // 构建JSONRPC错误响应载荷payload += std::to_string(id);payload += ",\"error\":{\"message\":\"";payload += message;payload += "\"}}";Application::GetInstance().SendMcpMessage(payload); // 发送MCP消息
}void McpServer::GetToolsList(int id, const std::string& cursor) {// 获取工具列表const int max_payload_size = 8000; // 最大载荷大小std::string json = "{\"tools\":["; // 构建JSON字符串bool found_cursor = cursor.empty(); // 检查游标是否为空auto it = tools_.begin(); // 迭代器指向工具列表开头std::string next_cursor = ""; // 下一个游标while (it != tools_.end()) {// 如果我们还没有找到起始位置,继续搜索// If we haven't found the starting position, continue searchingif (!found_cursor) {if ((*it)->name() == cursor) { // 如果找到游标对应的工具found_cursor = true; // 设置找到游标标志} else {++it; // 移动到下一个工具continue;}}// 添加tool前检查大小// Check size before adding toolstd::string tool_json = (*it)->to_json() + ","; // 获取工具的JSON字符串并添加逗号if (json.length() + tool_json.length() + 30 > max_payload_size) {// 如果添加这个tool会超出大小限制,设置next_cursor并退出循环// If adding this tool exceeds the size limit, set next_cursor and break the loopnext_cursor = (*it)->name(); // 设置下一个游标为当前工具的名称break; // 退出循环}json += tool_json; // 将工具JSON添加到字符串中++it; // 移动到下一个工具}if (json.back() == ',') { // 如果JSON字符串最后一个字符是逗号json.pop_back(); // 移除逗号}if (json.back() == '[' && !tools_.empty()) {// 如果没有添加任何tool,返回错误// If no tools have been added, return an errorESP_LOGE(TAG, "tools/list: Failed to add tool %s because of payload size limit", next_cursor.c_str()); // 记录错误日志ReplyError(id, "Failed to add tool " + next_cursor + " because of payload size limit"); // 回复错误信息return;}if (next_cursor.empty()) { // 如果下一个游标为空json += "]}"; // 结束JSON字符串} else {json += "],\"nextCursor\":\"" + next_cursor + "\"}"; // 结束JSON字符串并添加下一个游标}ReplyResult(id, json); // 回复结果
}void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size) {// 执行工具调用auto tool_iter = std::find_if(tools_.begin(), tools_.end(), [&tool_name](const McpTool* tool) { return tool->name() == tool_name; // 根据工具名称查找工具});if (tool_iter == tools_.end()) { // 如果没有找到工具ESP_LOGE(TAG, "tools/call: Unknown tool: %s", tool_name.c_str()); // 记录错误日志:未知工具ReplyError(id, "Unknown tool: " + tool_name); // 回复错误信息return;}PropertyList arguments = (*tool_iter)->properties(); // 获取工具的属性列表try {for (auto& argument : arguments) { // 遍历每个参数bool found = false; // 标志位:是否找到参数if (cJSON_IsObject(tool_arguments)) { // 如果工具参数是对象auto value = cJSON_GetObjectItem(tool_arguments, argument.name().c_str()); // 获取参数值if (argument.type() == kPropertyTypeBoolean && cJSON_IsBool(value)) { // 如果是布尔类型且cJSON是布尔值argument.set_value<bool>(value->valueint == 1); // 设置布尔值found = true;} else if (argument.type() == kPropertyTypeInteger && cJSON_IsNumber(value)) { // 如果是整数类型且cJSON是数字argument.set_value<int>(value->valueint); // 设置整数值found = true;} else if (argument.type() == kPropertyTypeString && cJSON_IsString(value)) { // 如果是字符串类型且cJSON是字符串argument.set_value<std::string>(value->valuestring); // 设置字符串值found = true;}}if (!argument.has_default_value() && !found) { // 如果参数没有默认值且未找到ESP_LOGE(TAG, "tools/call: Missing valid argument: %s", argument.name().c_str()); // 记录错误日志:缺少有效参数ReplyError(id, "Missing valid argument: " + argument.name()); // 回复错误信息return;}}} catch (const std::exception& e) { // 捕获异常ESP_LOGE(TAG, "tools/call: %s", e.what()); // 记录错误日志:异常信息ReplyError(id, e.what()); // 回复错误信息return;}// Start a task to receive data with stack size// 启动一个任务来接收数据,并指定栈大小esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); // 获取默认的pthread配置cfg.thread_name = "tool_call"; // 设置线程名称cfg.stack_size = stack_size; // 设置栈大小cfg.prio = 1; // 设置优先级esp_pthread_set_cfg(&cfg); // 设置pthread配置// Use a thread to call the tool to avoid blocking the main thread// 使用线程调用工具以避免阻塞主线程tool_call_thread_ = std::thread([this, id, tool_iter, arguments = std::move(arguments)]() {try {ReplyResult(id, (*tool_iter)->Call(arguments)); // 调用工具并回复结果} catch (const std::exception& e) { // 捕获异常ESP_LOGE(TAG, "tools/call: %s", e.what()); // 记录错误日志:异常信息ReplyError(id, e.what()); // 回复错误信息}});tool_call_thread_.detach(); // 分离线程
}
mcp_server.h
// MCP服务器头文件
// 实现模型控制协议(Model Control Protocol)服务器功能
// 提供工具注册、属性管理、消息解析、远程调用等功能#ifndef MCP_SERVER_H
#define MCP_SERVER_H// C++标准库
#include <string> // 字符串
#include <vector> // 动态数组
#include <map> // 映射容器
#include <functional> // 函数对象
#include <variant> // 变体类型
#include <optional> // 可选类型
#include <stdexcept> // 标准异常
#include <thread> // 线程// 第三方库
#include <cJSON.h> // JSON解析库// 返回值类型别名
// 支持布尔值、整数、字符串三种返回类型
using ReturnValue = std::variant<bool, int, std::string>;// 属性类型枚举
// 定义MCP工具支持的属性数据类型
enum PropertyType {kPropertyTypeBoolean, // 布尔类型kPropertyTypeInteger, // 整数类型kPropertyTypeString // 字符串类型
};// Property类 - MCP工具属性
// 定义MCP工具的输入参数属性,支持类型检查、默认值、范围限制
// 主要功能:属性定义、类型安全、值验证、JSON序列化
class Property {
private:// === 属性基本信息 ===std::string name_; // 属性名称PropertyType type_; // 属性类型std::variant<bool, int, std::string> value_; // 属性值(支持多种类型)bool has_default_value_; // 是否有默认值// === 整数范围限制 ===std::optional<int> min_value_; // 整数最小值(可选)std::optional<int> max_value_; // 整数最大值(可选)public:// === 构造函数 ===// 必需字段构造函数(无默认值)Property(const std::string& name, PropertyType type): name_(name), type_(type), has_default_value_(false) {}// 可选字段构造函数(带默认值)template<typename T>Property(const std::string& name, PropertyType type, const T& default_value): name_(name), type_(type), has_default_value_(true) {value_ = default_value;}// 整数范围限制构造函数(无默认值)Property(const std::string& name, PropertyType type, int min_value, int max_value): name_(name), type_(type), has_default_value_(false), min_value_(min_value), max_value_(max_value) {if (type != kPropertyTypeInteger) {throw std::invalid_argument("Range limits only apply to integer properties");}}// 整数范围限制构造函数(带默认值)Property(const std::string& name, PropertyType type, int default_value, int min_value, int max_value): name_(name), type_(type), has_default_value_(true), min_value_(min_value), max_value_(max_value) {if (type != kPropertyTypeInteger) {throw std::invalid_argument("Range limits only apply to integer properties");}if (default_value < min_value || default_value > max_value) {throw std::invalid_argument("Default value must be within the specified range");}value_ = default_value;}// === 属性信息查询接口(内联函数) ===inline const std::string& name() const { return name_; } // 获取属性名称inline PropertyType type() const { return type_; } // 获取属性类型inline bool has_default_value() const { return has_default_value_; } // 是否有默认值inline bool has_range() const { return min_value_.has_value() && max_value_.has_value(); } // 是否有范围限制inline int min_value() const { return min_value_.value_or(0); } // 获取最小值inline int max_value() const { return max_value_.value_or(0); } // 获取最大值// === 属性值操作接口 ===// 获取属性值(模板函数,类型安全)template<typename T>inline T value() const {return std::get<T>(value_);}// 设置属性值(模板函数,带范围检查)template<typename T>inline void set_value(const T& value) {// 对整数值进行范围检查if constexpr (std::is_same_v<T, int>) {if (min_value_.has_value() && value < min_value_.value()) {throw std::invalid_argument("Value is below minimum allowed: " + std::to_string(min_value_.value()));}if (max_value_.has_value() && value > max_value_.value()) {throw std::invalid_argument("Value exceeds maximum allowed: " + std::to_string(max_value_.value()));}}value_ = value;}// === JSON序列化接口 ===// 将属性定义转换为JSON Schema格式// 用于MCP协议中的工具描述和参数验证std::string to_json() const {cJSON *json = cJSON_CreateObject();// 根据属性类型生成相应的JSON Schemaif (type_ == kPropertyTypeBoolean) {cJSON_AddStringToObject(json, "type", "boolean");if (has_default_value_) {cJSON_AddBoolToObject(json, "default", value<bool>());}} else if (type_ == kPropertyTypeInteger) {cJSON_AddStringToObject(json, "type", "integer");if (has_default_value_) {cJSON_AddNumberToObject(json, "default", value<int>());}// 添加整数范围限制if (min_value_.has_value()) {cJSON_AddNumberToObject(json, "minimum", min_value_.value());}if (max_value_.has_value()) {cJSON_AddNumberToObject(json, "maximum", max_value_.value());}} else if (type_ == kPropertyTypeString) {cJSON_AddStringToObject(json, "type", "string");if (has_default_value_) {cJSON_AddStringToObject(json, "default", value<std::string>().c_str());}}// 转换为字符串并清理内存char *json_str = cJSON_PrintUnformatted(json);std::string result(json_str);cJSON_free(json_str);cJSON_Delete(json);return result;}
};// PropertyList类 - 属性列表管理器
// 管理MCP工具的属性集合,提供属性查找、迭代、序列化等功能
// 主要功能:属性集合管理、名称索引、必需属性识别、JSON序列化
class PropertyList {
private:std::vector<Property> properties_; // 属性列表public:// === 构造函数 ===PropertyList() = default; // 默认构造函数PropertyList(const std::vector<Property>& properties) : properties_(properties) {} // 列表构造函数// === 属性管理接口 ===void AddProperty(const Property& property) { // 添加属性properties_.push_back(property);}// 按名称查找属性(重载[]操作符)const Property& operator[](const std::string& name) const {for (const auto& property : properties_) {if (property.name() == name) {return property;}}throw std::runtime_error("Property not found: " + name);}// === 迭代器接口 ===auto begin() { return properties_.begin(); } // 开始迭代器auto end() { return properties_.end(); } // 结束迭代器// 获取必需属性列表(没有默认值的属性)std::vector<std::string> GetRequired() const {std::vector<std::string> required;for (auto& property : properties_) {if (!property.has_default_value()) {required.push_back(property.name());}}return required;}// === JSON序列化接口 ===// 将属性列表转换为JSON Schema properties格式// 用于MCP工具的参数定义和验证std::string to_json() const {cJSON *json = cJSON_CreateObject();// 遍历所有属性,将每个属性转换为JSON并添加到对象中for (const auto& property : properties_) {cJSON *prop_json = cJSON_Parse(property.to_json().c_str());cJSON_AddItemToObject(json, property.name().c_str(), prop_json);}// 转换为字符串并清理内存char *json_str = cJSON_PrintUnformatted(json);std::string result(json_str);cJSON_free(json_str);cJSON_Delete(json);return result;}
};// McpTool类 - MCP工具定义
// 定义一个可被远程调用的MCP工具,包含名称、描述、参数和回调函数
// 主要功能:工具定义、参数验证、远程调用、结果序列化
class McpTool {
private:// === 工具基本信息 ===std::string name_; // 工具名称std::string description_; // 工具描述PropertyList properties_; // 工具参数列表std::function<ReturnValue(const PropertyList&)> callback_; // 工具回调函数public:// === 构造函数 ===McpTool(const std::string& name,const std::string& description,const PropertyList& properties,std::function<ReturnValue(const PropertyList&)> callback): name_(name),description_(description),properties_(properties),callback_(callback) {}// === 工具信息查询接口(内联函数) ===inline const std::string& name() const { return name_; } // 获取工具名称inline const std::string& description() const { return description_; } // 获取工具描述inline const PropertyList& properties() const { return properties_; } // 获取工具参数列表// === JSON序列化接口 ===// 将工具定义转换为MCP协议标准的JSON格式// 包含工具名称、描述和输入参数Schemastd::string to_json() const {std::vector<std::string> required = properties_.GetRequired();cJSON *json = cJSON_CreateObject();cJSON_AddStringToObject(json, "name", name_.c_str());cJSON_AddStringToObject(json, "description", description_.c_str());// 构建输入参数SchemacJSON *input_schema = cJSON_CreateObject();cJSON_AddStringToObject(input_schema, "type", "object");// 添加属性定义cJSON *properties = cJSON_Parse(properties_.to_json().c_str());cJSON_AddItemToObject(input_schema, "properties", properties);// 添加必需属性列表if (!required.empty()) {cJSON *required_array = cJSON_CreateArray();for (const auto& property : required) {cJSON_AddItemToArray(required_array, cJSON_CreateString(property.c_str()));}cJSON_AddItemToObject(input_schema, "required", required_array);}cJSON_AddItemToObject(json, "inputSchema", input_schema);// 转换为字符串并清理内存char *json_str = cJSON_PrintUnformatted(json);std::string result(json_str);cJSON_free(json_str);cJSON_Delete(json);return result;}// === 工具调用接口 ===// 执行工具回调函数并返回MCP协议标准的结果格式// 支持多种返回值类型的自动转换std::string Call(const PropertyList& properties) {ReturnValue return_value = callback_(properties);// 构建MCP协议标准的返回结果cJSON* result = cJSON_CreateObject();cJSON* content = cJSON_CreateArray();cJSON* text = cJSON_CreateObject();cJSON_AddStringToObject(text, "type", "text");// 根据返回值类型进行相应的转换if (std::holds_alternative<std::string>(return_value)) {cJSON_AddStringToObject(text, "text", std::get<std::string>(return_value).c_str());} else if (std::holds_alternative<bool>(return_value)) {cJSON_AddStringToObject(text, "text", std::get<bool>(return_value) ? "true" : "false");} else if (std::holds_alternative<int>(return_value)) {cJSON_AddStringToObject(text, "text", std::to_string(std::get<int>(return_value)).c_str());}cJSON_AddItemToArray(content, text);cJSON_AddItemToObject(result, "content", content);cJSON_AddBoolToObject(result, "isError", false);// 转换为字符串并清理内存auto json_str = cJSON_PrintUnformatted(result);std::string result_str(json_str);cJSON_free(json_str);cJSON_Delete(result);return result_str;}
};// McpServer类 - MCP服务器
// 实现模型控制协议服务器,管理工具注册、消息解析、远程调用等功能
// 采用单例模式,提供全局统一的MCP服务接口
class McpServer {
public:// === 单例模式接口 ===static McpServer& GetInstance() { // 获取单例实例static McpServer instance;return instance;}// === 工具管理接口 ===void AddCommonTools(); // 添加通用工具void AddTool(McpTool* tool); // 添加工具(指针方式)void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback); // 添加工具(参数方式)// === 消息处理接口 ===void ParseMessage(const cJSON* json); // 解析JSON消息void ParseMessage(const std::string& message); // 解析字符串消息private:// === 私有构造函数和析构函数(单例模式) ===McpServer(); // 私有构造函数~McpServer(); // 私有析构函数// === 私有方法 ===void ParseCapabilities(const cJSON* capabilities); // 解析客户端能力void ReplyResult(int id, const std::string& result); // 回复成功结果void ReplyError(int id, const std::string& message); // 回复错误信息void GetToolsList(int id, const std::string& cursor); // 获取工具列表void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size); // 执行工具调用// === 私有成员变量 ===std::vector<McpTool*> tools_; // 工具列表std::thread tool_call_thread_; // 工具调用线程
};#endif // MCP_SERVER_H
相关类解析
实现模型控制协议(Model Control Protocol, MCP)服务器功能的组件。这四个类协同工作,共同构建了一个能够注册工具、管理属性、解析消息和处理远程调用的系统。
四个核心类及其作用
1. Property 类 (MCP工具属性)
- 作用:定义了 MCP工具的单个输入参数的属性。它不仅包含属性的名称和数据类型(布尔、整数、字符串),还支持默认值、整数范围限制等高级功能。它还提供了类型安全的属性值存取接口(通过 std::variant)以及将属性定义转换为 JSON Schema 格式的功能,用于协议中的工具描述和参数验证。
- 职责:
- 定义单个属性的元数据(名称、类型)。
- 存储和管理属性值,支持多种类型。
- 支持默认值和整数范围验证。
- 将自身序列化为 JSON 格式。
2. PropertyList 类 (属性列表管理器)
- 作用:管理 Property 对象的集合。它是一个容器,用于封装一个 MCP
工具的所有参数属性。它提供了添加属性、按名称查找属性、迭代属性以及将整个属性列表序列化为 JSON Schema properties
部分的功能。 - 职责:
- 作为 Property 对象的集合(内部使用 std::vector)。
- 提供方便的接口来添加和访问(通过重载 [] 操作符)属性。
- 识别并返回所有必需属性(没有默认值的属性)的列表。
- 将自身序列化为 JSON Schema 的 properties 部分。
3. McpTool 类 (MCP工具定义)
- 作用:定义了一个可以被远程调用的 MCP 工具。它包含了工具的名称、描述、它所接受的参数列表(通过
PropertyList)以及一个实际执行工具逻辑的回调函数。它是 MCP 协议中可调用操作的核心抽象。 - 职责:
- 封装工具的基本信息(名称、描述)。
- 包含一个 PropertyList 对象来定义工具的所有输入参数。
- 存储一个 std::function 对象作为工具的实际业务逻辑(回调函数),该函数接受 PropertyList 作为输入,并返回一个 ReturnValue。
- 将自身序列化为 MCP 协议标准的 JSON 格式,包括工具的名称、描述和输入参数的 JSON Schema。
- 提供 Call 方法来执行回调函数,并将结果格式化为 MCP 协议标准的 JSON 响应。
4. McpServer 类 (MCP服务器)
- 作用:实现整个 MCP 协议服务器的核心功能。它是一个单例模式的类,确保系统中只有一个服务器实例。McpServer 负责管理所有注册的
McpTool,解析传入的 MCP 消息,根据消息内容调用相应的工具,并回复结果或错误信息。 - 职责:
- 作为单例提供全局访问点。
- 管理所有注册的 McpTool 对象(内部使用 std::vector<McpTool*>)。
- 提供AddTool 方法来注册新的工具。
- 解析传入的 JSON 或字符串消息(ParseMessage)。
- 处理不同类型的 MCP 协议消息,例如: GetToolsList:获取所有注册工具的列表。 DoToolCall:执行特定的工具调用。 构建并回复 MCP 协议标准的成功或错误结果。
类之间的联系
这四个类之间存在着紧密的组合 (Composition) 和依赖 (Dependency) 关系,共同构建了 MCP 服务器的功能:
1. PropertyList 组合 Property:
- 一个 PropertyList 对象内部包含一个 std::vector。这意味着 PropertyList 是
Property 对象的集合或管理器。 - PropertyList 负责存储和操作多个 Property 实例。
McpTool 组合 PropertyList:
- 一个 McpTool 对象内部包含一个 PropertyList 对象 (properties_)。这个 PropertyList
定义了该工具所接受的所有输入参数。 - 当 McpTool 被调用时 (Call 方法),它会接收一个 PropertyList 作为参数,然后将其传递给其内部的回调函数。
McpServer 依赖/管理 McpTool:
- McpServer 内部维护一个 std::vector<McpTool*>(工具列表)。这意味着 McpServer
负责注册、存储和管理系统中的所有 McpTool 实例。 - McpServer 的 AddTool 方法用于将 McpTool 实例添加到其管理列表中。
- 当 McpServer 接收到 DoToolCall 消息时,它会根据消息中指定的工具名称,在它管理的 McpTool
列表中查找并调用相应的 McpTool 的 Call 方法。
总结关系流:
- Property 定义了单个参数的详细信息。
- PropertyList 将多个参数(Property 对象)组织成一个集合。
- McpTool 代表一个可执行的功能,它使用 PropertyList 来定义其输入参数,并包含实际执行逻辑的回调。
- McpServer 是整个系统的协调者,它管理所有 McpTool,解析外部请求,并根据请求调用对应的 McpTool。
通过这种分层和组合的设计,整个 MCP 服务器的结构清晰、职责明确,易于扩展和维护。