网站设计公司请示,专业的单位网站开发,购买商标,网站建设与维护项目六官方文档#xff1a;https://xiaozhi.dev/docs/development/mcp/故事的开始#xff1a;她说怕冷
“今天降温好厉害#xff0c;我一进门就手脚冰凉。”
小禾听完这句话#xff0c;脑子里只有一个念头#xff1a;她到家前 10 分钟把空调开到制热#xff0c;屋里先暖起来。
…官方文档https://xiaozhi.dev/docs/development/mcp/故事的开始她说怕冷“今天降温好厉害我一进门就手脚冰凉。”小禾听完这句话脑子里只有一个念头她到家前 10 分钟把空调开到制热屋里先暖起来。这样她推门进来的第一口空气不是冰的外套也不用穿着取暖。问题是怎么做才靠谱现成的智能家居方案当然能做“定时开空调”但小禾想要的是“到家前 10 分钟”这种更贴近生活的触发条件地理围栏/到家事件/打卡提醒……而且还要能把调试过程完全可控、可观测到底有没有连上工具有没有被调用调用失败是什么原因他最后盯上了“小智”这个开源项目以及它的 MCPModel Context Protocol把设备能力抽象成一组可发现、可调用的Tools。你只要把链路跑通——initialize → tools/list → tools/call就等于拿到了“遥控器”。本文先把“遥控器”做出来搭建 MCP 开发环境把工具发现与调用跑通。至于“空调预热”本身我们走最通用的路线红外遥控空调你只需要让设备具备发 IR 的能力并把“发一条空调红外指令”封装成一个 Tool。提前10分钟触发MCP tools/call红外发射手机地理围栏/到家事件你的后端/自动化服务小智设备 ESP32空调屋里先暖起来先把两个问题说清楚1小智音箱是什么你可以把“小智音箱”理解为跑在ESP32常见ESP32-S3上的一套语音交互设备/固件方案。它能完成唤醒、录音、传输音频、接收识别/合成结果并通过WebSocket/MQTT等方式和后端服务通信。如果你之前把它当成“会聊天的音箱”那也没错但它更有意思的地方在于——它其实是一台可编程的语音入口能被你扩展成各种“语音 设备控制”的组合。2为什么需要 MCP 开发环境使用场景有哪些因为从“能说话”到“能做事”中间差的就是一条稳定的“工具调用通道”。小智用 MCPJSON-RPC 2.0把设备能力抽象成 Tools你能在设备端注册工具、在服务端发现工具、再发起调用。这也是“小禾到家前 10 分钟开空调”的关键拆解触发条件到家前 10 分钟不属于 MCP它来自手机地理围栏/Home Assistant Presence/定时任务等。执行动作开暖风、设温度才属于 MCP后端在触发时tools/call调用设备上的“空调预热”工具。一旦你要开始做扩展尤其是自定义工具/自定义后端就必须有一个开发环境来支持你看得见协议能抓到hello、能看到initialize/tools/list/tools/call的往返不再“黑盒调试”。改得动能力在固件里注册/调整工具名称、参数、返回值让模型更容易正确调用。跑得通闭环本地就能模拟“后台 APIMCP Client”快速迭代而不是每次都依赖完整线上链路。典型场景包括智能家居控制灯光/空调/插座/窗帘等通过tools/call执行动作。传感器与状态查询温湿度、空气质量、设备在线状态、电量等通过工具读数并返回给模型。自定义设备/自制硬件给你的舵机、小车、门禁、继电器做语音控制入口。场景联动与自动化把“语音意图”落到可执行工具再接入你自己的规则引擎或自动化平台。联调与排障当设备说“调用失败”你能从 JSON-RPC 的error里直接定位是工具名不对、参数不对还是超时。带着这个目标往下看你只要把initialize → tools/list → tools/call跑通就等于把“小智音箱”从“聊天设备”升级成了“可控终端”。你最终要看到的效果idf.py build flash monitor能编译并烧录小智固件串口日志正常启动设备能连上你本机/局域网的 WebSocket 服务并发来type: hello握手消息你能从服务端发起 MCP 请求收到设备返回的initialize响应含protocolVersion、serverInfotools/list响应看到工具列表tools/call响应工具被执行并返回结果MCP 在小智里怎么“走线”先搞清谁是 Client / Server按官方协议文档的描述后台 API 是 MCP ClientESP32 设备是 MCP Server。也就是说你本地起的服务扮演“后台 API”负责发现并调用设备上的工具。MCP 消息用JSON-RPC 2.0但在小智里会再包一层“基础通信协议”的外壳WebSocket 或 MQTT{session_id:...,type:mcp,payload:{jsonrpc:2.0,method:tools/list,params:{cursor:},id:2}}官方协议文档页面也明确提示“AI 辅助生成”实现细节以代码为准本文所有字段/流程都以文档给出的示例与说明为基础。WebSocket 还是 MQTT“到家前 10 分钟”更适合哪个如果你的目标是像小禾一样“到家前 10 分钟自动开空调”你会发现连接方式会影响可靠性WebSocket更适合本地联调与开发调试设备往往在需要语音会话/音频通道时才会去连服务器未必长期在线。MQTT更适合做“随时可控”的自动化设备可以常在线并支持断线重连你的后端在触发事件时更容易保证tools/call送达。本文为了让你最快跑通链路先用 WebSocket 搭一个最小调试服务把 MCP 跑通等你要做“到家触发”这种稳定自动化时再把同样的 MCP 消息搬到 MQTT 通道即可消息格式不变。到家前 10 分钟怎么触发Android 地理围栏TaskerMCP 负责“执行动作”Android 地理围栏负责“在合适的时间触发动作”。最省事、最可控的一种做法是用 Tasker 在进入某个围栏时打一个 Webhook让你的后端去tools/call。一个更贴近“提前 10 分钟”的小技巧做两个围栏——外圈围栏预热半径设大一点例如800m~2000m大概对应你回家路上 10 分钟的距离内圈围栏到家半径设小一点例如150m~300m用于确认“真的到家了”1手机侧Tasker AutoLocation准备安装Tasker主应用安装AutoLocationTasker 插件用于 GeoFence给Tasker/AutoLocation授予“始终允许定位”把它们从电池优化里排除否则后台触发会不稳定配置一个“预热围栏”触发示意步骤AutoLocation 里创建一个 GeoFenceHOME_WARMUP中心你家半径例如1200mTasker 里创建 ProfileEvent - Plugin - AutoLocation - Geofence选择Enter: HOME_WARMUP给这个 Profile 绑定一个 Task动作发 HTTP 请求到你的后端Tasker 的 HTTP 动作里你可以用类似配置MethodPOSTURLhttps://your-domain.example.com/webhook/home_warmupHeadersAuthorization: Bearer tokenBodyJSON{event:home_warmup,temp:26,minutes:10}这里的minutes: 10更多是“业务意图”外圈围栏的半径才是“接近 10 分钟”的近似手段你可以按通勤路线调两三次把半径校到体感最舒服。2后端侧收到 Webhook 后发 MCP tools/call后端收到home_warmup事件后做两件事选中目标设备session/设备 ID发送 MCPtools/call调用你注册的self.ac.preheat你最终要发出的 MCP payload 形态就是本文后面展示的这条method: tools/call - params.name: self.ac.preheat - params.arguments: { temp, ... }。准备清单尽量一次配齐硬件一块支持的小智设备/开发板常见为ESP32-S3也可能是ESP32-C3数据线尽量用能传输数据的 USB 线可选红外发射能力IR LED 驱动电路/现成红外模块用于把“空调预热”做成可执行工具软件Windows 10/11官方 ESP-IDF 搭建文档以 Windows 为例Git用于拉取固件源码ESP-IDF5.4.0或更高小智 v2.x 要求 5.4.0串口驱动如果系统识别不出 COM 口Node.js用于快速起一个 WebSocket MCP 调试服务可选Android 手机 Tasker/AutoLocation用于“到家前 10 分钟”地理围栏触发第一步安装 ESP-IDF必须是 5.4官方建议使用离线安装包安装 ESP-IDF并明确说明小智 v2.x 需要ESP-IDF 5.4.0或更高版本如果你装了5.3.x执行idf.py set-target之类的命令会报依赖解析错误。安装完成后用桌面的ESP-IDF 5.4 PowerShell启动环境验证能编译官方示例cdexamples/get-started/hello_world/ idf.py build如果这里就失败先别急着编译小智把 IDF 环境先修到“能编译 hello_world”再继续。第二步拉取小智固件源码并编译烧录1获取源码官方文档给出的仓库gitclone https://github.com/78/xiaozhi-esp322设置目标芯片# ESP32-S3idf.py set-target esp32s3# ESP32-C3idf.py set-target esp32c33配置开发板类型 WebSocket URL关键打开配置界面idf.py menuconfig官方文档提到的两个常用入口Xiaozhi Assistant - Board Selection选择板型Xiaozhi Assistant - Websocket - Websocket URL配置设备要连接的 WebSocket 地址建议你先用最简单的局域网地址例如ws://192.168.1.10:8080注意如果你把 URL 写成localhost通常会指向设备自己不会指向你的电脑。设备要连的是“你的电脑在局域网的 IP”。4编译与烧录常用命令一套带走# 仅编译idf.py build# 编译 烧录 串口监控idf.py build flash monitor# 更快的烧录速度idf.py -b2000000build flash monitor# 指定串口示例COM5idf.py -p COM5 build flash monitor官方文档的注意事项也很实用工程路径不要包含中文转移工程/切分支后必要时删除build目录再重编译第三步起一个最小后端WebSocket MCP Webhook目标是设备连接进来 → 你收到hello→ 你发initialize→ 你发tools/listAndroid 地理围栏触发 Webhook → 后端发tools/call→ 设备执行比如self.ac.preheat下面给一个“可直接跑起来”的最小 Node.js 服务同一个端口同时提供 WebSocket给设备连 HTTP Webhook给手机/Tasker 调用。只处理文本 JSON二进制音频帧直接忽略。1创建项目并安装依赖mkdirxiaozhi-mcp-devcdxiaozhi-mcp-devnpminit -ynpmi express ws2保存为 server.jsimport{WebSocketServer}fromws;importcryptofromnode:crypto;importhttpfromnode:http;importexpressfromexpress;constportNumber(process.env.PORT||8080);constauthTokenprocess.env.AUTH_TOKEN||;// 为空则不校验constacToolNameprocess.env.AC_TOOL_NAME||self.ac.preheat;constappexpress();app.use(express.json());constserverhttp.createServer(app);constwssnewWebSocketServer({server});functionsendJson(ws,obj){ws.send(JSON.stringify(obj));}functionsendMcp(ws,sessionId,payload){sendJson(ws,{session_id:sessionId,type:mcp,payload});}letnextId1000;constsessionsnewMap();// sessionId - wsfunctiongetAnyOnlineSession(){for(const[sessionId,ws]ofsessions.entries()){if(wsws.readyStatews.OPEN)return{sessionId,ws};}returnnull;}functionrequireAuth(req,res){if(!authToken)returntrue;constheaderString(req.headers.authorization||);if(headerBearer${authToken})returntrue;res.status(401).json({ok:false,error:unauthorized});returnfalse;}// 给 Android/Tasker 调用到家前 10 分钟触发空调预热app.post(/webhook/home_warmup,(req,res){if(!requireAuth(req,res))return;consttempNumber(req.body?.temp??26);constminutesNumber(req.body?.minutes??10);constonlinegetAnyOnlineSession();if(!online){res.status(503).json({ok:false,error:device_not_connected});return;}constidnextId;sendMcp(online.ws,online.sessionId,{jsonrpc:2.0,method:tools/call,params:{name:acToolName,arguments:{temp,minutes}},id});res.json({ok:true,id,tool:acToolName,args:{temp,minutes}});});// 手动调试入口直接发 tools/callcurl 一下就能验证链路app.post(/mcp/tools/call,(req,res){if(!requireAuth(req,res))return;constnameString(req.body?.name||);constargsreq.body?.arguments??{};if(!name){res.status(400).json({ok:false,error:missing_name});return;}constonlinegetAnyOnlineSession();if(!online){res.status(503).json({ok:false,error:device_not_connected});return;}constidnextId;sendMcp(online.ws,online.sessionId,{jsonrpc:2.0,method:tools/call,params:{name,arguments:args},id});res.json({ok:true,id});});wss.on(connection,(ws,req){constpeerreq.socket.remoteAddress;constsessionIdcrypto.randomUUID();console.log([ws] connected from${peer}, session_id${sessionId});sessions.set(sessionId,ws);ws.on(message,(data,isBinary){if(isBinary)return;// 音频帧等二进制数据这里先忽略letmsg;try{msgJSON.parse(data.toString(utf8));}catch(e){console.log([ws] non-json text:,data.toString(utf8).slice(0,200));return;}if(msg?.typehello){console.log([device - server] hello:,msg);// 参考官方 MQTTUDP 文档中的 hello response 结构返回 session_idsendJson(ws,{type:hello,transport:websocket,session_id:sessionId,audio_params:msg.audio_params});// 立刻开始 MCPinitialize - tools/listsendMcp(ws,sessionId,{jsonrpc:2.0,method:initialize,params:{capabilities:{}},id:1});sendMcp(ws,sessionId,{jsonrpc:2.0,method:tools/list,params:{cursor:},id:2});return;}if(msg?.typemcp){console.log([device - server] mcp:,msg.payload);return;}console.log([device - server] msg:,msg);});ws.on(close,(){sessions.delete(sessionId);console.log([ws] closed session_id${sessionId});});});server.listen(port,(){console.log([httpws] listening on :${port});console.log([http] POST /webhook/home_warmup);console.log([http] POST /mcp/tools/call);});再把package.json的type设成module或者把import改成require。最简单做法{type:module}3启动服务PORT8080AUTH_TOKENchange-me node server.js然后把你在menuconfig里配置的 WebSocket URL 指向这台机器的局域网 IP例如ws://192.168.1.10:8080重启设备即可。你也可以用curl直接模拟“到家前 10 分钟”的触发curl-X POST http://127.0.0.1:8080/webhook/home_warmup\-HContent-Type: application/json\-HAuthorization: Bearer change-me\-d{temp:26,minutes:10}如果你还没来得及实现self.ac.preheat红外发送先用手动入口验证链路也行curl-X POST http://127.0.0.1:8080/mcp/tools/call\-HContent-Type: application/json\-HAuthorization: Bearer change-me\-d{name:self.audio_speaker.set_volume,arguments:{volume:30}}第四步真正跑通一次 tools/call别猜工具名1先看tools/list的返回里面会列出设备当前注册的工具包括name、description、inputSchema。2从列表里复制一个name再发tools/call。官方协议文档里的tools/call请求示例payload 部分{jsonrpc:2.0,method:tools/call,params:{name:self.audio_speaker.set_volume,arguments:{volume:50}},id:3}你要发的仍然是“外壳 payload”的完整 MCP 消息{session_id:xxx,type:mcp,payload:{jsonrpc:2.0,method:tools/call,params:{name:从 tools/list 里复制,arguments:{}},id:3}}收到设备响应示例成功result.content里通常会有{ type: text, text: true }并且isError: false失败走 JSON-RPC 的error例如code: -32601示例空调预热红外的 tools/call 长什么样前提你已经在设备端注册了一个工具比如self.ac.preheat它的回调会发出“制热 设温度”的红外码。{jsonrpc:2.0,method:tools/call,params:{name:self.ac.preheat,arguments:{temp:26,minutes:10}},id:4}注意MCP 只负责“调用工具”。“提前 10 分钟”的计算与触发应由手机地理围栏/Home Assistant/你的后端定时逻辑来完成。第五步MCP 的“生产力入口”在固件里注册你自己的工具如果你要复刻小禾的场景建议把“红外空调预热”做成一个独立工具输入尽量简单整数温度、预热分钟数输出只返回true/false或错误字符串方便模型与自动化系统处理。官方 MCP 使用指南给了设备端注册工具的方式设备通过McpServer::AddTool注册可被后台调用的工具典型签名如下voidAddTool(conststd::stringname,conststd::stringdescription,constPropertyListproperties,std::functionReturnValue(constPropertyList)callback);它的关键点只有三个name要稳定、层次化推荐self.模块.功能properties描述输入参数布尔/整数/字符串可加范围/默认值callback返回值用简单类型bool/int/string让大模型好理解一个“空调预热”的最小形态示意红外发送函数请替换成你自己的实现mcp_server.AddTool(self.ac.preheat,空调预热开启制热并设置温度红外遥控,PropertyList({Property(temp,kPropertyTypeInteger,26,16,30),Property(minutes,kPropertyTypeInteger,10,0,30)}),[this](constPropertyListp)-ReturnValue{inttempp[temp].valueint();intminutesp[minutes].valueint();// TODO: 发送空调红外码制热 temp不同品牌协议不同// TODO: minutes 的“提前量”通常放在后端算这里也可以仅作为日志/校验returntrue;});排错清单含90% 的坑IDF 版本不够报project depends on idf (5.4.0)就升级到ESP-IDF 5.4工程路径有中文menuconfig/编译/依赖解析各种诡异问题先把路径改成纯英文设备连不上 WebSocket确认 URL 用的是电脑的局域网 IP不是localhost再检查防火墙是否拦截端口想做“到家触发”但设备不在线优先考虑 MQTT 常在线WebSocket 更适合本地联调未必长期保持连接找不到串口先装 USB 串口驱动再确认线是“数据线”不是“充电线”编译很慢官方建议关闭杀毒软件/Windows Defender必要时删build目录重来参考链接推荐顺序MCP 协议开发总览https://xiaozhi.dev/docs/development/mcp/MCP 协议文档消息结构/交互流程https://xiaozhi.dev/docs/development/mcp/protocol/MCP 使用指南AddTool/最佳实践https://xiaozhi.dev/docs/development/mcp/usage/ESP-IDF 环境搭建及编译小智https://xiaozhi.dev/docs/development/esp-idf-setup/MQTT UDP 混合协议含 hello 与外壳格式https://xiaozhi.dev/docs/development/mqtt-udp/