MCP 協議物聯網控制用法說明

本文件介紹如何基於 MCP 協議實作 ESP32 裝置的物聯網控制。詳細協議流程請參考 MCP 協議文件。

簡介

MCP(Model Context Protocol)是新一代推薦用於物聯網控制的協議,透過標準 JSON-RPC 2.0 格式在後台與裝置間發現和呼叫「工具」(Tool),實作靈活的裝置控制。

典型使用流程

  1. 裝置啟動後透過基礎協議(如 WebSocket/MQTT)與後台建立連線。
  2. 後台透過 MCP 協議的 initialize 方法初始化會話。
  3. 後台透過 tools/list 取得裝置支援的所有工具(功能)及參數說明。
  4. 後台透過 tools/call 呼叫具體工具,實作對裝置的控制。

詳細協議格式與互動請見 MCP 協議文件。

裝置端工具註冊方法說明

裝置透過 McpServer::AddTool 方法註冊可被後台呼叫的「工具」。其常用函式簽名如下:

void AddTool(
    const std::string& name,           // 工具名稱,建議唯一且有層次感,如 self.dog.forward
    const std::string& description,    // 工具描述,簡明說明功能,便於大模型理解
    const PropertyList& properties,    // 輸入參數清單(可為空),支援類型:布林、整數、字串
    std::function<ReturnValue(const PropertyList&)> callback // 工具被呼叫時的回呼實作
);

參數說明

  • name:工具唯一標識,建議用「模組.功能」命名風格。
  • description:自然語言描述,便於 AI/使用者理解。
  • properties:參數清單,支援類型有布林、整數、字串,可指定範圍和預設值。
  • callback:收到呼叫請求時的實際執行邏輯,回傳值可為 bool/int/string。

典型註冊範例(以 ESP-Hi 為例)

void InitializeTools() {
    auto& mcp_server = McpServer::GetInstance();
    
    // 例1:無參數,控制機器人前進
    mcp_server.AddTool("self.dog.forward", "機器人向前移動", PropertyList(), 
        [this](const PropertyList&) -> ReturnValue {
            servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
            return true;
        });
    
    // 例2:帶參數,設定燈光 RGB 顏色
    mcp_server.AddTool("self.light.set_rgb", "設定RGB顏色", PropertyList({
        Property("r", kPropertyTypeInteger, 0, 255),
        Property("g", kPropertyTypeInteger, 0, 255),
        Property("b", kPropertyTypeInteger, 0, 255)
    }), [this](const PropertyList& properties) -> ReturnValue {
        int r = properties["r"].value<int>();
        int g = properties["g"].value<int>();
        int b = properties["b"].value<int>();
        led_on_ = true;
        SetLedColor(r, g, b);
        return true;
    });
}

典型註冊示例:僅使用者工具

mcp_server.AddUserOnlyTool("self.display.clear_cache",
    "Clear locally cached images. User-only action.",
    PropertyList(),
    [](const PropertyList&) -> ReturnValue {
        ClearLocalCache();
        return true;
    });

透過 AddUserOnlyTool 註冊的工具不會出現在普通的 tools/list 響應中,後臺需要設定 params.withUserTools = true 才能看到。

內建工具

McpServer::AddCommonTools 和 McpServer::AddUserOnlyTools 會自動註冊以下工具:

預設工具(AI 可呼叫,來自 AddCommonTools)

工具說明
self.get_device_status返回當前音量、螢幕、電池、網路等狀態。
self.audio_speaker.set_volume設定揚聲器音量(volume:0-100)。
self.screen.set_brightness在具備背光時設定螢幕亮度(brightness:0-100)。
self.screen.set_theme啟用 LVGL 時切換介面主題(theme:"light" 或 "dark")。
self.camera.take_photo使用板載攝像頭拍照(開發板帶攝像頭時可用),並針對照片回答給定的 question。

各開發板在 InitializeTools() 中註冊的專有工具會追加在這些內建工具之後。

僅使用者工具(來自 AddUserOnlyTools)

這些工具預設隱藏,後臺需要在 tools/list 中傳入 withUserTools=true 才能看到。它們面向配套 App 或終端使用者,而不是 AI 模型。

工具說明
self.get_system_info返回描述系統資訊的 JSON。
self.reboot延遲片刻後重啟裝置。
self.upgrade_firmware從 url 下載韌體並安裝,然後重啟。
self.screen.get_info返回螢幕寬度、高度以及是否為單色屏(僅 LVGL 開發板)。
self.screen.snapshot將螢幕截成 JPEG 並上傳到 url(可選 quality;僅 LVGL 開發板且啟用 CONFIG_LV_USE_SNAPSHOT=y)。
self.screen.preview_image從 url 下載圖片並顯示在螢幕上。
self.assets.set_download_url設定資源分割槽的下載地址。

常見工具呼叫 JSON-RPC 範例

1. 取得工具清單

{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": { "cursor": "", "withUserTools": false },
  "id": 1
}

2. 控制底盤前進

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.go_forward",
    "arguments": {}
  },
  "id": 2
}

3. 切換燈光模式

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.switch_light_mode",
    "arguments": { "light_mode": 3 }
  },
  "id": 3
}

4. 攝影機翻轉

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.camera.set_camera_flipped",
    "arguments": {}
  },
  "id": 4
}

5. 重啟裝置(僅使用者工具)

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.reboot",
    "arguments": {}
  },
  "id": 5
}

工具註冊最佳實務

1. 命名規範

  • 使用有層次的命名:self.模組.功能
  • 保持名稱簡潔且具有描述性
  • 避免使用特殊字元和空格
// 好的命名範例
"self.light.set_brightness"
"self.motor.rotate_left"  
"self.sensor.get_temperature"
"self.display.show_text"
// 不推薦的命名
"light-control"  // 缺少層次
"setBrightness"  // 缺少模組資訊
"控制燈光"       // 非英文命名
"self.light.set brightness" // 包含空格

2. 參數設計

  • 使用清晰的參數名稱
  • 設定合理的參數範圍和預設值
  • 提供詳細的參數描述
// 參數設計範例
PropertyList({
    Property("volume", kPropertyTypeInteger, 0, 100, "音量大小,範圍0-100"),
    Property("enabled", kPropertyTypeBool, true, "是否啟用功能"),
    Property("message", kPropertyTypeString, "", "要顯示的文字內容")
})

3. 錯誤處理

在工具回呼中進行適當的錯誤處理:

mcp_server.AddTool("self.servo.set_angle", "設定舵機角度", PropertyList({
    Property("angle", kPropertyTypeInteger, 0, 180)
}), [this](const PropertyList& properties) -> ReturnValue {
    try {
        int angle = properties["angle"].value<int>();
        if (angle < 0 || angle > 180) {
            return "角度超出範圍:0-180";
        }
        SetServoAngle(angle);
        return true;
    } catch (const std::exception& e) {
        return std::string("設定角度失敗:") + e.what();
    }
});

除錯技巧

1. 日誌記錄

在工具實作中新增日誌:

mcp_server.AddTool("self.debug.test", "測試工具", PropertyList(), 
    [this](const PropertyList& properties) -> ReturnValue {
        ESP_LOGI("MCP", "測試工具被呼叫");
        // 執行測試邏輯
        return "測試完成";
    });

2. 參數驗證

新增詳細的參數驗證:

int volume = properties["volume"].value<int>();
if (volume < 0 || volume > 100) {
    ESP_LOGE("MCP", "音量參數無效:%d", volume);
    return "音量必須在0-100之間";
}

注意事項

  • 工具名稱、參數及回傳值請以裝置端 AddTool 註冊為準。
  • 推薦所有新專案統一採用 MCP 協議進行物聯網控制。
  • 詳細協議與進階用法請查閱 MCP 協議文件。

相關文件

常見問題

Q: 如何處理工具呼叫逾時?

A: 在工具回呼中避免長時間阻塞操作,對於耗時操作可以非同步處理並立即回傳狀態。

Q: 可以動態新增或刪除工具嗎?

A: 目前實作通常在裝置啟動時註冊工具,動態管理需要重新設計工具註冊機制。

Q: 如何處理工具呼叫權限?

A: 可以在工具回呼中新增權限檢查邏輯,或在後台API層面進行權限控制。