1. MCP 服务简介
MCP 是专门为 AI 平台设计的,用于处理 AI 平台发送的指令,并返回处理结果的服务。MCP 服务需要与 AI 平台进行通信,这个过程已经在 SDK 中实现,开发者只需要如何添加MCP功能,实现MCP控制与查询即可。
▫️MCP 参考资料
2. MCP 工具开发
工具 是模型上下文协议(MCP)中的基础功能单元(原语),用于让服务器向客户端暴露可执行功能。通过工具,LLM 可以与外部系统交互(如控制设备、获取状态)。工具由 AI 模型自动调用(可配置人工审批)。
简单来说:AI 模型想要控制一些设备或者获取状态,就是通过调用 工具
来实现。
▫️MCP 工具开发流程
- 创建
工具
,并设置工具的名称、描述、参数、回调函数等。
- 创建
- 把
工具
注册到MCP
服务中。
- 把
- 编写
工具
的回调函数,实现工具
的功能。
- 编写
▫️MCP 工具开发示例
🔹步骤1:创建工具
创建 工具
需要通过 mcp_server_tool_t
结构体来实现,结构体定义如下:
c
/**
* @brief 工具结构体
* 用于创建 MCP 工具和描述 MCP 工具的输入参数
*/
typedef struct {
char *name; // 工具名称
char *description; // 工具描述
void (*setRequestHandler)(void *); // 工具设置回调函数
void (*checkRequestHandler)(void *); // 工具查询回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
c
/**
* @brief 输入参数结构体
* 用于描述工具的输入参数,告诉 AI 平台,该工具提供了哪些参数,参数的用途是什么。
*/
typedef struct
{
properties_t properties[MCP_SERVER_TOOL_PROPERTIES_NUM]; // 属性
methods_t methods[MCP_SERVER_TOOL_METHODS_NUM]; // 方法
} inputSchema_t;
c
/**
* @brief 属性结构体
* 用于描述工具的属性,告诉 AI 平台,该工具提供了哪些可以读取的属性,比如:查询当前音量
*/
typedef struct
{
char *name; // 属性名称
char *description; // 属性描述
mcp_server_tool_type_t type; // 属性类型
} properties_t;
c
/**
* @brief 方法结构体
* 用于描述工具的方法,告诉 AI 平台,该工具提供了哪些可以调用的方法,比如:设置音量
*/
typedef struct
{
char *name; // 方法名称
char *description; // 参数描述
parameters_t parameters[MCP_SERVER_TOOL_METHODS_PARAMETERS_NUM]; // 方法参数
} methods_t;
c
/**
* @brief 参数结构体
* 用于 方法的输入参数,告诉 AI 平台, 需要下发什么数据,才能实现控制,比如:设置音量时需要下发的关键字和值的范围
*/
typedef struct
{
char *name; // 参数名称
char *description; // 参数描述
mcp_server_tool_type_t type; // 参数类型
} parameters_t;
结构体说明:
name
:工具名称,用于标识工具,工具的唯一标识符。description
:工具描述,用于描述工具的功能。setRequestHandler
:工具设置回调函数,用于处理工具的设置请求。checkRequestHandler
:工具查询回调函数,用于处理工具的查询请求。inputSchema
:输入参数,用于描述工具的输入参数,MCP 查询工具时,会通过该参数获取工具的输入参数。如:当前音量
properties
:属性,用于描述工具的属性。name
:属性名称,用于标识属性。description
:属性描述,用于描述属性的用途。type
:属性类型,用于描述属性的值类型。
methods
:方法,用于描述工具的方法。需要实现MCP控制时,需要实现该参数。如:设置音量
name
:方法名称,用于标识方法。description
:方法描述,用于描述方法的用途。parameters
:方法参数,用于描述方法的输入参数。name
:参数名称,用于标识参数。description
:参数描述,用于描述参数的用途。描述参数的具体功能,比如:音量的设置范围,亮度的设置范围等
type
:参数类型,用于描述参数的值类型。
- 创建示例(LED 工具):
在 aipi-palchatv1/mcp_sercer/user_mcp_tools.c
的 user_cmp_creat_tools_examples
函数中定义工具。
c
int user_cmp_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 平台识别。
c
/**
* @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 工具创建):
c
mcp_server_add_tool_to_toolList(json_toolsList, &led);
🔹步骤3:实现设置回调函数
- (1)设置回调函数(处理控制请求)
当 AI 平台发送控制指令(如 “打开灯光”
)时,MCP 服务
会调用此函数。参数 value
为 AI 平台传入的控制值(类型与工具定义的 type
一致)。
setRequestHandler
函数原型
c
typedef struct
{
char *name; // 工具名称
char *description; // 工具描述
void (*setRequestHandler)(void *); // 工具回调函数
void (*checkRequestHandler)(void *); // 工具回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
参数说明
void *
(void * value):会自动输出为 AI 设置结果,比如:设置LED时,value为 int 型,当设置类型为string
或text
时,value为char *
型。
因此,你只需要在回调函数中使用 value
参数,作为控制的识别即可。
- 设置回调示例,
控制LED为例
c
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,
},
};
c
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static void setLEDRequestHandler(void *value)
{
if (value == NULL)
{
return;
}
led_state = *(int *)value; //获取控制结果
// 设置 LED 状态
bl_gpio_output_set(GPIO_LED_PIN, led_state); //控制LED
}
- (2)查询回调函数(
处理状态查询
)
当 AI 平台查询状态(如 “灯光是否打开”
)时,MCP 服务会调用此函数。需将查询结果写入 returnValues_t
结构体返回。
checkRequestHandler
结构体定义
c
typedef struct
{
char *name; // 工具名称
char *description; // 工具描述
void (*setRequestHandler)(void *); // 工具回调函数
void (*checkRequestHandler)(void *); // 工具回调函数
inputSchema_t inputSchema; // 输入参数
} mcp_server_tool_t;
参数说明
void *
(void * value):为需要返回的查询结果,但是它的类型为固定的returnValues_t
结构体,结构体原型如下:
c
/**
* @brief MCP 服务器工具结构体
*
*/
typedef struct
{
int error_code; // 错误码
char value[8]; // 内容
char *type; // 类型
} returnValues_t;
returnValues_t 参数说明
int error_code
:错误码,0 表示成功,-32602 表示失败。char value[8]
:返回内容,char *type
:返回类型,固定为text
型
- 示例(查询 LED 状态):
c
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,
},
};
c
/**
* @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+音量控制)
点击展开查看
c
/**
* @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_cmp_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 void setVolumeRequestHandler(void *value)
{
int volume = *(int *)value;
uint8_t vb_volume_value = volume * 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, vb_volume_value);
vb6824_set_volume(vb_volume_value);
ef_set_u8(volume_key, vb_volume_value);
}
/**
* @brief Set the Volume Request Handler object
*
* @param value
*/
static void checkVolumeRequestHandler(void *value)
{
int volume;
returnValues_t *returnValues = (returnValues_t *)value;
uint8_t vb_volume_value = 0;
ef_get_u8(volume_key, &vb_volume_value);
volume = vb_volume_value * 100 / VB6824_MAX_VOLUME;
sprintf(returnValues->value, "%d", volume);
printf("[%s()-%d]get volume:%s\r\n", __func__, __LINE__, returnValues->value);
}
/**
* @brief Set the LED Request Handler object
*
* @param value
*/
static void setLEDRequestHandler(void *value)
{
if (value == NULL)
{
return;
}
led_state = *(int *)value;
// 设置 LED 状态
bl_gpio_output_set(GPIO_LED_PIN, led_state);
}
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_cmp_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", "设置音量", {{"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
c
/**
* @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_cmp_tools_hw_init(void);
int user_cmp_creat_tools_examples(cJSON *toolsList);
#endif // USER_MCP_TOOLS_H
#endif