Prerequisites
Ensure the following conditions are met
- You have completed the porting as described in Porting to MCU
- The emMCP project compiles successfully
- The AI module can receive data correctly
1. Creating the emMCP Event Callback
▫️Event List
Events are defined as an enum in emMCP.h:
| Index | Event | Description |
|---|---|---|
| 0 | emMCP_EVENT_NONE | No event |
| 1 | emMCP_EVENT_CMD_OK | Command executed successfully |
| 2 | emMCP_EVENT_CMD_ERROR | Command execution failed |
| 3 | emMCP_EVENT_AI_START | Module started |
| 4 | emMCP_EVENT_AI_NETCFG | Network configuration |
| 5 | emMCP_EVENT_AI_NETERR | Network error |
| 6 | emMCP_EVENT_AI_WIFI_CONNNECT | Wi-Fi connecting |
| 7 | emMCP_EVENT_AI_WIFI_CONNECTED | Wi-Fi connected |
| 8 | emMCP_EVENT_AI_WIFI_GOT_IP | IP address obtained |
| 9 | emMCP_EVENT_AI_WIFI_DISCONNECT | Wi-Fi disconnected |
| 10 | emMCP_EVENT_AI_WAKE | Module woken up |
| 11 | emMCP_EVENT_AI_SLEEP | Module sleeping |
| 12 | emMCP_EVENT_AI_OTAUPDATE | OTA update started |
| 13 | emMCP_EVENT_AI_OTAOK | OTA update successful |
| 14 | emMCP_EVENT_AI_OTAERR | OTA update failed |
| 15 | emMCP_EVENT_AI_MCP_CMD | MCP command received |
| 16 | emMCP_EVENT_AI_MCP_Text | Subtitle text received |
| 17 | emMCP_EVENT_AI_MCP_CHECK | MCP check command received |
v1.0.1 New Events
emMCP_EVENT_AI_WIFI_CONNECTEDandemMCP_EVENT_AI_WIFI_GOT_IPprovide finer-grained Wi-Fi statusemMCP_EVENT_AI_MCP_CHECKis used for tool health checks
▫️Event Callback Function
The emMCP event callback is defined as a weak function in emMCP.c and can be overridden:
__emMCPWeak void emMCP_EventCallback(emMCP_event_t event, mcp_server_tool_type_t type, void *param)
{
char *param_str = (char *)param;
emMCP_log_debug("emMCP_EventCallback: event:%d,type:%d,param:%s", event, type, param_str);
}Parameters:
event: Event type, refer to the event listtype: Parameter type (refers tomcp_server_tool_type_tenum, currently only string type)param: Event parameter (typically a string)
Override example:
void emMCP_EventCallback(emMCP_event_t event, mcp_server_tool_type_t type, void *param)
{
switch (event) {
case emMCP_EVENT_CMD_OK:
log_info("emMCP_EVENT_CMD_OK");
break;
case emMCP_EVENT_CMD_ERROR:
log_error("emMCP_EVENT_CMD_ERROR");
break;
case emMCP_EVENT_AI_START:
log_info("emMCP_EVENT_AI_START");
break;
case emMCP_EVENT_AI_WAKE:
log_info("emMCP_EVENT_AI_WAKE");
break;
case emMCP_EVENT_AI_MCP_CMD:
log_info("emMCP_EVENT_AI_MCP_CMD:%s", (char *)param);
break;
case emMCP_EVENT_AI_MCP_Text:
log_info("emMCP_EVENT_AI_MCP_Text:%s", (char *)param);
break;
default:
break;
}
}2. Creating emMCP Tools
▫️Tool Creation Flow
▫️Step 1: Create emMCP_tool_t Variable
The emMCP_tool_t struct is defined as:
typedef struct emMCP_tool
{
char *name; // Tool name
char *description; // Tool description
void (*setRequestHandler)(void *); // Set callback
void (*checkRequestHandler)(void *); // Check callback
inputSchema_t inputSchema; // Input parameters
struct emMCP_tool *next; // Next tool
} emMCP_tool_t;The inputSchema_t struct:
typedef struct
{
properties_t properties[MCP_SERVER_TOOL_PROPERTIES_NUM]; // Properties
methods_t methods[MCP_SERVER_TOOL_METHODS_NUM]; // Methods
} inputSchema_t;name: Tool name, unique identifierdescription: Description of what the tool doessetRequestHandler: Callback for control commands (e.g., "turn on the light")checkRequestHandler: Callback for query commands (e.g., "is the light on?")inputSchema: Input parameter description
▫️Step 2: Create Tool Callbacks
// Control callback
static void emMCP_SetLEDHandler(void *arg) { }
// Query callback
static void emMCP_GetLEDHandler(void *arg) { }
emMCP_t emMCP;
emMCP_tool_t led;
int main(void)
{
emMCP_Init(&emMCP);
led.name = "LED";
led.description = "Controls the LED on/off";
led.inputSchema.properties[0].name = "enable";
led.inputSchema.properties[0].description = "true=on, false=off, null=query";
led.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;
led.setRequestHandler = emMCP_SetLEDHandler;
led.checkRequestHandler = emMCP_GetLEDHandler;
// ...
}▫️Step 3: Add Tool to emMCP
emMCP_AddToolToToolList(&led);▫️Step 4: Register Tools with XiaoAn AI
emMCP_RegistrationTools();Full example:
int main(void)
{
emMCP_Init(&emMCP);
led.name = "LED";
led.description = "Controls the LED on/off";
led.inputSchema.properties[0].name = "enable";
led.inputSchema.properties[0].description = "true=on, false=off, null=query";
led.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;
led.setRequestHandler = emMCP_SetLEDHandler;
led.checkRequestHandler = emMCP_GetLEDHandler;
emMCP_AddToolToToolList(&led);
emMCP_RegistrationTools();
while (1) {
emMCP_TickHandle(10);
}
}3. Handling MCP Commands
The tool callbacks are invoked when an MCP command arrives. Use cJSON to parse parameters:
static void emMCP_SetLEDHandler(void *arg)
{
cJSON *param = (cJSON *)arg;
cJSON *enable = cJSON_GetObjectItemCaseSensitive(param, "enable");
if (enable != NULL) {
if (enable->valueint == 1) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
} else {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
}
emMCP_ResponseValue(emMCP_CTRL_OK);
} else {
emMCP_ResponseValue(emMCP_CTRL_ERROR);
}
}Note
v1.0.1 uses cJSON_GetObjectItemCaseSensitive() (case-sensitive JSON key lookup). Ensure your JSON keys match exactly.
4. Multi-Parameter Control
For tools with multiple parameters (e.g., RGB LED), define multiple properties:
rgb.inputSchema.properties[0].name = "enable";
rgb.inputSchema.properties[0].description = "RGB LED on/off";
rgb.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;
rgb.inputSchema.properties[1].name = "red";
rgb.inputSchema.properties[1].description = "Red value, 0-255";
rgb.inputSchema.properties[1].type = MCP_SERVER_TOOL_TYPE_NUMBER;
rgb.inputSchema.properties[2].name = "green";
rgb.inputSchema.properties[2].description = "Green value, 0-255";
rgb.inputSchema.properties[2].type = MCP_SERVER_TOOL_TYPE_NUMBER;
rgb.inputSchema.properties[3].name = "blue";
rgb.inputSchema.properties[3].description = "Blue value, 0-255";
rgb.inputSchema.properties[3].type = MCP_SERVER_TOOL_TYPE_NUMBER;5. Memory Configuration (v1.0.1)
Define these macros to control memory usage (default values shown below; override before including emMCP.h):
#define MCP_SERVER_TOOL_NUMBLE_MAX 4 // Max tools
#define MCP_SERVER_TOOL_PROPERTIES_NUM 4 // Max properties per tool
#define MCP_SERVER_TOOL_METHODS_NUM 2 // Max methods per tool
#define MCP_SERVER_TOOL_METHODS_PARAMETERS_NUM 3 // Max parameters per methodv1.0.1 Memory Optimization
Defaults changed from (4, 6, 5, 5) to (4, 4, 2, 3), saving ~40% RAM. Override if needed.
6. Helper API (v1.0.1 New)
▫️Get Parameter
emMCP_GetParam(cJSON *params, char *param_name) safely extracts a field from the callback argument:
static void emMCP_SetLEDHandler(void *arg) {
cJSON *param = (cJSON *)arg;
cJSON *enable = emMCP_GetParam(param, "enable");
if (enable != NULL && cJSON_IsTrue(enable)) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
emMCP_ResponseValue(emMCP_CTRL_OK);
}▫️UART Status
emMCP_CheckUartSendStatus()— Check UART send completionemMCP_UpdateUartRecv(bool isRecv)— Update RX status
7. Extra Features
v1.0.1 Change
The following extra features (wake-up, volume, baud rate) are now controlled by the EMCP_ENABLE_EXTRA_CMDS macro, disabled by default to save memory. Enable by defining before including emMCP.h:
#define EMCP_ENABLE_EXTRA_CMDS▫️Wake-up Control
emMCP_SetAiWakeUp(20); // Wake up for 20 seconds▫️Volume Control
emMCP_SetAiVolume(50); // Set volume to 50▫️Query Volume (v1.0.1 New)
uint8_t vol = emMCP_CheckAiVolume();▫️Baud Rate
emMCP_SetBaudrate(115200); // Set baud rate (takes effect immediately)TIP
After changing the baud rate via emMCP, you must update the MCU's UART baud rate accordingly.

