1、一个开源协议,一个开源落地项目
MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
MCP(模型上下文协议)是用于将AI应用连接到外部系统的开源标准。
作为一个去年(2024年)11月份发布的开源协议,它在短短一年时间里飞速发展成为AI领域极其重要的协议。
xiaozhi-esp32是由开发者"虾哥"于2024年底在GitHub上开源的面向嵌入式 AI应用的软硬件一体化项目,旨在将大语言模型(LLM)的能力通过Model Context Protocol(MCP)安全、高效地延伸至资源受限的 ESP32 系列芯片上,打造可语音交互、可自主控制物理世界的智能终端。截至写稿时,它在github上拥有了22.7k的Star,也是一个妥妥的明星项目。
本文根据作者使用MCP协议和xiaozhi-esp32的工作经历,解读和分析MCP协议在具体项目的落地实践。
2、MCP协议2025年关键发展回顾
2.1 MCP解决了什么痛点?
在大模型(LLM)应用爆发式增长的背景下,"如何让LLM可靠地调用外部工具"成为工程落地的核心瓶颈。2023--2024年间,各框架各自寻找解决方案:LangChain使用自定义Tool Schema,Ollama依赖函数描述字符串。这种碎片化导致工具无法跨平台复用、上下文状态管理混乱以及边缘设备难以解析非结构化指令等问题。
为了解决这一问题,MCP于2024年中由Anthropic、LangChain社区及多位开源贡献者联合发起,旨在提供一个语言无关、轻量、可扩展的标准化协议,用于描述LLM与外部工具之间的交互上下文。进入2025年以后,MCP从概念验证快速走向生态构建,成为AI工程化基础设施的重要一环。
2.2 2025年MCP核心版本演进
2025年是MCP协议迅速发展的关键一年。全年共发布三个重要规范版本,逐步完善协议语义与工程适配能力。
| 版本 | 关键特性 |
|---|---|
| 2024-11-05 | 定义基础消息结构:ToolRequest / ToolResponse,支持 JSON Schema 描述工具参数,无认证机制 |
| 2025-03-26 | 授权升级至 OAuth 2.1;工具注解系统,JSON-RPC批处理;引入 Streamable HTTP(单端点),支持双向通信与断线恢复,简化架构 |
| 2025-06-18 | 新增 Elicitation 协议,支持主动询问用户确认;工具注解扩展为 结构化输出;移除 JSON-RPC 批处理,简化协议;增强断点续传机制 |
| 2025-11-25 | 授权与安全增强:OpenID Connect Discovery 1.0(PR #797)支持标准化授权服务器发现、增量范围授权(Incremental Scope Consent);元数据与可视化;交互与协议扩展 |
注:
- 在这里查找所有规范文档modelcontextprotocol/specification仓库
- MCP版本号命名采用日期格式
2.3 主流框架支持与官方SDK的双向奔赴
2025年,MCP迅速获得主流AI框架和平台的官方或社区级支持,简直就是遍地开花。比如LangChain 很早就开始对MCP提供支持,目前可以通过langchain-mcp-adapters库来使用在MCP服务器上定义的工具。入下图所示:

同时,MCP协议也提供了多种开发语言的SDK供开发者使用:

其中MCP Python SDK是使用人数最多的,也印证了python是AI时代最合适的语言这个说法。
2.4 小结
2025年,MCP协议完成了从"理念"到"基础设施"的关键跨越。其核心价值不仅在于统一了工具调用格式,更在于为LLM与物理世界之间架起了一座可编程的桥梁。
3、xiaozhi-esp32项目概览
3.1 项目简介
xiaozhi-esp32 是由开发者"虾哥"于2024年12月在GitHub上开源的AIoT项目,目标是打造一个低成本、可语音交互、能控制物理设备的边缘AI助手终端 。
到了今天,项目的描述被精炼概括为:一个基于MCP的聊天机器人|An MCP-based chatbot。
作为语音交互入口,小智AI聊天机器人利用Qwen/DeepSeek等大型模型的AI功能,通过MCP协议实现多终端控制。

该项目已经具备了非常完善的AI对话硬件应该具备的功能,包含:
- 基于 ESP-IDF 的嵌入式固件;
- 配套的云端服务参考实现;
- 详细的硬件接线与烧录教程;
- 面向普通用户的开箱即用体验(支持免开发环境烧录)。
这是让我惊喜异常的,因为我在2024年初开始搞AI音响,当时可参考的项目非常稀少。我还记得当时采用HTTP通信,为了调通协议头而费劲了心思。现在,我可以更新到小智方案了。
3.2 项目整体架构
xiaozhi-esp32 采用典型的"云-边协同"架构,将计算密集型任务(如语音识别、大模型推理)交由云端处理,而指令解析与物理控制则在本地 ESP32 设备完成,兼顾响应速度与功能完整性。
端到端流程如下图:

3.3 核心功能与硬件支持
3.3.1 主要功能特性
-
语音交互全流程支持
支持唤醒词检测(默认"你好小智")、流式语音上传、TTS 语音播报,形成完整对话闭环。
-
MCP驱动的设备控制
通过MCP协议调用预定义工具,例如控制底盘前进:
json{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "self.chassis.go_forward", "arguments": {} }, "id": 2 } -
多模态反馈
- OLED / LCD 屏幕显示表情或状态(如"思考中..."、"执行成功");
- LED呼吸灯随对话状态变化颜色;
- 语音 + 视觉双重反馈提升用户体验。
-
低功耗与离线能力
- 支持深度睡眠模式,待机电流 < 10μA;
- 唤醒词识别完全本地运行,不依赖网络;
- 断网时可缓存指令,恢复后重试。
3.3.2 硬件平台兼容性
项目适配多种主流 ESP32 开发板,包括:
| 平台 | 特点 | 典型应用场景 |
|---|---|---|
| ESP32-S3-BOX | 内置麦克风、喇叭、屏幕 | 桌面语音助手 |
| M5Stack CoreS3 | 彩色触摸屏 + 扬声器 | 教学演示、智能家居面板 |
| LILYGO T-Circle-S3 | 圆形AMOLED + 小巧机身 | 可穿戴AI吊坠 |
| ESP32-S3开发板 | 市面上成本非常低的方案 | 超低成本IoT控制器 |
此外,项目还支持通过 红外发射管 控制传统家电(如空调、电视),极大扩展了物理控制范围。
3.4 通信与协议栈
xiaozhi-esp32 支持两种主要通信模式,适应不同部署场景:
| 模式 | 协议 | 适用场景 |
|---|---|---|
| WebSocket模式 | WebSocket既传文本又可以传音频 | 高频交互、低延迟要求(如实时对话) |
| MQTT + UDP混合模式 | MQTT用于控制信令,UDP用于音频流 | 弱网环境、4G移动网络 |
所有来自LLM的工具调用指令均封装为 MCP 格式的 JSON 对象,通过上述通道下发至设备。设备端不关心LLM如何生成该消息,只负责"按规执行"即可。
3.5 项目选择MCP带来的好处
引入MCP后,项目获得三大优势:
- 标准化:工具描述符合社区规范,便于与其他MCP服务互通;
- 可扩展:新增设备只需注册新tool,无需修改通信层;
- 安全性 :通过schema限制参数范围,防止非法指令(如
brightness=9999)。
4、源码解析:MCP是如何在ESP32上运行的?
4.1 相关源文件简介
由于MCP消息是封装在基础通信协议(如 WebSocket 或 MQTT)的消息体中的,今天的源码分析基于WebSocket通信,所以我们只关心如下几个源文件:
- main/application.cc
它是项目的核心主控逻辑模块,协调硬件、网络、音频、协议通信与用户交互,管理设备全生命周期状态,并提供MCP能力集成。 - main/mcp_server.h, main/mcp_server.cc
提供了MCP服务端相关的工具类,包括:
ImageContent :封装一张图片的内容,用于 MCP 工具返回图像结果。
Property :描述一个MCP工具的输入参数(属性)。
PropertyList :管理一组Property,代表一个工具的所有输入参数。
McpTool :一个可被LLM调用的本地工具。
McpServer:MCP服务的全局入口,负责协议解析与调度。它是一个单例。 - main/protocol/protocol.cc, main/protocol/websocket_protocol.cc
protocol是一个通信协议抽象类,websocket_protocol是实现类。
4.2 整体交互图
MCP在xiaozhi-esp32上的整体交互流程如下图:

4.3 协议格式
消息结构格式如下所示:
json
{
"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)。
4.4 交互流程
MCP的交互主要是围绕客户端(后台 API)发现和调用设备上的"工具"(Tool)进行的。后台API相当于MCP的客户端,ESP32设备其实是MCP的服务器。
(1) 设备端初始化
esp32设备启动后,首先设备上电、初始化 Application,创建并初始化实现 Protocol 接口的WebSocket协议实例(WebsocketProtocol)
参考源码application.cc的start函数:
cpp
void Application::Start() {
// ...
if (ota.HasMqttConfig()) {
protocol_ = std::make_unique<MqttProtocol>();
} else if (ota.HasWebsocketConfig()) {
protocol_ = std::make_unique<WebsocketProtocol>();
}
// ...
}
(2) 建立WebSocket连接并发送"hello"消息
当设备需要开始语音会话时(如被用户唤醒),调用 OpenAudioChannel()函数,它会根据配置获取 WebSocket URL、设置若干请求头(Authorization, Protocol-Version, Device-Id, Client-Id)以及调用 Connect() 与服务器建立 WebSocket 连接。
参考源码websocket_protocol.cc的OpenAudioCHannel函数:
cpp
bool WebsocketProtocol::OpenAudioChannel() {
// ...
websocket_->SetHeader("Protocol-Version", std::to_string(version_).c_str());
websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
websocket_->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
websocket_->OnData([this](const char* data, size_t len, bool binary) {
// 接收数据处理
}
ESP_LOGI(TAG, "Connecting to websocket server: %s with version: %d", url.c_str(), version_);
if (!websocket_->Connect(url.c_str())) {
ESP_LOGE(TAG, "Failed to connect to websocket server");
SetError(Lang::Strings::SERVER_NOT_CONNECTED);
return false;
}
// 连接成功,发送hello消息
// Send hello message to describe the client
auto message = GetHelloMessage();
if (!SendText(message)) {
return false;
}
// ...
}
hello消息的格式参考:
json
{
"type": "hello",
"version": 1,
"features": {
"mcp": true
},
"transport": "websocket",
"audio_params": {
"format": "opus",
"sample_rate": 16000,
"channels": 1,
"frame_duration": 60
}
}
- 其中
features中的"mcp": true表示支持 MCP 协议。 frame_duration的值对应OPUS_FRAME_DURATION_MS(例如 60ms)。
(3) 服务器回复 "hello"及初始化MCP
服务端收到hello后,会回复一个hello。此时通信正式建立。
同时,服务端确认设备支持MCP后, 会发送initialize初始化MCP会话。
服务器返回hello的消息格式如下:
json
{
"type": "hello",
"transport": "websocket",
"session_id": "xxx",
"audio_params": {
"format": "opus",
"sample_rate": 24000,
"channels": 1,
"frame_duration": 60
}
}
初始化MCP会话消息格式如下:
json
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"capabilities": {
// 客户端能力,可选
// 摄像头视觉相关
"vision": {
"url": "...", //摄像头: 图片处理地址(必须是http地址, 不是websocket地址)
"token": "..." // url token
}
// ... 其他客户端能力
}
},
"id": 1 // 请求 ID
}
解析MCP消息的方法在mcp_server.cc的ParseMessage,截取片段如下:
cpp
void McpServer::ParseMessage(const cJSON* json) {
// ...
if (method_str == "initialize") {
if (cJSON_IsObject(params)) {
auto capabilities = cJSON_GetObjectItem(params, "capabilities");
if (cJSON_IsObject(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);
}
// ...
}
如上面代码所示,在收到MCP初始化消息后,设备端立即做成响应。
(4)发现设备工具列表
接着,服务端会发送"tools/list"请求,消息格式如下:
json
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {
"cursor": "" // 用于分页,首次请求为空字符串
},
"id": 2 // 请求 ID
}
同样的,也是在ParseMessage方法中解析:
cpp
void McpServer::ParseMessage(const cJSON* json) {
// ...
if (method_str == "tools/list") {
std::string cursor_str = "";
bool list_user_only_tools = false;
if (params != nullptr) {
auto cursor = cJSON_GetObjectItem(params, "cursor");
if (cJSON_IsString(cursor)) {
cursor_str = std::string(cursor->valuestring);
}
auto with_user_tools = cJSON_GetObjectItem(params, "withUserTools");
if (cJSON_IsBool(with_user_tools)) {
list_user_only_tools = with_user_tools->valueint == 1;
}
}
GetToolsList(id_int, cursor_str, list_user_only_tools);
}
// ...
}
设备端在获取工具列表后,返回结果。细节参考GetToolsList方法,以及McpTool类。当然,想要能够发现设备工具,前提是要将工具加入到列表中。请自行参考并调查如下函数:
cpp
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));
}
响应消息的格式如下:
json
{
"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 值
}
}
(5) 调用设备工具
当后台需要执行设备上的某个具体功能时,就会调用tools/call方法来调用设备工具。
消息格式为:
json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "self.audio_speaker.set_volume", // 要调用的工具名称
"arguments": {
// 工具参数,对象格式
"volume": 50 // 参数名及其值
}
},
"id": 3 // 请求 ID
}
同样的,也是在ParseMessage方法中解析:
cpp
void McpServer::ParseMessage(const cJSON* json) {
// ...
if (method_str == "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;
}
// 执行具体任务,比如上文中调整音量为50
DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments);
}
// ...
}
返回消息结构为:
json
{
"jsonrpc": "2.0",
"id": 3, // 匹配请求 ID
"result": {
"content": [
// 工具执行结果内容
{ "type": "text", "text": "true" } // 示例:set_volume 返回 bool
],
"isError": false // 表示成功
}
}
至此,一个正常的MCP交互流程就完成了。请结合时序图和代码一并来看,效果更加。
5、 写在最后
Model Context Protocol(MCP)作为一种面向大语言模型(LLM)的标准化工具调用协议,为LLM与物理世界的交互提供了通用接口。本文基于ESP32嵌入式平台上的实际实现,深入解读了MCP的运行机制,我们可以看出它们配合的几个特点:
轻量的协议适配 :
该版本的xiaozhi-esp32项目实现了MCP v2024-11-05 规范的核心方法(initialize、tools/list、tools/call),采用 JSON-RPC 2.0 作为传输格式,与WebSocket无缝集成。
硬件能力抽象化 :
将音频、屏幕、摄像头、系统信息等本地硬件功能封装为具名工具(如 self.audio_speaker.set_volume),形成统一的"设备能力 API"。工具描述包含自然语言说明与结构化输入 Schema,便于LLM理解与调用。
权限管控 :
引入User-Only工具机制,将敏感操作(如重启、固件升级)与普通控制指令隔离,确保LLM无法自主触发高危行为。
短短的一年,技术发展飞快!
可以预见的是,未来随着协议演进与硬件升级,嵌入式设备将成为LLM在现实世界中的"眼睛、耳朵与双手",而且这些感官功能越来越丝滑,越来越强大。
参考:
1、https://github.com/modelcontextprotocol
2、https://github.com/78/xiaozhi-esp32
3、https://github.com/modelcontextprotocol/python-sdk
4、https://modelcontextprotocol.io/specification/versioning
5、https://modelcontextprotocol.io/specification/2025-11-25/changelog