小智完整MCP交互流程(以调节音量为例)_小智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_MCP McpServer::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日志库#include // ESP应用程序描述库#include // 标准算法库#include // 字符串操作库#include // ESP Pthread库#include // GPIO驱动#include // 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 searching if (!found_cursor) { if ((*it)->name() == cursor) { // 如果找到游标对应的工具 found_cursor = true; // 设置找到游标标志 } else { ++it; // 移动到下一个工具 continue; } } // 添加tool前检查大小 // Check size before adding tool std::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 loop next_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 error ESP_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 // 字符串#include // 动态数组#include // 映射容器#include // 函数对象#include // 变体类型#include // 可选类型#include // 标准异常#include // 线程// 第三方库#include // 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 Schema if (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格式 // 包含工具名称、描述和输入参数Schema std::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()); // 构建输入参数Schema cJSON *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)。
- 提供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(工具列表)。这意味着 McpServer
负责注册、存储和管理系统中的所有 McpTool 实例。 - McpServer 的 AddTool 方法用于将 McpTool 实例添加到其管理列表中。
- 当 McpServer 接收到 DoToolCall 消息时,它会根据消息中指定的工具名称,在它管理的 McpTool
列表中查找并调用相应的 McpTool 的 Call 方法。
总结关系流:
- Property 定义了单个参数的详细信息。
- PropertyList 将多个参数(Property 对象)组织成一个集合。
- McpTool 代表一个可执行的功能,它使用 PropertyList 来定义其输入参数,并包含实际执行逻辑的回调。
- McpServer 是整个系统的协调者,它管理所有 McpTool,解析外部请求,并根据请求调用对应的 McpTool。
通过这种分层和组合的设计,整个 MCP 服务器的结构清晰、职责明确,易于扩展和维护。