小智完整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接收到音频后:

  1. ASR识别: “把音量调到80”
  2. 意图理解: 需要调节音量到80
  3. 工具选择:
    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工具调用的核心路径:

  1. 工具注册: McpServer::AddTool() 注册音量控制工具
  2. 工具列表: AI查询时返回所有已注册工具
  3. 工具执行: 收到 tools/call 时,查找并执行对应的lambda函数
  4. 硬件操作: board.GetAudioCodec()->SetOutputVolume(volume)
  5. 结果返回: 返回执行结果给AI

音频处理的并行路径:

  1. 音频输入: Application::OnAudioInput() 持续采集用户语音
  2. 音频输出: Application::OnAudioOutput() 播放AI回复
  3. 状态管理: 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 服务器的结构清晰、职责明确,易于扩展和维护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89007.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89007.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

极狐GitLab CEO 柳钢——极狐 GitLab 打造中国企业专属 AI 编程平台,引领编程新潮流

当下&#xff0c;AI 编程已成为人工智能大模型领域的耀眼明星&#xff0c;是公认的最好应用领域之一。其发展速度惊人&#xff0c;从最初简单的代码建议、代码补全等基础智能功能&#xff0c;一路高歌猛进&#xff0c;如今已涵盖智能单元测试、智能代码审核、智能体编程、代码仓…

tiktok 弹幕 逆向分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析部分python代码部分python代码…

Java学习打卡22(JAVA中的错误Error和异常Exception)

异常&#xff08; Exception &#xff09;&#xff1a;软件程序在运行过程中&#xff0c;出现的不期而至的各种情况&#xff0c;如&#xff1a;文件找不到、网络连接失败、非法参数等。要理解JAVA异常处理是如何工作的&#xff0c;需要掌握以下三种类型的异常&#xff1a;检查性…

AntV G6 基础元素详解(React版)

一、初识 AntV G6 AntV G6 是蚂蚁集团推出的专业级图可视化引擎&#xff0c;适合构建关系图谱、拓扑图、流程图等场景。相比其他图形库&#xff0c;G6 提供完整的布局算法 和交互体系 &#xff0c;开发者在 10 分钟内即可搭建可交互的图应用。 技术特点速览&#xff1a; 支持 C…

【解决】联想电脑亮度调节

更新后图标变大调节分辨率然后亮度就不能调节了快捷键WindowsX打开设备管理器右键显卡更新驱动程序选择“浏览我的电脑以查找驱动程序&#xff08;R&#xff09;”选择“让我从计算机上的可驱动程序列表中选取&#xff08;L&#xff09;”点击下一步成功解决

将Blender、Three.js与Cesium集成构建物联网3D可视化系统

将Blender、Three.js与Cesium集成构建物联网3D可视化系统&#xff0c;可实现从精细设备建模到宏观地理空间展示的全栈能力。以下是技术整合方案及典型应用场景&#xff1a;一、技术栈分工与集成逻辑 #mermaid-svg-gCvcBVB9ebl092ap {font-family:"trebuchet ms",verd…

用TensorFlow进行逻辑回归(一)

这一节我们用TensorFlow定义简单的分类器。首先考虑分类器的方程式是什么是值得的。数学习的技巧是使用sigmoid函数。sigmoid函数绘制如图3-40, 通常标记为σ, 是实数域里的函数取值(0, 1)。这个特征很便利&#xff0c;因为我们可以将sigmoid的输出解释为事件发现的概率。 (转…

【Java代码审计(2)】MyBatis XML 注入审计

代码背景&#xff1a;某公司使用 MyBatis 作为持久层框架&#xff0c;登录功能如下&#xff1a; Java 接口代码&#xff1a; public interface UserMapper {User findByUsernameAndPassword(Param("username") String username,Param("password") String p…

Spring Boot目录变文件夹?3步解决!

在 Spring Boot 项目中&#xff0c;当你在 src/main/java 下看到目录结构而不是包结构时&#xff0c;这通常是 IDE&#xff08;如 IntelliJ IDEA&#xff09;的显示问题或项目配置问题。以下是原因和解决方案&#xff1a;问题原因IDE 未正确识别 Java 源代码根目录 src/main/ja…

Appium源码深度解析:从驱动到架构

Appium源码深度解析:从驱动到架构 Appium 源码概览 Appium 是一个开源的移动自动化测试框架,支持跨平台(iOS、Android)和多种编程语言(Java、Python 等)。其源码托管在 GitHub 上,主要由 JavaScript 和 Node.js 实现,核心逻辑围绕客户端-服务器架构设计。 GitHub 仓库…

给 Excel 整列空格文字内容加上前缀:像给文字穿衣服一样简单!

目录 步骤一&#xff1a;选中目标列 打开Excel表格并定位列点击列标题选中整列 步骤二&#xff1a;输入公式&#xff0c;变身“魔法” 在公式编辑栏输入公式按下回车键查看效果 步骤三&#xff1a;向下填充&#xff0c;批量处理 鼠标定位到单元格右下角按住鼠标左键向下拖动填充…

Spring Boot 启动原理揭秘:从 main 方法到自动装配

Spring Boot 启动原理揭秘&#xff1a;从 main 方法到自动装配 引言 Spring Boot 作为 Java 领域最流行的开发框架之一&#xff0c;凭借其“开箱即用”的特性极大地简化了 Spring 应用的搭建和部署。然而&#xff0c;尽管开发者在日常工作中频繁使用 Spring Boot 的启动类&…

OpenCV 与深度学习:从图像分类到目标检测技术

一、深度学习&#xff1a;从 “人工设计” 到 “自动学习”1.1 深度学习的定位&#xff1a;AI、机器学习与深度学习的关系人工智能&#xff08;AI&#xff09;&#xff1a;是一个宽泛的领域&#xff0c;目标是构建能模拟人类智能的系统&#xff0c;涵盖推理、感知、决策等能力。…

Docker 镜像推送至 Coding 制品仓库超时问题排查与解决

Docker 镜像推送至 Coding 制品仓库超时问题排查与解决 在将 Docker 镜像推送至 Coding 的制品仓库时&#xff0c;可能会遇到 docker push 命令超时失败的问题。但使用 curl -i http://xxx.coding.xxxx.xx 测试时&#xff0c;连接却能成功建立。以下是排查过程及解决方案。 问题…

https交互原理

Https 交互时序图&#xff1a;HTTPS 通信中结合 RSA 和 AES 加密的流程&#xff0c;本质是利用 RSA 的安全特性交换 AES 密钥&#xff0c;再用高效的 AES 加密实际数据传输。HTTPS 交互核心流程&#xff08;TLS/SSL 握手&#xff09; 1. 建立 TCP 连接 客户端通过 TCP 三次握手…

LSTM入门案例(时间序列预测)| pytorch实现

需求 假如我有一个时间序列&#xff0c;例如是前113天的价格数据&#xff08;训练集&#xff09;&#xff0c;然后我希望借此预测后30天的数据&#xff08;测试集&#xff09;&#xff0c;实际上这143天的价格数据都已经有了。这里为了简单&#xff0c;每一天的数据只有一个价…

WPS、Word加载项开发流程(免费最简版本)

文章目录1 加载项对比2 WPS 加载项2.1 本地开发2.1.1 准备开发环境2.1.2 新建 WPS 加载项项目2.1.3 运行项目2.2 在线部署2.2.1 编译项目2.2.2 部署项目2.2.3 生成分发文件2.2.4 部署分发文件2.3 安装加载项2.4 取消发布3 Word 加载项3.1 本地开发3.1.1 准备开发环境3.1.2 新建…

Flink SQL 性能优化实战

最近我们组在大规模上线Flink SQL作业。首先&#xff0c;在进行跑批量初始化完历史数据后&#xff0c;剩下的就是消费Kafka历史数据进行追数了。但是发现某些作业的追数过程十分缓慢&#xff0c;要运行一晚上甚至三四天才能追上最新数据。由于是实时数仓指标计算上线初期&#…

HTML 树结构(DOM)深入讲解教程

一、HTML 树结构的核心概念 1.1 DOM&#xff08;文档对象模型&#xff09;的定义 DOM&#xff08;Document Object Model&#xff09;是 W3C 制定的标准接口&#xff0c;允许程序或脚本&#xff08;如 JavaScript&#xff09;动态访问和更新 HTML/XML 文档的内容、结构和样式。…

用鼠标点击终端窗口的时候出现:0;61;50M0;61;50M0;62;50M0

在做aws webrtc viewer拉流压测的过程中&#xff0c;我本地打开了多个终端&#xff0c;用于连接EC2实例&#xff1a; 一个终端用于启动 ‘并发master脚本’、监控master端的cpu、mem&#xff1b;一个终端用于监控master端的带宽情况&#xff1b;一个终端用于监控viewer端的cpu、…