MCP Protocol IoT Control Usage Guide
Introduction
MCP (Model Context Protocol) is a next-generation protocol recommended for IoT control. It discovers and calls “tools” between backend and devices through standard JSON-RPC 2.0 format, enabling flexible device control.
Typical Usage Flow
- Device establishes connection with backend via underlying protocol (like WebSocket/MQTT) after startup.
- Backend initializes session through MCP protocol’s
initialize
method. - Backend gets all tools (functions) supported by device and parameter descriptions via
tools/list
. - Backend calls specific tools via
tools/call
to achieve device control.
For detailed protocol format and interaction, see MCP Protocol Documentation.
Device-side Tool Registration Method
Devices register callable “tools” for backend via McpServer::AddTool
method. Common function signature:
void AddTool(
const std::string& name, // Tool name, suggest unique and hierarchical, like self.dog.forward
const std::string& description, // Tool description, brief functionality explanation for LLM understanding
const PropertyList& properties, // Input parameter list (can be empty), supports: bool, int, string
std::function<ReturnValue(const PropertyList&)> callback // Callback implementation when tool is called
);
Parameter Description
- name: Tool unique identifier, suggest using “module.function” naming style.
- description: Natural language description for AI/user understanding.
- properties: Parameter list, supports bool, int, string types, can specify range and default values.
- callback: Actual execution logic when receiving call request, return value can be bool/int/string.
Typical Registration Examples (Using ESP-Hi)
void InitializeTools() {
auto& mcp_server = McpServer::GetInstance();
// Example 1: No parameters, control robot to move forward
mcp_server.AddTool("self.dog.forward", "Robot moves forward", PropertyList(),
[this](const PropertyList&) -> ReturnValue {
servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
return true;
});
// Example 2: With parameters, set light RGB color
mcp_server.AddTool("self.light.set_rgb", "Set RGB color", PropertyList({
Property("r", kPropertyTypeInteger, 0, 255),
Property("g", kPropertyTypeInteger, 0, 255),
Property("b", kPropertyTypeInteger, 0, 255)
}), [this](const PropertyList& properties) -> ReturnValue {
int r = properties["r"].value<int>();
int g = properties["g"].value<int>();
int b = properties["b"].value<int>();
led_on_ = true;
SetLedColor(r, g, b);
return true;
});
}
Common Tool Call JSON-RPC Examples
1. Get Tool List
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": { "cursor": "" },
"id": 1
}
2. Control Chassis Forward
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "self.chassis.go_forward",
"arguments": {}
},
"id": 2
}
3. Switch Light Mode
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "self.chassis.switch_light_mode",
"arguments": { "light_mode": 3 }
},
"id": 3
}
4. Camera Flip
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "self.camera.set_camera_flipped",
"arguments": {}
},
"id": 4
}
Tool Registration Best Practices
1. Naming Conventions
- Use hierarchical naming:
self.module.function
- Keep names concise and descriptive
- Avoid special characters and spaces
// Good naming examples
"self.light.set_brightness"
"self.motor.rotate_left"
"self.sensor.get_temperature"
"self.display.show_text"
// Discouraged naming
"light-control" // Lacks hierarchy
"setBrightness" // Lacks module info
"控制灯光" // Non-English naming
"self.light.set brightness" // Contains spaces
2. Parameter Design
- Use clear parameter names
- Set reasonable parameter ranges and default values
- Provide detailed parameter descriptions
// Parameter design example
PropertyList({
Property("volume", kPropertyTypeInteger, 0, 100, "Volume level, range 0-100"),
Property("enabled", kPropertyTypeBool, true, "Whether to enable function"),
Property("message", kPropertyTypeString, "", "Text content to display")
})
3. Error Handling
Include appropriate error handling in tool callbacks:
mcp_server.AddTool("self.servo.set_angle", "Set servo angle", PropertyList({
Property("angle", kPropertyTypeInteger, 0, 180)
}), [this](const PropertyList& properties) -> ReturnValue {
try {
int angle = properties["angle"].value<int>();
if (angle < 0 || angle > 180) {
return "Angle out of range: 0-180";
}
SetServoAngle(angle);
return true;
} catch (const std::exception& e) {
return std::string("Failed to set angle: ") + e.what();
}
});
Debugging Tips
1. Logging
Add logging in tool implementations:
mcp_server.AddTool("self.debug.test", "Test tool", PropertyList(),
[this](const PropertyList& properties) -> ReturnValue {
ESP_LOGI("MCP", "Test tool called");
// Execute test logic
return "Test completed";
});
2. Parameter Validation
Add detailed parameter validation:
int volume = properties["volume"].value<int>();
if (volume < 0 || volume > 100) {
ESP_LOGE("MCP", "Invalid volume parameter: %d", volume);
return "Volume must be between 0-100";
}
Notes
- Tool names, parameters and return values should be based on device-side
AddTool
registration. - All new projects are recommended to uniformly adopt MCP protocol for IoT control.
- For detailed protocol and advanced usage, please refer to MCP Protocol Documentation.
Related Documentation
- MCP Protocol Documentation - Detailed protocol interaction flow
- WebSocket Communication Protocol - WebSocket underlying communication protocol
- MQTT + UDP Hybrid Protocol - MQTT + UDP hybrid communication protocol
Frequently Asked Questions
Q: How to handle tool call timeouts?
A: Avoid long blocking operations in tool callbacks. For time-consuming operations, handle asynchronously and return status immediately.
Q: Can tools be dynamically added or removed?
A: Current implementation typically registers tools at device startup. Dynamic management requires redesigning the tool registration mechanism.
Q: How to handle tool call permissions?
A: Permission check logic can be added in tool callbacks, or permission control can be implemented at the backend API layer.