💡温馨提示:本教程同时适用于 Ai-WV01-32S模组
1. MCP 服务简介
MCP 是专门为 AI 平台设计的服务,作用是接收并处理 AI 平台发送的指令,然后返回处理结果。开发者不用关心 MCP 服务和 AI 平台之间的通信细节(SDK 已经实现了),只需要知道怎么添加 MCP 功能,实现设备的控制和状态查询就行。
▫️MCP 参考资料
2. MCP 工具开发
工具 是 MCP(模型上下文协议)里的基础功能单元。简单说,就是服务器暴露给客户端的可执行功能。有了工具,AI 模型就能和外部系统交互了(比如控制设备、获取设备状态)。AI 模型会自动调用这些工具,也可以配置成需要人工审批后再调用。
简单理解:AI 模型要控制设备或者查设备状态,都得通过调用 工具 来实现。
▫️MCP 工具开发流程
- 创建
工具
,确定工具的名称、功能描述、需要的参数以及处理请求的回调函数等。
- 创建
- 把创建好的
工具
注册到MCP
服务里。
- 把创建好的
- 编写
工具
的回调函数,实现工具具体的控制或查询功能。
- 编写
▫️MCP 工具开发示例
🔹步骤 1:创建工具
创建 工具
要用到 mcp_server_tool_t
结构体,这个结构体的定义如下:
typedef struct
{
int error_code; // 错误码
char value[64]; // 内容
char *type; // 类型
} returnValues_t;
/**
* @brief 工具结构体
* 用于创建 MCP 工具和描述 MCP 工具的输入参数
*/
typedef struct
{
char *name; // 工具名称
char *description; // 工具描述
returnValues_t (*setRequestHandler)(void *); // 工具回调函数
void (*checkRequestHandler)(void *); // 工具回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
/**
* @brief 输入参数结构体
* 用于描述工具的输入参数,告诉 AI 平台,该工具提供了哪些参数,参数的用途是什么。
*/
typedef struct
{
properties_t properties[MCP_SERVER_TOOL_PROPERTIES_NUM]; // 属性
methods_t methods[MCP_SERVER_TOOL_METHODS_NUM]; // 方法
} inputSchema_t;
/**
* @brief 属性结构体
* 用于描述工具的属性,告诉 AI 平台,该工具提供了哪些可以读取的属性,比如:查询当前音量
*/
typedef struct
{
char *name; // 属性名称
char *description; // 属性描述
mcp_server_tool_type_t type; // 属性类型
} properties_t;
/**
* @brief 方法结构体
* 用于描述工具的方法,告诉 AI 平台,该工具提供了哪些可以调用的方法,比如:设置音量
*/
typedef struct
{
char *name; // 方法名称
char *description; // 参数描述
parameters_t parameters[MCP_SERVER_TOOL_METHODS_PARAMETERS_NUM]; // 方法参数
} methods_t;
/**
* @brief 参数结构体
* 用于 方法的输入参数,告诉 AI 平台, 需要下发什么数据,才能实现控制,比如:设置音量时需要下发的关键字和值的范围
*/
typedef struct
{
char *name; // 参数名称
char *description; // 参数描述
mcp_server_tool_type_t type; // 参数类型
} parameters_t;
结构体说明:
name
:工具名称,是工具的唯一标识,不能重复。description
:工具描述,用简单的语言说明这个工具能实现什么功能(方便 AI 理解)。setRequestHandler
:控制回调函数,当 AI 发送控制指令(比如“打开灯”
)时,会调用这个函数。checkRequestHandler
:查询回调函数,当 AI 发送查询指令(比如“灯是不是开着的”
)时,会调用这个函数。inputSchema
:输入参数描述,告诉 AI 这个工具支持哪些查询和控制操作:properties
:可查询的属性(AI 能获取这些属性的当前值):name
:属性名称(比如“enabled”
表示灯的开关状态)。description
:属性描述(说明这个属性代表什么,比如“当前灯光状态”
)。type
:属性类型(比如布尔型MCP_SERVER_TOOL_TYPE_BOOLEAN
、数值型MCP_SERVER_TOOL_TYPE_NUMBER
)。
methods
:可调用的控制方法(AI 能通过这些方法控制设备):name
:方法名称(比如“SetEnabled”
表示设置灯的开关)。description
:方法描述(说明这个方法的作用,比如“设置是否打开灯光”
)。parameters
:方法需要的参数:name
:参数名称(比如“enabled”
表示开关参数)。description
:参数描述(说明参数的具体含义和范围,比如 “true
表示打开灯光,false
表示关闭灯光”)。type
:参数类型(和属性类型类似)。
- 创建示例(LED 工具):
可以在 aipi-palchatv1/mcp_sercer/user_mcp_tools.c
的 user_mcp_creat_tools_examples
函数中定义工具。
int user_mcp_creat_tools_examples(cJSON *toolsList)
{
if (toolsList == NULL)
{
return -1;
}
cJSON *json_toolsList = toolsList;
mcp_server_tool_t led = {
.name = "Light",
.description = "控制是否打开灯光",
.setRequestHandler = NULL, //后续实现的设置回调
.checkRequestHandler = NULL,//后续实现的查询回调
.inputSchema = {
// 设置属性,让小智AI读取当前LED状态
.properties[0].name = "enabled",
.properties[0].description = "当前灯光状态",
.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
// 设置方法,让小智AI控制LED
.methods[0].name = "SetEnabled",
.methods[0].description = "设置是否打开灯光",
// 添加设置参数
.methods[0].parameters[0].name = "enabled",
.methods[0].parameters[0].description = "true 表示打开灯光,false 表示关闭灯光",
.methods[0].parameters[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
},
};
}
🔹步骤2:注册工具
通过 mcp_server_add_tool_to_toolList
函数把创建好的工具添加到 MCP 服务的工具列表里,这样 AI 平台才能识别到这个工具。
/**
* @brief 添加工具到工具列表
*
* @param toolsList 工具列表
* @param tool 工具对象
* @return int int 0 表示成功,-32602 表示失败
*/
int mcp_server_add_tool_to_toolList(void *toolsList, mcp_server_tool_t *tool)
- 示例(承接 LED 工具创建):
mcp_server_add_tool_to_toolList(json_toolsList, &led);
🔹步骤3:实现设置回调函数
(1)设置回调函数(处理控制请求)
当 AI 平台发送控制指令(比如
“打开灯光”
)时,MCP 服务
就会调用这个函数。函数的参数value
是AI
平台传过来的控制指令(格式是JSON
字符串,里面包含具体的控制参数)。
setRequestHandler
函数原型(在工具结构体中定义):
typedef struct
{
int error_code; // 错误码
char value[64]; // 内容
char *type; // 类型
} returnValues_t;
typedef struct
{
char *name; // 工具名称
char *description; // 工具描述
returnValues_t (*setRequestHandler)(void *); // 工具回调函数
void (*checkRequestHandler)(void *); // 工具回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
参数说明
(void * )
:AI 平台下发的控制指令(JSON
格式字符串),需要解析这个字符串才能获取具体的控制参数(比如 “打开灯”
对应的参数是{"enabled":true}
)。解析方法可以看下面的 “获取被设置参数值”。
(2) 获取被设置参数值
要从 AI 传过来的 JSON 指令中获取具体的参数值,可以用
mcp_server_get_param
函数,这个函数的作用就是从JSON
中提取指定名称的参数。
mcp_param_t mcp_server_get_param(mcp_param_t params, char *param_name)
{
if (params == NULL || param_name == NULL)
{
return NULL;
}
return cJSON_GetObjectItem(params, param_name);
}
简单说,这个函数就是个 JSON 解析工具,用法很简单,传参的时候指定要获取的参数名称就行。可以参考下面的:设置回调示例(控制 LED 为例
)
(3) 返回设置结果
控制回调函数执行完之后,需要把处理结果返回给 AI 平台。返回的结果要放在
returnValues_t
结构体里,通过控制回调函数返回。
returnValues_t
结构体定义:
typedef struct
{
int error_code; // 错误码
char value[64]; // 内容
char *type; // 类型
} returnValues_t;
参数说明
int error_code
:错误码,0 表示成功,-32602 表示失败。char value[64]
:内容,用于描述设置结果,内容为设置后的参数值。char *type
:类型,用于描述内容的类型,可以固定设置为:text
。
- 设置回调示例(
控制 LED 为例
)
mcp_server_tool_t led = {
.name = "Light",
.description = "控制是否打开灯光",
.setRequestHandler = setLEDRequestHandler, //设置回调
.checkRequestHandler = NULL,//暂不设置回调
.inputSchema = {
// 设置属性,让小智AI读取当前LED状态
.properties[0].name = "enabled",
.properties[0].description = "当前灯光状态",
.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
// 设置方法,让小智AI控制LED
.methods[0].name = "SetEnabled",
.methods[0].description = "设置是否打开灯光",
// 添加设置参数
.methods[0].parameters[0].name = "enabled",
.methods[0].parameters[0].description = "true 表示打开灯光,false 表示关闭灯光",
.methods[0].parameters[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
},
};
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static returnValues_t setLEDRequestHandler(void *value)
{
returnValues_t ret = {0, "", NULL};
if (value == NULL)
{
ret.error_code = -32602;
return ret;
}
mcp_param_t params = (mcp_param_t)value; // 获取参数
// 获取被设置参数值
mcp_param_t enabled = mcp_server_get_param(params, "enabled");
led_state = enabled->valueint;
// 设置 LED 状态
bl_gpio_output_set(GPIO_LED_PIN, led_state);
// 返回结果
sprintf(ret.value, "%s", led_state ? "true" : "false");
return ret;
}
🔹步骤4:实现查询回调函数(执行查询命令
)
当 AI 平台发送查询指令(比如
“灯光是否打开”
)时,MCP 服务会调用这个函数。需要把查询到的设备状态写入returnValues_t
结构体返回给 AI。
checkRequestHandler
结构体定义
typedef struct
{
char *name; // 工具名称
char *description; // 工具描述
returnValues_t (*setRequestHandler)(void *); // 工具回调函数
void (*checkRequestHandler)(void *); // 工具回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
- 示例(查询 LED 状态):
mcp_server_tool_t led = {
.name = "Light",
.description = "控制是否打开灯光",
.setRequestHandler = setLEDRequestHandler, //设置回调
.checkRequestHandler = checkLEDRequestHandler,//暂不设置回调
.inputSchema = {
// 设置属性,让小智AI读取当前LED状态
.properties[0].name = "enabled",
.properties[0].description = "当前灯光状态",
.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
// 设置方法,让小智AI控制LED
.methods[0].name = "SetEnabled",
.methods[0].description = "设置是否打开灯光",
// 添加设置参数
.methods[0].parameters[0].name = "enabled",
.methods[0].parameters[0].description = "true 表示打开灯光,false 表示关闭灯光",
.methods[0].parameters[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
},
};
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static void checkLEDRequestHandler(void *value)
{
if (value == NULL)
{
return;
}
//读取 led_state 状态
returnValues_t *returnValues = (returnValues_t *)value;
//把状态值转化成字符转复制给 returnValues->value
sprintf(returnValues->value, "%s", led_state ? "true" : "false");
}
▫️完整示例 (LED+音量控制)
点击展开查看
/**
* @file user_mcp_tools.c
* @author Seahi-Mo (seahi-mo@foxmail.com)
* @brief
* @version 0.1
* @date 2025-08-25
*
* @copyright Ai-Thinker co.,ltd (c) 2025
*
*/
#if 1
#include <FreeRTOS.h>
#include <string.h>
#include <task.h>
#include "timers.h"
#include "user_mcp_tools.h"
#include <bl_gpio.h>
#include "vb6824.h"
#include "ws_demo.h"
#include "easyflash_common.h"
#include "user_mcp_tools.h"
#define GPIO_LED_PIN 17
static bool led_state = false;
/**
* @brief 初始化 MCP 设备
*
*/
void user_mcp_tools_hw_init(void)
{
// 初始化 LED GPIO
bl_gpio_enable_output(GPIO_LED_PIN, 0, 0);
if (mcp_tool_arry[0].name == NULL)
{
memset(mcp_tool_arry, 0, sizeof(mcp_server_tool_t) * MCP_SERVER_TOOL_NUMBLE_LEN);
}
}
/**
* @brief Set the Volume Request Handler object
*
* @param value
*/
static returnValues_t setVolumeRequestHandler(void *value)
{
returnValues_t ret = {0, "", NULL};
if (value == NULL)
{
ret.error_code = -32602;
return ret;
}
mcp_param_t params = (mcp_param_t)value; // 获取参数
mcp_param_t volume = mcp_server_get_param(params, "Set_volume");
uint8_t vb_volume_value = volume->valueint * VB6824_MAX_VOLUME / 100;
if (vb_volume_value < VB6824_MIN_VOLUME)
{
vb_volume_value = VB6824_MIN_VOLUME;
}
printf("[%s()-%d]cloud volume:%d set volume:%d\r\n", __func__, __LINE__, volume->valueint, vb_volume_value);
sprintf(ret.value, "%d", volume->valueint);
vb6824_set_volume(vb_volume_value);
// 保存音量值
ef_set_u8(volume_key, vb_volume_value);
// 保存AI设置的真实音量值,用于查询
ef_set_u8(ai_volume_key, volume->valueint);
// 返回结果
return ret;
}
/**
* @brief Set the Volume Request Handler object
*
* @param value
*/
static void checkVolumeRequestHandler(void *value)
{
if (value == NULL)
{
return;
}
returnValues_t *returnValues = (returnValues_t *)value;
uint8_t vb_volume_value = 0;
ef_get_u8(ai_volume_key, &vb_volume_value);
if (vb_volume_value == 0)
{
ef_get_u8(volume_key, &vb_volume_value);
vb_volume_value = vb_volume_value * 100 / VB6824_MAX_VOLUME;
}
sprintf(returnValues->value, "%d", vb_volume_value);
printf("[%s()-%d]get volume:%s\r\n", __func__, __LINE__, returnValues->value);
}
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static returnValues_t setLEDRequestHandler(void *value)
{
returnValues_t ret = {0, "", NULL};
if (value == NULL)
{
ret.error_code = -32602;
return ret;
}
mcp_param_t params = (mcp_param_t)value; // 获取参数
mcp_param_t enabled = mcp_server_get_param(params, "enabled");
led_state = enabled->valueint;
// 设置 LED 状态
bl_gpio_output_set(GPIO_LED_PIN, led_state);
// 返回结果
sprintf(ret.value, "%s", led_state ? "true" : "false");
return ret;
}
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static void checkLEDRequestHandler(void *value)
{
if (value == NULL)
{
return;
}
returnValues_t *returnValues = (returnValues_t *)value;
sprintf(returnValues->value, "%s", led_state ? "true" : "false");
}
/**
* @brief MCP 服务器示例
*
*/
int user_mcp_creat_tools_examples(cJSON *toolsList)
{
if (toolsList == NULL)
{
return -1;
}
cJSON *json_toolsList = toolsList;
// 添加一个扬声器工具
mcp_server_tool_t speaker = {
.name = "Speaker",
.description = "扬声器",
.inputSchema = {
// 设置属性,让小智AI读取当前音量
.properties = {
{"volume", "用于查询当前音量值,不能设置音量", MCP_SERVER_TOOL_TYPE_NUMBER},
},
// 设置方法,让小智AI控制音量
.methods = {
{"SetVolume", "设置音量", {{"Set_volume", "音量值,0到100之间的整数", MCP_SERVER_TOOL_TYPE_NUMBER}}},
},
},
.setRequestHandler = setVolumeRequestHandler,
.checkRequestHandler = checkVolumeRequestHandler,
};
// 添加一个点灯工具
mcp_server_tool_t led = {
.name = "Light",
.description = "控制是否打开灯光",
.setRequestHandler = setLEDRequestHandler,
.checkRequestHandler = checkLEDRequestHandler,
.inputSchema = {
// 设置属性,让小智AI读取当前LED状态
.properties[0].name = "enabled",
.properties[0].description = "当前灯光状态",
.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
// 设置方法,让小智AI控制LED
.methods[0].name = "SetEnabled",
.methods[0].description = "设置是否打开灯光",
// 添加设置参数
.methods[0].parameters[0].name = "enabled",
.methods[0].parameters[0].description = "true 表示打开灯光,false 表示关闭灯光",
.methods[0].parameters[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN,
},
};
int ret = mcp_server_add_tool_to_toolList(json_toolsList, &speaker);
ret = mcp_server_add_tool_to_toolList(json_toolsList, &led);
return ret;
}
#endif
/**
* @file user_mcp_tools.h
* @author Seahi-Mo (seahi-mo@foxmail.com)
* @brief
* @version 0.1
* @date 2025-08-25
*
* @copyright Ai-Thinker co.,ltd (c) 2025
*
*/
#if 1
#ifndef USER_MCP_TOOLS_H
#define USER_MCP_TOOLS_H
#include "mcp_server.h"
/**
* @brief 设备初始化
*
*/
void user_mcp_tools_hw_init(void);
int user_mcp_creat_tools_examples(cJSON *toolsList);
#endif // USER_MCP_TOOLS_H
#endif