小智完整MCP交互流程(以调节音量为例)

1. 初始化阶段 - MCP工具注册

在 mcp_server.cc 中,音量控制工具在 AddCommonTools() 中注册:

cpp 复制代码
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服务初始化

cpp 复制代码
// 在Application::Initialize()中
#if CONFIG_IOT_PROTOCOL_MCP
    McpServer::GetInstance().AddCommonTools();  // 注册包括音量控制在内的所有工具
#endif

3. AI服务器获取工具列表

当小智AI连接到ESP32设备时,会发送工具列表请求:

AI发送请求:

cpp 复制代码
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

ESP32响应(基于mcp_server.cc的实现):

cpp 复制代码
{
  "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. 用户语音输入处理

cpp 复制代码
用户说话: "把音量调到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发送状态查询:

cpp 复制代码
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "self.get_device_status",
    "arguments": {}
  }
}

ESP32执行状态查询:

cpp 复制代码
AddTool("self.get_device_status",
    "Provides the real-time information of the device...",
    PropertyList(),
    [&board](const PropertyList& properties) -> ReturnValue {
        return board.GetDeviceStatusJson();
    });

ESP32返回设备状态:

cpp 复制代码
{
  "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发送音量设置命令:

cpp 复制代码
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "self.audio_speaker.set_volume",
    "arguments": {
      "volume": 80
    }
  }
}

8. ESP32执行音量设置

基于 mcp_server.cc 中的实现:

cpp 复制代码
[&board](const PropertyList& properties) -> ReturnValue {
    auto codec = board.GetAudioCodec();
    codec->SetOutputVolume(properties["volume"].value<int>());
    return true;
}

详细执行过程:

cpp 复制代码
// 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返回执行结果

cpp 复制代码
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": true
}

10. AI生成语音回复

AI根据工具执行结果生成回复:

cpp 复制代码
工具调用结果: 
- get_device_status: 当前音量50
- set_volume: 设置成功

AI生成回复: "好的,已将音量从50调整到80"

11. TTS合成和音频传输

cpp 复制代码
AI文字回复 → TTS合成 → Opus编码 → 发送到ESP32

12. ESP32播放AI回复

cpp 复制代码
// 接收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

cpp 复制代码
/*
 * 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() {
} // 构造函数,初始化McpServer

McpServer::~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

cpp 复制代码
// 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 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<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 服务器的结构清晰、职责明确,易于扩展和维护。

相关推荐
TeamDev14 小时前
使用 MCP 自动化 JxBrowser
浏览器自动化·jxbrowser·mcp·模型上下文协议·mcp 自动化·jxbrowser 自动化·jxbrowser mcp
ChaITSimpleLove1 天前
使用 .net10 构建 AI 友好的 RSS 订阅机器人
人工智能·.net·mcp·ai bot·rss bot
妮妮分享1 天前
维智 MCP 接口服务技术支持指南
mcp·mcp server·维智 mcp·智能体接口
感谢地心引力2 天前
【AI】免费的代价?Google AI Studio 使用指南与 Cherry Studio + MCP 实战教程
人工智能·ai·google·chatgpt·gemini·mcp·cherry studio
AI架构师易筋2 天前
模型上下文协议(MCP)完全指南:从AI代理痛点到实战开发
人工智能·microsoft·语言模型·llm·mcp
qdprobot2 天前
齐护AiTall pro ESP32S3 小智AI对话 MQTT MCP 开发板Mixly Scratch Steam图形化编程创客教育
人工智能·mqtt·scratch·mixly·mcp·小智ai·齐护机器人aitall pro
路西法012 天前
Office-Word-MCP-Server在Cursor中使用方法
cursor·mcp
Light603 天前
【MCP原生时代】第2篇|前端如何舞动 MCP:新一代交互范式——从 Hook 到流式渲染,打造 AI 原生前端体验
状态模式·前端架构·mcp·react hook·流式渲染·ai交互
渣渣苏4 天前
MCP实战指南
mcp
爬点儿啥4 天前
[Ai Agent] 10 MCP基础:快速编写你自己的MCP服务器(Server)
人工智能·ai·langchain·agent·transport·mcp