xiaozhi-esp32 MCP 协议全解析:ESP32 设备作为 MCP 服务器的交互流程与工具调用实现
2026/9/11 16:42:58 网站建设 项目流程

xiaozhi-esp32 MCP 协议全解析:ESP32 设备作为 MCP 服务器的交互流程与工具调用实现

【免费下载链接】xiaozhi-esp32An MCP-based chatbot | 一个基于MCP的聊天机器人项目地址: https://gitcode.com/GitHub_Trending/xia/xiaozhi-esp32

导读

本文档全面讲解 xiaozhi-esp32 项目中 MCP(Model Context Protocol)协议的完整交互流程:后台 API(作为 MCP 客户端)如何通过 WebSocket / MQTT 消息通道发现并调用 ESP32 设备(作为 MCP 服务器)上注册的各类"工具"(Tool)。读完本文,你将掌握 MCP 消息的 JSON-RPC 2.0 封装格式、hello → initialize → tools/list → tools/call 的完整握手时序、设备端工具注册机制(公共工具与仅用户工具),以及如何基于 main/mcp_server.cc 和 main/mcp_server.h 的源码理解底层实现细节。

说明:本文档为基于仓库源码的技术解析,实现后台服务时请以代码为准核对细节。

MCP 在 xiaozhi-esp32 中的角色

本项目中的 MCP 协议用于后台 API(MCP 客户端)与 ESP32 设备(MCP 服务器)之间的通信,以便后台能够发现和调用设备提供的功能(工具)。设备端的能力抽象由McpServer单例承载,各工具通过回调函数绑定到具体硬件操作,例如设置音量、调节屏幕亮度、切换主题、拍照、查询设备状态等。

从源码结构看,MCP 相关核心实现集中在三个文件:

  • main/mcp_server.h:McpServerMcpToolPropertyPropertyListImageContent等核心类定义;
  • main/mcp_server.cc:MCP 消息解析、initialize/tools/list/tools/call分发与工具注册;
  • main/protocols/protocol.cc:SendMcpMessage将 MCP payload 封装进基础协议消息体。

协议格式:JSON-RPC 2.0 封装在基础协议消息体中

根据代码(main/protocols/protocol.cc、main/mcp_server.cc),MCP 消息是封装在基础通信协议(如 WebSocket 或 MQTT)的消息体中的,其内部结构遵循 JSON-RPC 2.0 规范。

整体消息结构示例:

{ "session_id": "...", // 会话 ID "type": "mcp", // 消息类型,固定为 "mcp" "payload": { // JSON-RPC 2.0 负载 "jsonrpc": "2.0", "method": "...", // 方法名 (如 "initialize", "tools/list", "tools/call") "params": { ... }, // 方法参数 (对于 request) "id": ..., // 请求 ID (对于 request 和 response) "result": { ... }, // 方法执行结果 (对于 success response) "error": { ... } // 错误信息 (对于 error response) } }

其中,payload部分是标准的 JSON-RPC 2.0 消息:

  • jsonrpc: 固定的字符串 "2.0"。
  • method: 要调用的方法名称 (对于 Request)。
  • params: 方法的参数,一个结构化值,通常为对象 (对于 Request)。
  • id: 请求的标识符,客户端发送请求时提供,服务器响应时原样返回,用于匹配请求和响应。
  • result: 方法成功执行时的结果 (对于 Success Response)。
  • error: 方法执行失败时的错误信息 (对于 Error Response)。

源码印证:封装与解析的落点

  • 发送方向Protocol::SendMcpMessage(main/protocols/protocol.cc)将 payload 包装为{"session_id":...,"type":"mcp","payload":...}后经SendText下发,即文档中的外层结构。
  • 接收方向Application在 main/application.cc 中按type == "mcp"分支取出payload对象,交给McpServer::GetInstance().ParseMessage(payload)处理。
  • JSON-RPC 版本校验McpServer::ParseMessage(main/mcp_server.cc)首先校验jsonrpc必须为"2.0",随后校验methodparams(若存在必须为对象)与数值型id,任一不满足即打日志并丢弃。

响应消息的构造

  • 成功响应:ReplyResult(main/mcp_server.cc)构造{"jsonrpc":"2.0","id":N,"result":...}并通过Application::SendMcpMessage发出。
  • 错误响应:ReplyError(main/mcp_server.cc)构造{"jsonrpc":"2.0","id":N,"error":{"message":"..."}}。注意当前实现中错误对象只携带message字段,未显式填充标准 JSON-RPCcode字段,对接后台时需留意。

交互流程及发送时机

MCP 的交互主要围绕客户端(后台 API)发现和调用设备上的"工具"(Tool)进行。

1. 连接建立与能力通告

  • 时机:设备启动并成功连接到后台 API 后。

  • 发送方:设备。

  • 消息:设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过支持 MCP 协议 ("mcp": true)。

  • 示例 (非 MCP 负载,而是基础协议消息):

    { "type": "hello", "version": ..., "features": { "mcp": true, ... }, "transport": "websocket", // 或 "mqtt" "audio_params": { ... }, "session_id": "..." // 设备收到服务器hello后可能设置 }

源码印证:WebSocket 通道的WebsocketProtocol::GetHelloMessage(main/protocols/websocket_protocol.cc)与 MQTT 通道的MqttProtocol::GetHelloMessage(main/protocols/mqtt_protocol.cc)均在features中写入"mcp": true,同时携带transport(websocket / udp)、audio_params(opus、16000Hz、单声道、frame_duration)等字段。WebSocket 与 MQTT 两条链路都宣告 MCP 能力,后台可按需选择任一通道收发 MCP 消息。

2. 初始化 MCP 会话

  • 时机:后台 API 收到设备 "hello" 消息,确认设备支持 MCP 后,通常作为 MCP 会话的第一个请求发送。

  • 发送方:后台 API (客户端)。

  • 方法:initialize

  • 消息 (MCP payload):

    { "jsonrpc": "2.0", "method": "initialize", "params": { "capabilities": { // 客户端能力,可选 // 摄像头视觉相关 "vision": { "url": "...", // 摄像头: 图片处理地址 (必须是 http 地址, 不是 websocket 地址) "token": "..." // url token } // ... 其他客户端能力 } }, "id": 1 // 请求 ID }
  • 设备响应时机:设备收到initialize请求并处理后。

  • 设备响应消息 (MCP payload):

    { "jsonrpc": "2.0", "id": 1, // 匹配请求 ID "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} // 这里的 tools 似乎不列出详细信息,需要 tools/list }, "serverInfo": { "name": "...", // 设备名称 (BOARD_NAME) "version": "..." // 设备固件版本 } } }

源码印证McpServer::ParseMessageinitialize分支(main/mcp_server.cc)先解析params.capabilities中可选的vision对象(ParseCapabilities会把url/token交给摄像头模块SetExplainUrl,见 main/mcp_server.cc),随后读取esp_app_get_description()的固件版本,构造固定protocolVersion: "2024-11-05"capabilities.tools: {}serverInfo.name = BOARD_NAME的响应。

3. 发现设备工具列表

  • 时机:后台 API 需要获取设备当前支持的具体功能(工具)列表及其调用方式时。

  • 发送方:后台 API (客户端)。

  • 方法:tools/list

  • 消息 (MCP payload):

    { "jsonrpc": "2.0", "method": "tools/list", "params": { "cursor": "" // 用于分页,首次请求为空字符串 }, "id": 2 // 请求 ID }
  • 设备响应时机:设备收到tools/list请求并生成工具列表后。

  • 设备响应消息 (MCP payload):

    { "jsonrpc": "2.0", "id": 2, // 匹配请求 ID "result": { "tools": [ // 工具对象列表 { "name": "self.get_device_status", "description": "...", "inputSchema": { ... } // 参数 schema }, { "name": "self.audio_speaker.set_volume", "description": "...", "inputSchema": { ... } // 参数 schema } // ... 更多工具 ], "nextCursor": "..." // 如果列表很大需要分页,这里会包含下一个请求的 cursor 值 } }
  • 分页处理:如果nextCursor字段非空,客户端需要再次发送tools/list请求,并在params中带上这个cursor值以获取下一页工具。

源码印证GetToolsList(main/mcp_server.cc)以cursor定位起始工具(为空则从头开始),按约 8000 字节的max_payload_size上限逐个累加工具 JSON,超出即把当前工具名写入nextCursor提前终止;同时支持params.withUserTools布尔参数控制是否列出"仅用户工具"(见下文第 6 节)。分页 cursor 用的是下一个未返回工具的名称字符串,而非偏移量。

4. 调用设备工具

  • 时机:后台 API 需要执行设备上的某个具体功能时。

  • 发送方:后台 API (客户端)。

  • 方法:tools/call

  • 消息 (MCP payload):

    { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "self.audio_speaker.set_volume", // 要调用的工具名称 "arguments": { // 工具参数,对象格式 "volume": 50 // 参数名及其值 } }, "id": 3 // 请求 ID }
  • 设备响应时机:设备收到tools/call请求,执行相应的工具函数后。

  • 设备成功响应消息 (MCP payload):

    { "jsonrpc": "2.0", "id": 3, // 匹配请求 ID "result": { "content": [ // 工具执行结果内容 { "type": "text", "text": "true" } // 示例:set_volume 返回 bool ], "isError": false // 表示成功 } }
  • 设备失败响应消息 (MCP payload):

    { "jsonrpc": "2.0", "id": 3, // 匹配请求 ID "error": { "code": -32601, // JSON-RPC 错误码,例如 Method not found (-32601) "message": "Unknown tool: self.non_existent_tool" // 错误描述 } }

源码印证ParseMessagetools/call分支(main/mcp_server.cc)校验params.name为字符串、params.arguments为对象(允许为空),随后进入DoToolCall(main/mcp_server.cc):

  • 按工具名在注册表中查找,找不到即返回Unknown tool: <name>错误;
  • 将客户端arguments逐字段按Property类型(布尔/整数/字符串)匹配赋值;必填参数缺失或类型不匹配返回Missing valid argument: <name>;整数值超出min/max范围会抛出异常并回错误;
  • 参数校验通过后,通过Application::Schedule把工具回调投递到主线程执行(保证与显示、音频等 FreeRTOS 任务的线程安全),执行中抛出的异常统一转为ReplyError

关于错误码的说明:文档示例中的"code": -32601为 JSON-RPC 规范约定错误码;从 main/mcp_server.cc 看,当前设备端ReplyError构造的错误对象仅含message字段,后台服务对接时建议以message文本为准并自行兜底。

5. 设备主动发送消息 (Notifications)

  • 时机:设备内部发生需要通知后台 API 的事件时(例如状态变化,虽然代码示例中没有明确的工具发送此类消息,但Application::SendMcpMessage的存在暗示了设备可能主动发送 MCP 消息)。

  • 发送方:设备 (服务器)。

  • 方法:可能是以notifications/开头的方法名,或者其他自定义方法。

  • 消息 (MCP payload):遵循 JSON-RPC Notification 格式,没有id字段。

    { "jsonrpc": "2.0", "method": "notifications/state_changed", // 示例方法名 "params": { "newState": "idle", "oldState": "connecting" } // 没有 id 字段 }
  • 后台 API 处理:接收到 Notification 后,后台 API 进行相应的处理,但不回复。

源码印证McpServer::ParseMessage中(main/mcp_server.cc),凡method"notifications"开头的方法会被直接return,即设备端对通知不做任何应答;同时 main/application.cc 的Application::SendMcpMessage作为统一出口存在,说明设备具备向后台主动推送 MCP 消息的能力(例如工具回调内部需要上报事件时可复用该通道)。

交互序列图

下面是一个简化的交互序列图,展示了主要的 MCP 消息流程:

设备端工具注册机制

公共工具(AddCommonTools)

McpServer::AddCommonTools(main/mcp_server.cc)在设备启动阶段注册与板型无关的通用工具,并刻意把公共工具放在工具列表最前面,以利用服务端 prompt cache 提升响应速度。当前公共工具包括:

工具名说明参数
self.get_device_status返回设备实时状态 JSON(音频、屏幕、电池、网络等),也是执行控制类工具前的推荐第一步
self.audio_speaker.set_volume设置扬声器音量volume(integer, 0–100)
self.screen.set_brightness设置屏幕亮度(板级存在背光时注册)brightness(integer, 0–100)
self.screen.set_theme切换屏幕主题light/dark(启用 LVGL 且存在主题时注册)theme(string)
self.camera.take_photo拍照并返回图片解释结果(启用 LVGL 且板载摄像头时注册)question(string)

从 main/mcp_server.h 可见,Property支持布尔/整数/字符串三种类型,可声明默认值、整数minimum/maximum范围(越界在赋值时抛异常),并在to_json中生成对应 JSON Schema 片段;McpTool::Call(main/mcp_server.h)把bool/int/string/cJSON*/ImageContent*五类返回值统一序列化为content[0].text(图片类型则为type:"image"的 image 内容),并固定附加"isError": false

仅用户工具(AddUserOnlyTools)

McpServer::AddUserOnlyTools(main/mcp_server.cc)注册面向"用户"而非 AI 模型管理的系统级工具,通过set_user_only(true)标记,并在McpTool::to_json(main/mcp_server.h)中为其附加annotations.audience = ["user"]。这些工具在默认tools/list(未传withUserTools)中不可见,后台若需列出它们须在请求参数中携带"withUserTools": true。当前仅用户工具包括:

  • self.get_system_info:返回系统信息 JSON;
  • self.reboot:延时 1 秒后重启设备;
  • self.upgrade_firmware:从指定 URL 下载并安装固件后重启,参数url(string);
  • self.screen.get_info(启用 LVGL 时):屏幕宽高、是否单色等信息;
  • self.screen.snapshot(启用 LVGL 且开启CONFIG_LV_USE_SNAPSHOT):屏幕截图并以 multipart/form-data 上传到指定 URL,参数url(string)、quality(integer, 1–100, 默认 80);
  • self.screen.preview_image(同上条件):从 URL 下载图片并预览到屏幕,参数url(string);
  • self.assets.set_download_url:设置资源包的下载地址(写入assets命名空间 Settings)。

板级自定义工具(InitializeTools)

McpServer::AddCommonTools中明确注释:自定义工具必须在板级InitializeTools函数中注册(main/mcp_server.cc)。Board基类约定各板卡实现InitializeTools()(在构造流程中被调用,例如 main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc),典型示例如机械狗板卡 main/boards/espressif/esp-hi/esp_hi.cc 通过McpServer::GetInstance().AddTool(...)注册self.dog.basic_control(forward/backward/turn_left/turn_right/stop)与self.dog.advanced_control等运动控制工具;其余数十款板卡(如 waveshare、lilygo、movecall、wdmomo 等)均遵循同一模式,在InitializeTools中注册各自外设(灯环、舵机、摄像头、按键等)的 MCP 工具。

AddTool注册时会对同名工具去重并打印Add tool: <name> [user]日志(main/mcp_server.cc),便于排查重复注册问题。

后台 API 对接要点

综合以上协议与实现,后台服务(MCP 客户端)在对接设备时需注意:

  1. 通道选择:WebSocket 与 MQTT 链路均声明"mcp": true,MCP 消息统一以type: "mcp"+payload形式在通道内传输,先与设备完成基础握手拿到session_id
  2. 时序要求:先initialize(可携带capabilities.vision激活摄像头视觉解释能力),再tools/list(可传withUserTools: true获取含系统级工具的完整列表),最后按列表中的inputSchema构造tools/call
  3. 分页处理tools/list响应中nextCursor非空时,用其值作为下一次请求的cursor继续拉取,直到nextCursor为空;
  4. 响应匹配:设备端id原样回传,后台据此关联请求与响应;Notification(无id)不需要应答;
  5. 错误处理tools/call可能返回error(如未知工具、缺失参数、参数越界、执行异常),后台应针对error分支与result.isError分支分别处理。

总结

这份文档概述了该项目中 MCP 协议的主要交互流程:外层消息体由session_idtype: "mcp"与 JSON-RPC 2.0 格式的payload构成,交互按"hello 能力通告 → initialize 初始化 → tools/list 工具发现(可分页、可选含用户工具)→ tools/call 工具调用 → notifications 设备通知"的顺序展开。具体的参数细节和工具功能,可进一步参考 main/mcp_server.cc 中McpServer::AddCommonToolsAddUserOnlyTools以及各板卡InitializeTools中的工具实现。

【免费下载链接】xiaozhi-esp32An MCP-based chatbot | 一个基于MCP的聊天机器人项目地址: https://gitcode.com/GitHub_Trending/xia/xiaozhi-esp32

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询