【項目經驗】小智ai MCP學習筆記

理論

1、什么是MCP

MCP(Model Context Protocol,模型上下文協議)是一種開放式協議,它實現了LLM與各種工具的調用。使LLM從對話、生成式AI變成了擁有調用三方工具的AI。用官方的比喻,MCP就是USB-C接口,只要實現了這個接口,就可以接入AI,對AI進行賦能。
MCP
其本質是統一了AI調用三方功能的接口,借助AI Agent,使得LLM可以使用三方提供的服務來處理用戶提出的問題。

從上圖可以看到一些MCP的相關概念
MCP server:提供服務的三方,需要實現MCP server的功能,即將提供的功能接口按照MCP協議規定的格式,告知MCP client。
MCP client:連接MCP server與LLM的橋梁,負責管理與MCP server一對一的連接。
MCP hosts:一般指AI應用,通常由AI Agent實現MCP client的功能,再由AI Agent作為MCP hosts。
除此之外,還需要知道MCP tools的概念,第三方提供的功能接口一般稱為一個tool,在后面的代碼中會展示這一點。

這里引用up隔壁的程序員老王的一張視頻截圖,很清晰的展示了從用戶提問,到AI返回結果這一過程中,是如何調用三方MCP服務的。原視頻:10分鐘講清楚 Prompt, Agent, MCP 是什么
在這里插入圖片描述

2、小智AI MCP server

下面回到小智AI中,在蝦哥提供的源碼中,實現了MCP server,目前該功能還在內測中(2025年7月20日),可以去小智官網看看使用教程。

關于MCP協議的格式這里也不再復述,菜鳥教程和xiaozhi-esp32源碼的/docs/mcp-protocol.md中有非常詳細的介紹。這里只關注MCP的核心邏輯。

我們來看看MCP server的源碼。最關鍵的類就是McpServer,這個類實現了注冊工具、解析響應、調用工具等功能。

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);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_;
};

AddCommonTools()
這個方法實現了注冊工具的功能,在Application::Start()中調用。

    // Add MCP common tools before initializing the protocol
#if CONFIG_IOT_PROTOCOL_MCPMcpServer::GetInstance().AddCommonTools();
#endif

其具體實現沒什么神秘的,就是調用AddTool將功能接口的信息、參數和接口push進tools隊列。
比如設置音量的接口:

    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;});
void McpServer::AddTool(McpTool* tool) {// Prevent adding duplicate toolsif (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));
}

ParseMessage()
解析收到的JSON,對JSON格式校驗,如果是調用tool就調用DoToolCall去執行對應的tool。

void McpServer::ParseMessage(const cJSON* json) {// Check JSONRPC versionauto version = cJSON_GetObjectItem(json, "jsonrpc");if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) {ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null");return;}// Check methodauto method = cJSON_GetObjectItem(json, "method");if (method == nullptr || !cJSON_IsString(method)) {ESP_LOGE(TAG, "Missing method");return;}...if (method_str == "tools/call") {...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);}
}

DoToolCall()
查找tool,創建新的線程,在新線程中調用tool中的回調函數,即三方實現的功能接口。

void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size) {// 在tools中按tool_name查找toolauto tool_iter = std::find_if(tools_.begin(), tools_.end(), [&tool_name](const McpTool* tool) { return tool->name() == tool_name; });// 解析回調函數的參數PropertyList arguments = (*tool_iter)->properties();try {for (auto& argument : arguments) {...}}// Start a task to receive data with stack sizeesp_pthread_cfg_t cfg = esp_pthread_get_default_config();cfg.thread_name = "tool_call";cfg.stack_size = stack_size;cfg.prio = 1;esp_pthread_set_cfg(&cfg);// Use a thread to call the tool to avoid blocking the main threadtool_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();
}

ReplyResult()ReplyError() 就是將結果轉為JSON,并通過protocol(mqtt或websocket)發送出去。

void McpServer::ReplyResult(int id, const std::string& result) {std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":";payload += std::to_string(id) + ",\"result\":";payload += result;payload += "}";Application::GetInstance().SendMcpMessage(payload);
}void Application::SendMcpMessage(const std::string& payload) {Schedule([this, payload]() {if (protocol_) {protocol_->SendMcpMessage(payload);}});
}

通過對源碼的分析,我們知道了MCP server的核心邏輯,簡單來說就是在server中將接口放入tools,之后由MCP client發起調用,server解析JSON后去調用對應的接口。至于client是如何知道有哪些tool的,可以在ParseMessage()中發現端倪:

if (method_str == "tools/list") {std::string cursor_str = "";if (params != nullptr) {auto cursor = cJSON_GetObjectItem(params, "cursor");if (cJSON_IsString(cursor)) {cursor_str = std::string(cursor->valuestring);}}GetToolsList(id_int, cursor_str);

具體的client代碼實現還沒有看,但不難猜測,client會發起一次獲取tools的請求,這樣就知道了有哪些tool。

下面我們就可以注冊一個自己的tool,來實現對外設的控制。

實踐

實現功能:語音控制燈光亮度
首先初始化RGB燈

// 配置定時器
ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,.duty_resolution = LEDC_TIMER_8_BIT,.timer_num = LEDC_TIMER_0,.freq_hz = 5000,
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));// 配置通道
ledc_channel_config_t ledc_channel={.gpio_num = GPIO_NUM_1,.speed_mode = LEDC_LOW_SPEED_MODE,.channel = LEDC_CHANNEL_0,.timer_sel = LEDC_TIMER_0,.duty = 0,
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));

然后添加tool

AddTool("self.my_led.set_brightness","Set the brightness of the blue LED. The brightness is a percentage value from 0 to 100.",PropertyList({Property("brightness", kPropertyTypeInteger, 0, 100)}),[](const PropertyList& properties) -> ReturnValue {uint32_t brightness = static_cast<uint32_t>(properties["brightness"].value<int>());ESP_LOGI(TAG, "my led set brightness %lu", brightness);if (brightness > 100) {brightness = 100;}uint32_t duty = (brightness * 255) / 100; // Convert to 8-bit duty cycleESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty));ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));return true;});

效果如下:

小智AI使用MCP控制RGB燈光亮度

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92413.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92413.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92413.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Matlab學習筆記:矩陣基礎

MATLAB學習筆記:矩陣基礎 作為MATLAB的核心,矩陣是處理數據的基礎工具。矩陣本質上是一個二維數組,由行和列組成,用于存儲和操作數值數據。在本節中,我將詳細講解矩陣的所有知識點,包括創建、索引、運算、函數等,確保內容通俗易懂。我會在關鍵地方添加MATLAB代碼示例,…

技術演進中的開發沉思-38 MFC系列:關于打印

打印程序也是MFC開發中不能忽視的一個環節&#xff0c;現在做打印開發so easy。但當年做打印開發還是挺麻煩。在當年的桌面程序里就像拼圖的最后一塊&#xff0c;看著簡單&#xff0c;實則要把屏幕上的像素世界&#xff0c;準確映射到打印機的物理紙張上。而MFC 的打印機制就像…

Apache Ignite 長事務終止機制

這段內容講的是 Apache Ignite 中長事務終止機制&#xff08;Long Running Transactions Termination&#xff09;&#xff0c;特別是關于分區映射交換&#xff08;Partition Map Exchange&#xff09;與事務超時設置&#xff08;Transaction Timeout&#xff09;之間的關系。下…

網絡編程---TCP協議

TCP協議基礎知識TCP&#xff08;Transmission Control Protocol&#xff0c;傳輸控制協議&#xff09;是互聯網核心協議之一&#xff0c;位于傳輸層&#xff08;OSI第4層&#xff09;&#xff0c;為應用層提供可靠的、面向連接的、基于字節流的數據傳輸服務。它與IP協議共同構成…

K 近鄰算法(K-Nearest Neighbors, KNN)詳解及案例

K近鄰算法&#xff08;K-Nearest Neighbors, KNN&#xff09;詳解及案例 一、基本原理 K近鄰算法是一種監督學習算法&#xff0c;核心思想是“物以類聚&#xff0c;人以群分”&#xff1a;對于一個新樣本&#xff0c;通過計算它與訓練集中所有樣本的“距離”&#xff0c;找出距…

深入理解 Redis 集群化看門狗機制:原理、實踐與風險

在分布式系統中&#xff0c;我們常常需要執行一些關鍵任務&#xff0c;這些任務要么必須成功執行&#xff0c;要么失敗后需要明確的狀態&#xff08;如回滾&#xff09;&#xff0c;并且它們的執行時間可能難以精確預測。如何確保這些任務不會被意外中斷&#xff0c;或者在長時…

Python機器學習:從零基礎到項目實戰

目錄第一部分&#xff1a;思想與基石——萬法歸宗&#xff0c;筑基問道第1章&#xff1a;初探智慧之境——機器學習世界觀1.1 何為學習&#xff1f;從人類學習到機器智能1.2 機器學習的“前世今生”&#xff1a;一部思想與技術的演進史1.3 為何是Python&#xff1f;——數據科學…

數據庫:庫的操作

1&#xff1a;查看所有數據庫SHOW DATABASES;2&#xff1a;創建數據庫CREATE DATABASE [ IF NOT EXISTS ] 數據庫名 [ CHARACTER SET 字符集編碼 | COLLATE 字符集校驗規則 | ENCRYPTION { Y | N } ];[]&#xff1a;可寫可不寫{}&#xff1a;必選一個|&#xff1a;n 選 1ENCR…

AngularJS 動畫

AngularJS 動畫 引言 AngularJS 是一個流行的JavaScript框架,它為開發者提供了一種構建動態Web應用的方式。在AngularJS中,動畫是一個強大的功能,可以幫助我們創建出更加生動和引人注目的用戶界面。本文將詳細介紹AngularJS動畫的原理、用法以及最佳實踐。 AngularJS 動畫…

SonarQube 代碼分析工具

??親愛的技術愛好者們,熱烈歡迎來到 Kant2048 的博客!我是 Thomas Kant,很開心能在CSDN上與你們相遇~?? 本博客的精華專欄: 【自動化測試】 【測試經驗】 【人工智能】 【Python】 ??全面掌握 SonarQube:企業代碼質量保障的利器 ?? 在當今 DevOps 流水線中,代碼…

vmware vsphere esxi6.5 使用工具導出鏡像

注&#xff1a;為什么使用這個工具&#xff0c;我這邊主要因為esxi6.5自身bug導致web導出鏡像會失敗一、下載VMware-ovftool到本地系統&#xff08;根據你的操作系統版本到官網下載安裝&#xff0c;此處略&#xff09;以下內容默認將VMware-ovftool安裝到windows 本地系統為例。…

ES 踩坑記:Set Processor 字段更新引發的 _source 污染

問題背景 社區的一個伙伴想對一個 integer 的字段類型添加一個 keyword 類型的子字段&#xff0c;然后進行精確匹配的查詢優化&#xff0c;提高查詢的速度。 整個索引數據量不大&#xff0c;并不想進行 reindex 這樣的復雜操作&#xff0c;就想到了使用 update_by_query 的存量…

如何徹底搞定 PyCharm 中 pip install 報錯 ModuleNotFoundError: No module named ‘requests’ 的問題

如何徹底搞定 PyCharm 中 pip install 報錯 ModuleNotFoundError: No module named ‘requests’ 的問題 在使用 PyCharm 開發 Python 項目時&#xff0c;ModuleNotFoundError: No module named requests 是一個常見但令人頭疼的問題。本篇博文將從環境配置、原因分析到多種解…

powerquery如何實現表的拼接主鍵

在做表過程中&#xff0c;有時候沒有基表&#xff0c;這個時候就要構造完整的主鍵&#xff0c;這樣才可以使之后匹配的數據不會因為主鍵不全而丟失數據 我的處理方法是吧多個表的主鍵拼在一起然后去重&#xff0c;構造一個單單之后之間的表作為基表去匹配數據 所以就喲啊用到自…

今日Github熱門倉庫推薦 第八期

今日Github熱門倉庫推薦2025-07-22 如果讓AI分別扮演 后端開發人員和前端開發人員&#xff0c;然后看看他們分別對github每天的trending倉庫感興趣的有哪些&#xff0c;并且給出他感興趣的理由&#xff0c;那會發生什么呢&#xff1f; 本內容通過Python AI生成&#xff0c;項…

Dify-13: 文本生成API端點

本文檔提供了有關 Dify 中與文本生成相關的 API 端點的全面信息。文本生成 API 支持無會話持久性的單次請求文本生成&#xff0c;使其適用于翻譯、摘要、文章寫作等非對話式人工智能應用場景。 概述 文本生成 API 端點允許開發人員將 Dify 的文本生成功能集成到不需要維護對話上…

Leetcode 3620. Network Recovery Pathways

Leetcode 3620. Network Recovery Pathways 1. 解題思路2. 代碼實現 題目鏈接&#xff1a;3620. Network Recovery Pathways 1. 解題思路 這一題我最開始想的是遍歷一下所有的網絡路徑&#xff0c;不過遇到了超時的情況。因此后來調整了一下處理思路&#xff0c;使用二分法的…

鏈路備份技術(鏈路聚合、RSTP)

一、鏈路聚合&#xff01;鏈路備份技術之一-----鏈路聚合&#xff08;Link Aggregation&#xff09;被視為鏈路備份技術&#xff0c;核心原因在于它能通過多條物理鏈路的捆綁&#xff0c;實現 “一條鏈路故障時&#xff0c;其他鏈路自動接管流量” 的冗余備份效果&#xff0c;同…

PyTorch新手實操 安裝

PyTorch簡介 PyTorch 是一個基于 Python 的開源深度學習框架&#xff0c;由 Meta AI&#xff08;原 Facebook AI&#xff09;主導開發&#xff0c;以動態計算圖&#xff08;Define-by-Run&#xff09;為核心&#xff0c;支持靈活構建和訓練神經網絡模型。其設計理念高度契合科…

Element Plus Table 組件擴展:表尾合計功能詳解

前言在現代數據驅動的社會中&#xff0c;數據分析和統計成為了非常重要的任務。為了更有效地分析數據和展示統計結果&#xff0c;前端開發人員可以使用Vue框架和Element Plus組件庫來實現數據的統計和分析功能。以下是一個關于如何在 Element Plus 的 el-table 組件中實現行匯總…