1. 初始化阶段 - MCP工具注册
在 mcp_server.cc 中,音量控制工具在 AddCommonTools() 中注册:
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;
2. 设备启动时的MCP服务初始化
// 在Application::Initialize()中
#if CONFIG_IOT_PROTOCOL_MCP
McpServer::GetInstance().AddCommonTools(); // 注册包括音量控制在内的所有工具
#endif
3. AI服务器获取工具列表
当小智AI连接到ESP32设备时,会发送工具列表请求:
AI发送请求:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
ESP32响应(基于mcp_server.cc的实现):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "self.get_device_status",
"description": "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\nUse this tool for: \n1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",
"inputSchema": {
"type": "object",
"properties": {}
}
},
{
"name": "self.audio_speaker.set_volume",
"description": "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.",
"inputSchema": {
"type": "object",
"properties": {
"volume": {
"type": "integer",
"minimum": 0,
"maximum": 100
}
},
"required": ["volume"]
}
}
]
}
}
4. 用户语音输入处理
用户说话: "把音量调到80"
↓
ESP32音频采集: Application::OnAudioInput()
↓
音频处理: AfeAudioProcessor处理音频数据
↓
Opus编码: OpusEncoderWrapper编码
↓
发送到AI: protocol_->SendAudio(packet)
5. AI理解和决策过程
小智AI接收到音频后:
- ASR识别: “把音量调到80”
- 意图理解: 需要调节音量到80
- 工具选择:
1)首先可能调用 self.get_device_status 获取当前音量
2)然后调用 self.audio_speaker.set_volume 设置音量
6. 第一步:获取当前设备状态
AI发送状态查询:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "self.get_device_status",
"arguments": {}
}
}
ESP32执行状态查询:
AddTool("self.get_device_status",
"Provides the real-time information of the device...",
PropertyList(),
[&board](const PropertyList& properties) -> ReturnValue {
return board.GetDeviceStatusJson();
});
ESP32返回设备状态:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"audio_speaker": {
"volume": 50
},
"screen": {
"brightness": 100,
"theme": "light"
},
"battery": {
"level": 85,
"charging": false
},
"network": {
"type": "wifi",
"ssid": "MyWiFi",
"rssi": -45
}
}
}
7. 第二步:设置音量
AI发送音量设置命令:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "self.audio_speaker.set_volume",
"arguments": {
"volume": 80
}
}
}
8. ESP32执行音量设置
基于 mcp_server.cc 中的实现:
[&board](const PropertyList& properties) -> ReturnValue {
auto codec = board.GetAudioCodec();
codec->SetOutputVolume(properties["volume"].value<int>());
return true;
}
详细执行过程:
// 1. 从MCP参数中提取音量值
int volume = properties["volume"].value<int>(); // volume = 80
// 2. 获取音频编解码器实例
auto codec = board.GetAudioCodec();
// 3. 设置音量(实际硬件操作)
codec->SetOutputVolume(80);
// 4. 可能的显示通知(如果设备有屏幕)
auto display = board.GetDisplay();
if (display) {
display->ShowNotification("音量: 80");
}
9. ESP32返回执行结果
{
"jsonrpc": "2.0",
"id": 3,
"result": true
}
10. AI生成语音回复
AI根据工具执行结果生成回复:
工具调用结果:
- get_device_status: 当前音量50
- set_volume: 设置成功
AI生成回复: "好的,已将音量从50调整到80"
11. TTS合成和音频传输
AI文字回复 → TTS合成 → Opus编码 → 发送到ESP32
12. ESP32播放AI回复
// 接收AI回复音频
protocol_->OnIncomingAudio([this](AudioStreamPacket&& packet) {
std::lock_guard<std::mutex> lock(mutex_);
if (device_state_ == kDeviceStateSpeaking) {
audio_decode_queue_.emplace_back(std::move(packet));
}
});
// 播放音频回复
void Application::OnAudioOutput() {
if (!audio_decode_queue_.empty()) {
auto packet = std::move(audio_decode_queue_.front());
audio_decode_queue_.pop_front();
// Opus解码
std::vector<int16_t> pcm_data;
opus_decoder_->Decode(packet.payload, pcm_data);
// 播放到扬声器
auto codec = Board::GetInstance().GetAudioCodec();
codec->WriteOutput(pcm_data);
}
}
完整时序图
关键代码执行路径
MCP工具调用的核心路径:
- 工具注册: McpServer::AddTool() 注册音量控制工具
- 工具列表: AI查询时返回所有已注册工具
- 工具执行: 收到 tools/call 时,查找并执行对应的lambda函数
- 硬件操作: board.GetAudioCodec()->SetOutputVolume(volume)
- 结果返回: 返回执行结果给AI
音频处理的并行路径:
- 音频输入: Application::OnAudioInput() 持续采集用户语音
- 音频输出: Application::OnAudioOutput() 播放AI回复
- 状态管理: Application::SetDeviceState() 管理设备状态切换
性能分析
整个音量调节流程的延迟构成:
- 语音采集和编码: ~50ms
- 网络传输到AI: ~50-100ms
- AI语音识别: ~200-500ms
- AI意图理解: ~100-200ms
- MCP工具调用1(状态查询): ~10-20ms
- MCP工具调用2(音量设置): ~10-20ms
- AI回复生成和TTS: ~200-400ms
- 音频传输和播放: ~100-200ms
总延迟: ~720-1440ms
这个流程展示了ESP32本地MCP实现的高效性,特别是工具执行部分的延迟极低(10-20ms),这是本地实现相比远程服务器的主要优势。
MCP相关源码和类解析
mcp_server.cc
/*
* MCP Server Implementation
* Reference: https://modelcontextprotocol.io/specification/2024-11-05
*/
// MCP 服务器实现
// 参考: https://modelcontextprotocol.io/specification/2024-11-05
#include "mcp_server.h" // MCP 服务器头文件
#include <esp_log.h> // ESP日志库
#include <esp_app_desc.h> // ESP应用程序描述库
#include <algorithm> // 标准算法库
#include <cstring> // 字符串操作库
#include <esp_pthread.h> // ESP Pthread库
#include <driver/gpio.h> // GPIO驱动
#include <driver/ledc.h> // LEDC (PWM) 驱动
#include "application.h" // 应用程序头文件
#include "display.h" // 显示头文件
#include "board.h" // 板级支持包头文件
#define TAG "MCP" // 日志标签
#define DEFAULT_TOOLCALL_STACK_SIZE 6144 // 工具调用默认栈大小
McpServer::McpServer() {
} // 构造函数,初始化McpServer
McpServer::~McpServer() {
// 析构函数,释放工具列表中的内存
for (auto tool : tools_) {
delete tool; // 删除每个工具对象
}
tools_.clear(); // 清空工具列表
}
void McpServer::AddCommonTools() {
// To speed up the response time, we add the common tools to the beginning of
// the tools list to utilize the prompt cache.
// Backup the original tools list and restore it after adding the common tools.
// 为了加快响应时间,我们将常用工具添加到工具列表的开头,以利用提示缓存。
// 备份原始工具列表,并在添加常用工具后恢复。
auto original_tools = std::move(tools_); // 备份原始工具列表
auto& board = Board::GetInstance(); // 获取Board单例
// 添加获取设备状态的工具
AddTool("self.get_device_status",
"Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n"
"Use this tool for: \n"
"1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n"
"2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",
PropertyList(),
[&board](const PropertyList& properties) -> ReturnValue {
return board.GetDeviceStatusJson(); // 返回设备状态的JSON字符串
});
// 添加设置音量工具
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) // 音量属性,整数类型,范围0-100
}),
[&board](const PropertyList& properties) -> ReturnValue {
auto codec = board.GetAudioCodec(); // 获取音频编解码器
codec->SetOutputVolume(properties["volume"].value<int>()); // 设置输出音量
return true; // 返回成功
});
// === 屏幕亮度控制工具 ===
// 如果设备支持背光控制,添加屏幕亮度设置工具
auto backlight = board.GetBacklight();
if (backlight) {
AddTool("self.screen.set_brightness",
"Set the brightness of the screen.",
PropertyList({
Property("brightness", kPropertyTypeInteger, 0, 100) // 亮度属性,整数类型,范围0-100
}),
[backlight](const PropertyList& properties) -> ReturnValue {
uint8_t brightness = static_cast<uint8_t>(properties["brightness"].value<int>()); // 获取亮度值
backlight->SetBrightness(brightness, true); // 设置背光亮度
return true; // 返回成功
});
}
auto display = board.GetDisplay(); // 获取显示对象
if (display && !display->GetTheme().empty()) { // 如果显示对象存在且主题不为空
// 添加设置屏幕主题工具
AddTool("self.screen.set_theme",
"Set the theme of the screen. The theme can be `light` or `dark`.",
PropertyList({
Property("theme", kPropertyTypeString) // 主题属性,字符串类型
}),
[display](const PropertyList& properties) -> ReturnValue {
display->SetTheme(properties["theme"].value<std::string>().c_str()); // 设置显示主题
return true; // 返回成功
});
}
auto camera = board.GetCamera(); // 获取摄像头对象
if (camera) { // 如果摄像头对象存在
// 添加拍照并解释工具
AddTool("self.camera.take_photo",
"Take a photo and explain it. Use this tool after the user asks you to see something.\n"
"Args:\n"
" `question`: The question that you want to ask about the photo.\n"
"Return:\n"
" A JSON object that provides the photo information.",
PropertyList({
Property("question", kPropertyTypeString) // 问题属性,字符串类型
}),
[camera](const PropertyList& properties) -> ReturnValue {
if (!camera->Capture()) { // 如果捕获照片失败
return "{\"success\": false, \"message\": \"Failed to capture photo\"}"; // 返回失败信息
}
auto question = properties["question"].value<std::string>(); // 获取问题
return camera->Explain(question); // 解释照片并返回信息
});
}
// Restore the original tools list to the end of the tools list
// 将原始工具列表恢复到当前工具列表的末尾
tools_.insert(tools_.end(), original_tools.begin(), original_tools.end());
}
void McpServer::AddTool(McpTool* tool) {
// Prevent adding duplicate tools
// 防止添加重复的工具
if (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)); // 创建新的McpTool对象并添加
}
void McpServer::ParseMessage(const std::string& message) {
cJSON* json = cJSON_Parse(message.c_str()); // 解析JSON消息
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse MCP message: %s", message.c_str()); // 记录错误日志:解析消息失败
return;
}
ParseMessage(json); // 调用重载的ParseMessage函数处理cJSON对象
cJSON_Delete(json); // 释放cJSON对象内存
}
void McpServer::ParseCapabilities(const cJSON* capabilities) {
// 解析客户端能力,特别是视觉(vision)相关的能力
auto vision = cJSON_GetObjectItem(capabilities, "vision"); // 获取"vision"能力
if (cJSON_IsObject(vision)) { // 如果"vision"是对象
auto url = cJSON_GetObjectItem(vision, "url"); // 获取"url"
auto token = cJSON_GetObjectItem(vision, "token"); // 获取"token"
if (cJSON_IsString(url)) { // 如果"url"是字符串
auto camera = Board::GetInstance().GetCamera(); // 获取摄像头对象
if (camera) { // 如果摄像头对象存在
std::string url_str = std::string(url->valuestring); // 获取url字符串
std::string token_str;
if (cJSON_IsString(token)) { // 如果"token"是字符串
token_str = std::string(token->valuestring); // 获取token字符串
}
camera->SetExplainUrl(url_str, token_str); // 设置解释URL和token
}
}
}
}
void McpServer::ParseMessage(const cJSON* json) {
// Check JSONRPC version
// 检查JSONRPC版本
auto version = cJSON_GetObjectItem(json, "jsonrpc"); // 获取"jsonrpc"版本
if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) {
ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null"); // 记录错误日志:无效的JSONRPC版本
return;
}
// Check method
// 检查方法
auto method = cJSON_GetObjectItem(json, "method"); // 获取"method"
if (method == nullptr || !cJSON_IsString(method)) {
ESP_LOGE(TAG, "Missing method"); // 记录错误日志:缺少方法
return;
}
auto method_str = std::string(method->valuestring); // 将方法转换为字符串
if (method_str.find("notifications") == 0) { // 如果方法是通知
return; // 直接返回,不处理通知
}
// Check params
// 检查参数
auto params = cJSON_GetObjectItem(json, "params"); // 获取"params"
if (params != nullptr && !cJSON_IsObject(params)) {
ESP_LOGE(TAG, "Invalid params for method: %s", method_str.c_str()); // 记录错误日志:方法的参数无效
return;
}
auto id = cJSON_GetObjectItem(json, "id"); // 获取"id"
if (id == nullptr || !cJSON_IsNumber(id)) {
ESP_LOGE(TAG, "Invalid id for method: %s", method_str.c_str()); // 记录错误日志:方法的id无效
return;
}
auto id_int = id->valueint; // 获取id的整数值
if (method_str == "initialize") { // 如果方法是"initialize"
if (cJSON_IsObject(params)) { // 如果参数是对象
auto capabilities = cJSON_GetObjectItem(params, "capabilities"); // 获取"capabilities"
if (cJSON_IsObject(capabilities)) { // 如果"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); // 回复初始化结果
} else if (method_str == "tools/list") { // 如果方法是"tools/list"
std::string cursor_str = ""; // 初始化游标字符串
if (params != nullptr) { // 如果参数不为空
auto cursor = cJSON_GetObjectItem(params, "cursor"); // 获取"cursor"
if (cJSON_IsString(cursor)) { // 如果"cursor"是字符串
cursor_str = std::string(cursor->valuestring); // 获取游标字符串
}
}
GetToolsList(id_int, cursor_str); // 获取工具列表
} else if (method_str == "tools/call") { // 如果方法是"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;
}
auto stack_size = cJSON_GetObjectItem(params, "stackSize"); // 获取栈大小
if (stack_size != nullptr && !cJSON_IsNumber(stack_size)) { // 如果栈大小不为空且不是数字
ESP_LOGE(TAG, "tools/call: Invalid stackSize"); // 记录错误日志:栈大小无效
ReplyError(id_int, "Invalid stackSize"); // 回复错误信息
return;
}
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); // 回复错误信息
}
}
void McpServer::ReplyResult(int id, const std::string& result) {
// 回复成功结果
std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; // 构建JSONRPC响应载荷
payload += std::to_string(id) + ",\"result\":";
payload += result;
payload += "}";
Application::GetInstance().SendMcpMessage(payload); // 发送MCP消息
}
void McpServer::ReplyError(int id, const std::string& message) {
// 回复错误信息
std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":"; // 构建JSONRPC错误响应载荷
payload += std::to_string(id);
payload += ",\"error\":{\"message\":\"";
payload += message;
payload += "\"}}";
Application::GetInstance().SendMcpMessage(payload); // 发送MCP消息
}
void McpServer::GetToolsList(int id, const std::string& cursor) {
// 获取工具列表
const int max_payload_size = 8000; // 最大载荷大小
std::string json = "{\"tools\":["; // 构建JSON字符串
bool found_cursor = cursor.empty(); // 检查游标是否为空
auto it = tools_.begin(); // 迭代器指向工具列表开头
std::string next_cursor = ""; // 下一个游标
while (it != tools_.end()) {
// 如果我们还没有找到起始位置,继续搜索
// If we haven't found the starting position, continue searching
if (!found_cursor) {
if ((*it)->name() == cursor) { // 如果找到游标对应的工具
found_cursor = true; // 设置找到游标标志
} else {
++it; // 移动到下一个工具
continue;
}
}
// 添加tool前检查大小
// Check size before adding tool
std::string tool_json = (*it)->to_json() + ","; // 获取工具的JSON字符串并添加逗号
if (json.length() + tool_json.length() + 30 > max_payload_size) {
// 如果添加这个tool会超出大小限制,设置next_cursor并退出循环
// If adding this tool exceeds the size limit, set next_cursor and break the loop
next_cursor = (*it)->name(); // 设置下一个游标为当前工具的名称
break; // 退出循环
}
json += tool_json; // 将工具JSON添加到字符串中
++it; // 移动到下一个工具
}
if (json.back() == ',') { // 如果JSON字符串最后一个字符是逗号
json.pop_back(); // 移除逗号
}
if (json.back() == '[' && !tools_.empty()) {
// 如果没有添加任何tool,返回错误
// If no tools have been added, return an error
ESP_LOGE(TAG, "tools/list: Failed to add tool %s because of payload size limit", next_cursor.c_str()); // 记录错误日志
ReplyError(id, "Failed to add tool " + next_cursor + " because of payload size limit"); // 回复错误信息
return;
}
if (next_cursor.empty()) { // 如果下一个游标为空
json += "]}"; // 结束JSON字符串
} else {
json += "],\"nextCursor\":\"" + next_cursor + "\"}"; // 结束JSON字符串并添加下一个游标
}
ReplyResult(id, json); // 回复结果
}
void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size) {
// 执行工具调用
auto tool_iter = std::find_if(tools_.begin(), tools_.end(),
[&tool_name](const McpTool* tool) {
return tool->name() == tool_name; // 根据工具名称查找工具
});
if (tool_iter == tools_.end()) { // 如果没有找到工具
ESP_LOGE(TAG, "tools/call: Unknown tool: %s", tool_name.c_str()); // 记录错误日志:未知工具
ReplyError(id, "Unknown tool: " + tool_name); // 回复错误信息
return;
}
PropertyList arguments = (*tool_iter)->properties(); // 获取工具的属性列表
try {
for (auto& argument : arguments) { // 遍历每个参数
bool found = false; // 标志位:是否找到参数
if (cJSON_IsObject(tool_arguments)) { // 如果工具参数是对象
auto value = cJSON_GetObjectItem(tool_arguments, argument.name().c_str()); // 获取参数值
if (argument.type() == kPropertyTypeBoolean && cJSON_IsBool(value)) { // 如果是布尔类型且cJSON是布尔值
argument.set_value<bool>(value->valueint == 1); // 设置布尔值
found = true;
} else if (argument.type() == kPropertyTypeInteger && cJSON_IsNumber(value)) { // 如果是整数类型且cJSON是数字
argument.set_value<int>(value->valueint); // 设置整数值
found = true;
} else if (argument.type() == kPropertyTypeString && cJSON_IsString(value)) { // 如果是字符串类型且cJSON是字符串
argument.set_value<std::string>(value->valuestring); // 设置字符串值
found = true;
}
}
if (!argument.has_default_value() && !found) { // 如果参数没有默认值且未找到
ESP_LOGE(TAG, "tools/call: Missing valid argument: %s", argument.name().c_str()); // 记录错误日志:缺少有效参数
ReplyError(id, "Missing valid argument: " + argument.name()); // 回复错误信息
return;
}
}
} catch (const std::exception& e) { // 捕获异常
ESP_LOGE(TAG, "tools/call: %s", e.what()); // 记录错误日志:异常信息
ReplyError(id, e.what()); // 回复错误信息
return;
}
// Start a task to receive data with stack size
// 启动一个任务来接收数据,并指定栈大小
esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); // 获取默认的pthread配置
cfg.thread_name = "tool_call"; // 设置线程名称
cfg.stack_size = stack_size; // 设置栈大小
cfg.prio = 1; // 设置优先级
esp_pthread_set_cfg(&cfg); // 设置pthread配置
// Use a thread to call the tool to avoid blocking the main thread
// 使用线程调用工具以避免阻塞主线程
tool_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(); // 分离线程
}
mcp_server.h
// MCP服务器头文件
// 实现模型控制协议(Model Control Protocol)服务器功能
// 提供工具注册、属性管理、消息解析、远程调用等功能
#ifndef MCP_SERVER_H
#define MCP_SERVER_H
// C++标准库
#include <string> // 字符串
#include <vector> // 动态数组
#include <map> // 映射容器
#include <functional> // 函数对象
#include <variant> // 变体类型
#include <optional> // 可选类型
#include <stdexcept> // 标准异常
#include <thread> // 线程
// 第三方库
#include <cJSON.h> // JSON解析库
// 返回值类型别名
// 支持布尔值、整数、字符串三种返回类型
using ReturnValue = std::variant<bool, int, std::string>;
// 属性类型枚举
// 定义MCP工具支持的属性数据类型
enum PropertyType {
kPropertyTypeBoolean, // 布尔类型
kPropertyTypeInteger, // 整数类型
kPropertyTypeString // 字符串类型
};
// Property类 - MCP工具属性
// 定义MCP工具的输入参数属性,支持类型检查、默认值、范围限制
// 主要功能:属性定义、类型安全、值验证、JSON序列化
class Property {
private:
// === 属性基本信息 ===
std::string name_; // 属性名称
PropertyType type_; // 属性类型
std::variant<bool, int, std::string> value_; // 属性值(支持多种类型)
bool has_default_value_; // 是否有默认值
// === 整数范围限制 ===
std::optional<int> min_value_; // 整数最小值(可选)
std::optional<int> max_value_; // 整数最大值(可选)
public:
// === 构造函数 ===
// 必需字段构造函数(无默认值)
Property(const std::string& name, PropertyType type)
: name_(name), type_(type), has_default_value_(false) {}
// 可选字段构造函数(带默认值)
template<typename T>
Property(const std::string& name, PropertyType type, const T& default_value)
: name_(name), type_(type), has_default_value_(true) {
value_ = default_value;
}
// 整数范围限制构造函数(无默认值)
Property(const std::string& name, PropertyType type, int min_value, int max_value)
: name_(name), type_(type), has_default_value_(false), min_value_(min_value), max_value_(max_value) {
if (type != kPropertyTypeInteger) {
throw std::invalid_argument("Range limits only apply to integer properties");
}
}
// 整数范围限制构造函数(带默认值)
Property(const std::string& name, PropertyType type, int default_value, int min_value, int max_value)
: name_(name), type_(type), has_default_value_(true), min_value_(min_value), max_value_(max_value) {
if (type != kPropertyTypeInteger) {
throw std::invalid_argument("Range limits only apply to integer properties");
}
if (default_value < min_value || default_value > max_value) {
throw std::invalid_argument("Default value must be within the specified range");
}
value_ = default_value;
}
// === 属性信息查询接口(内联函数) ===
inline const std::string& name() const { return name_; } // 获取属性名称
inline PropertyType type() const { return type_; } // 获取属性类型
inline bool has_default_value() const { return has_default_value_; } // 是否有默认值
inline bool has_range() const { return min_value_.has_value() && max_value_.has_value(); } // 是否有范围限制
inline int min_value() const { return min_value_.value_or(0); } // 获取最小值
inline int max_value() const { return max_value_.value_or(0); } // 获取最大值
// === 属性值操作接口 ===
// 获取属性值(模板函数,类型安全)
template<typename T>
inline T value() const {
return std::get<T>(value_);
}
// 设置属性值(模板函数,带范围检查)
template<typename T>
inline void set_value(const T& value) {
// 对整数值进行范围检查
if constexpr (std::is_same_v<T, int>) {
if (min_value_.has_value() && value < min_value_.value()) {
throw std::invalid_argument("Value is below minimum allowed: " + std::to_string(min_value_.value()));
}
if (max_value_.has_value() && value > max_value_.value()) {
throw std::invalid_argument("Value exceeds maximum allowed: " + std::to_string(max_value_.value()));
}
}
value_ = value;
}
// === JSON序列化接口 ===
// 将属性定义转换为JSON Schema格式
// 用于MCP协议中的工具描述和参数验证
std::string to_json() const {
cJSON *json = cJSON_CreateObject();
// 根据属性类型生成相应的JSON Schema
if (type_ == kPropertyTypeBoolean) {
cJSON_AddStringToObject(json, "type", "boolean");
if (has_default_value_) {
cJSON_AddBoolToObject(json, "default", value<bool>());
}
} else if (type_ == kPropertyTypeInteger) {
cJSON_AddStringToObject(json, "type", "integer");
if (has_default_value_) {
cJSON_AddNumberToObject(json, "default", value<int>());
}
// 添加整数范围限制
if (min_value_.has_value()) {
cJSON_AddNumberToObject(json, "minimum", min_value_.value());
}
if (max_value_.has_value()) {
cJSON_AddNumberToObject(json, "maximum", max_value_.value());
}
} else if (type_ == kPropertyTypeString) {
cJSON_AddStringToObject(json, "type", "string");
if (has_default_value_) {
cJSON_AddStringToObject(json, "default", value<std::string>().c_str());
}
}
// 转换为字符串并清理内存
char *json_str = cJSON_PrintUnformatted(json);
std::string result(json_str);
cJSON_free(json_str);
cJSON_Delete(json);
return result;
}
};
// PropertyList类 - 属性列表管理器
// 管理MCP工具的属性集合,提供属性查找、迭代、序列化等功能
// 主要功能:属性集合管理、名称索引、必需属性识别、JSON序列化
class PropertyList {
private:
std::vector<Property> properties_; // 属性列表
public:
// === 构造函数 ===
PropertyList() = default; // 默认构造函数
PropertyList(const std::vector<Property>& properties) : properties_(properties) {} // 列表构造函数
// === 属性管理接口 ===
void AddProperty(const Property& property) { // 添加属性
properties_.push_back(property);
}
// 按名称查找属性(重载[]操作符)
const Property& operator[](const std::string& name) const {
for (const auto& property : properties_) {
if (property.name() == name) {
return property;
}
}
throw std::runtime_error("Property not found: " + name);
}
// === 迭代器接口 ===
auto begin() { return properties_.begin(); } // 开始迭代器
auto end() { return properties_.end(); } // 结束迭代器
// 获取必需属性列表(没有默认值的属性)
std::vector<std::string> GetRequired() const {
std::vector<std::string> required;
for (auto& property : properties_) {
if (!property.has_default_value()) {
required.push_back(property.name());
}
}
return required;
}
// === JSON序列化接口 ===
// 将属性列表转换为JSON Schema properties格式
// 用于MCP工具的参数定义和验证
std::string to_json() const {
cJSON *json = cJSON_CreateObject();
// 遍历所有属性,将每个属性转换为JSON并添加到对象中
for (const auto& property : properties_) {
cJSON *prop_json = cJSON_Parse(property.to_json().c_str());
cJSON_AddItemToObject(json, property.name().c_str(), prop_json);
}
// 转换为字符串并清理内存
char *json_str = cJSON_PrintUnformatted(json);
std::string result(json_str);
cJSON_free(json_str);
cJSON_Delete(json);
return result;
}
};
// McpTool类 - MCP工具定义
// 定义一个可被远程调用的MCP工具,包含名称、描述、参数和回调函数
// 主要功能:工具定义、参数验证、远程调用、结果序列化
class McpTool {
private:
// === 工具基本信息 ===
std::string name_; // 工具名称
std::string description_; // 工具描述
PropertyList properties_; // 工具参数列表
std::function<ReturnValue(const PropertyList&)> callback_; // 工具回调函数
public:
// === 构造函数 ===
McpTool(const std::string& name,
const std::string& description,
const PropertyList& properties,
std::function<ReturnValue(const PropertyList&)> callback)
: name_(name),
description_(description),
properties_(properties),
callback_(callback) {}
// === 工具信息查询接口(内联函数) ===
inline const std::string& name() const { return name_; } // 获取工具名称
inline const std::string& description() const { return description_; } // 获取工具描述
inline const PropertyList& properties() const { return properties_; } // 获取工具参数列表
// === JSON序列化接口 ===
// 将工具定义转换为MCP协议标准的JSON格式
// 包含工具名称、描述和输入参数Schema
std::string to_json() const {
std::vector<std::string> required = properties_.GetRequired();
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "name", name_.c_str());
cJSON_AddStringToObject(json, "description", description_.c_str());
// 构建输入参数Schema
cJSON *input_schema = cJSON_CreateObject();
cJSON_AddStringToObject(input_schema, "type", "object");
// 添加属性定义
cJSON *properties = cJSON_Parse(properties_.to_json().c_str());
cJSON_AddItemToObject(input_schema, "properties", properties);
// 添加必需属性列表
if (!required.empty()) {
cJSON *required_array = cJSON_CreateArray();
for (const auto& property : required) {
cJSON_AddItemToArray(required_array, cJSON_CreateString(property.c_str()));
}
cJSON_AddItemToObject(input_schema, "required", required_array);
}
cJSON_AddItemToObject(json, "inputSchema", input_schema);
// 转换为字符串并清理内存
char *json_str = cJSON_PrintUnformatted(json);
std::string result(json_str);
cJSON_free(json_str);
cJSON_Delete(json);
return result;
}
// === 工具调用接口 ===
// 执行工具回调函数并返回MCP协议标准的结果格式
// 支持多种返回值类型的自动转换
std::string Call(const PropertyList& properties) {
ReturnValue return_value = callback_(properties);
// 构建MCP协议标准的返回结果
cJSON* result = cJSON_CreateObject();
cJSON* content = cJSON_CreateArray();
cJSON* text = cJSON_CreateObject();
cJSON_AddStringToObject(text, "type", "text");
// 根据返回值类型进行相应的转换
if (std::holds_alternative<std::string>(return_value)) {
cJSON_AddStringToObject(text, "text", std::get<std::string>(return_value).c_str());
} else if (std::holds_alternative<bool>(return_value)) {
cJSON_AddStringToObject(text, "text", std::get<bool>(return_value) ? "true" : "false");
} else if (std::holds_alternative<int>(return_value)) {
cJSON_AddStringToObject(text, "text", std::to_string(std::get<int>(return_value)).c_str());
}
cJSON_AddItemToArray(content, text);
cJSON_AddItemToObject(result, "content", content);
cJSON_AddBoolToObject(result, "isError", false);
// 转换为字符串并清理内存
auto json_str = cJSON_PrintUnformatted(result);
std::string result_str(json_str);
cJSON_free(json_str);
cJSON_Delete(result);
return result_str;
}
};
// McpServer类 - MCP服务器
// 实现模型控制协议服务器,管理工具注册、消息解析、远程调用等功能
// 采用单例模式,提供全局统一的MCP服务接口
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); // 解析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_; // 工具调用线程
};
#endif // MCP_SERVER_H
相关类解析
实现模型控制协议(Model Control Protocol, MCP)服务器功能的组件。这四个类协同工作,共同构建了一个能够注册工具、管理属性、解析消息和处理远程调用的系统。
四个核心类及其作用
1. Property 类 (MCP工具属性)
- 作用:定义了 MCP工具的单个输入参数的属性。它不仅包含属性的名称和数据类型(布尔、整数、字符串),还支持默认值、整数范围限制等高级功能。它还提供了类型安全的属性值存取接口(通过 std::variant)以及将属性定义转换为 JSON Schema 格式的功能,用于协议中的工具描述和参数验证。
- 职责:
- 定义单个属性的元数据(名称、类型)。
- 存储和管理属性值,支持多种类型。
- 支持默认值和整数范围验证。
- 将自身序列化为 JSON 格式。
2. PropertyList 类 (属性列表管理器)
- 作用:管理 Property 对象的集合。它是一个容器,用于封装一个 MCP
工具的所有参数属性。它提供了添加属性、按名称查找属性、迭代属性以及将整个属性列表序列化为 JSON Schema properties
部分的功能。 - 职责:
- 作为 Property 对象的集合(内部使用 std::vector)。
- 提供方便的接口来添加和访问(通过重载 [] 操作符)属性。
- 识别并返回所有必需属性(没有默认值的属性)的列表。
- 将自身序列化为 JSON Schema 的 properties 部分。
3. McpTool 类 (MCP工具定义)
- 作用:定义了一个可以被远程调用的 MCP 工具。它包含了工具的名称、描述、它所接受的参数列表(通过
PropertyList)以及一个实际执行工具逻辑的回调函数。它是 MCP 协议中可调用操作的核心抽象。 - 职责:
- 封装工具的基本信息(名称、描述)。
- 包含一个 PropertyList 对象来定义工具的所有输入参数。
- 存储一个 std::function 对象作为工具的实际业务逻辑(回调函数),该函数接受 PropertyList 作为输入,并返回一个 ReturnValue。
- 将自身序列化为 MCP 协议标准的 JSON 格式,包括工具的名称、描述和输入参数的 JSON Schema。
- 提供 Call 方法来执行回调函数,并将结果格式化为 MCP 协议标准的 JSON 响应。
4. McpServer 类 (MCP服务器)
- 作用:实现整个 MCP 协议服务器的核心功能。它是一个单例模式的类,确保系统中只有一个服务器实例。McpServer 负责管理所有注册的
McpTool,解析传入的 MCP 消息,根据消息内容调用相应的工具,并回复结果或错误信息。 - 职责:
- 作为单例提供全局访问点。
- 管理所有注册的 McpTool 对象(内部使用 std::vector<McpTool*>)。
- 提供AddTool 方法来注册新的工具。
- 解析传入的 JSON 或字符串消息(ParseMessage)。
- 处理不同类型的 MCP 协议消息,例如: GetToolsList:获取所有注册工具的列表。 DoToolCall:执行特定的工具调用。 构建并回复 MCP 协议标准的成功或错误结果。
类之间的联系
这四个类之间存在着紧密的组合 (Composition) 和依赖 (Dependency) 关系,共同构建了 MCP 服务器的功能:
1. PropertyList 组合 Property:
- 一个 PropertyList 对象内部包含一个 std::vector。这意味着 PropertyList 是
Property 对象的集合或管理器。 - PropertyList 负责存储和操作多个 Property 实例。
McpTool 组合 PropertyList:
- 一个 McpTool 对象内部包含一个 PropertyList 对象 (properties_)。这个 PropertyList
定义了该工具所接受的所有输入参数。 - 当 McpTool 被调用时 (Call 方法),它会接收一个 PropertyList 作为参数,然后将其传递给其内部的回调函数。
McpServer 依赖/管理 McpTool:
- McpServer 内部维护一个 std::vector<McpTool*>(工具列表)。这意味着 McpServer
负责注册、存储和管理系统中的所有 McpTool 实例。 - McpServer 的 AddTool 方法用于将 McpTool 实例添加到其管理列表中。
- 当 McpServer 接收到 DoToolCall 消息时,它会根据消息中指定的工具名称,在它管理的 McpTool
列表中查找并调用相应的 McpTool 的 Call 方法。
总结关系流:
- Property 定义了单个参数的详细信息。
- PropertyList 将多个参数(Property 对象)组织成一个集合。
- McpTool 代表一个可执行的功能,它使用 PropertyList 来定义其输入参数,并包含实际执行逻辑的回调。
- McpServer 是整个系统的协调者,它管理所有 McpTool,解析外部请求,并根据请求调用对应的 McpTool。
通过这种分层和组合的设计,整个 MCP 服务器的结构清晰、职责明确,易于扩展和维护。