The author’s native language is Chinese. This document is translated using AI.
1. Introduction
This blog demonstrates how to remotely control an LED using an LLM Chat tool, communicating via the MCP (Model Context Protocol). The device side uses the ESP32-C6-DevKitC-1 development board. Many of the steps in this tutorial are the same as in the previous article, RTIO-based Remote LED Control. However, instead of using curl
on the user side, control is now performed via an LLM (acting as an Assistant).
The overall architecture is shown in the diagram below:
+-------+ +----------+ +--------+ +--------+
| |--->|MCP-Server|--->| RTIO |--->|ESP32-C6|
| Chat | +----------+ +--------+ +--------+
| or |
| Agent | +----------+
| |--->| LLM-APIs |
+-------+ +----------+
2. Hardware and Environment Setup
For details on setting up the hardware and system development environment on the device side, refer to RTIO-based Remote LED Control- Hardware and Environment Setup.
3. Experiment Steps
3.1. Run the Server
docker run -it --entrypoint ./rtio \
--rm -p 17017:17017 -p 17917:17917 registry.cn-guangzhou.aliyuncs.com/rtio/rtio:v0.8.0 \
-disable.deviceverify -disable.hubconfiger \
-enable.hub.tls \
-hub.tls.certfile ./certificates/demo_server.crt \
-hub.tls.keyfile ./certificates/demo_server.key \
-log.level info
If running the service on WSL, you’ll need to configure the firewall and port mapping to WSL. Refer to RTIO-based Remote LED Control - WSL Network Configuration.
3.2. Compile and Run on the Device Side
Follow the steps in RTIO-based Remote LED Control - Device Compilation to compile and run the device-side code.
3.3. Configure MCP Server
The MCP Server can be integrated using various MCP Host tools. In this example, we use fast-agent
. For detailed usage, refer to the fast-agent.ai.
Below is fastagent.config.yaml
,
- Add a
rtio_led_switch
entry undermcp.servers
; - RTIO_DEVICE_URL - The RTIO server address and deviceID.
mcp:
servers:
rtio_led_switch:
command: "npx"
args: ["-y", "@mkrainbow/rtio-led-switch-demo"]
env:
RTIO_DEVICE_URL: "http://localhost:17917/cfa09baa-4913-4ad7-a936-3e26f9671b10"
Also, ensure that rtio_led_switch
is included in agent.py
:
# Define the agent
@fast.agent(instruction="You are a helpful AI Agent", servers=["rtio_led_switch", "memory"])
async def main():
# use the --model command line switch or agent arguments to change model
async with fast.run() as agent:
await agent.interactive()
3.4. Chat with LLM
Prompts:
- Turn LED on via RTIO service.
- Turn LED off via RTIO service.
Below is a sample conversation with the LLM. You can also refer to Showcase.
Device online:
$ uv run agent.py
default > Turn LED on via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│ │
│ Turn LED on via RTIO service. │
│ │
╰─ gemma3-4b turn 1 ──────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ the assistant requested tool calls │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│ │
│ {"value":"on"} │
│ │
╰─ [rtio_led_sw…] ───────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│ │
│ meta=None content=[TextContent(type='text', text='{"code":"OK"}', │
│ annotations=None)] isError=False │
│ │
╰─────────────────────────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ The LED has been turned on. │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
default > Turn LED off via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│ │
│ Turn LED off via RTIO service. │
│ │
╰─ gemma3-4b turn 2 ──────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ the assistant requested tool calls │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│ │
│ {"value":"off"} │
│ │
╰─ [rtio_led_sw…] ───────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│ │
│ meta=None content=[TextContent(type='text', text='{"code":"OK"}', │
│ annotations=None)] isError=False │
│ │
╰─────────────────────────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ The LED has been turned off. │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
Device offline:
default > Turn LED on via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│ │
│ Turn LED on via RTIO service. │
│ │
╰─ gemma3-4b turn 7 ──────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ the assistant requested tool calls │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│ │
│ {"value":"on"} │
│ │
╰─ [rtio_led_sw…] ───────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│ │
│ meta=None content=[TextContent(type='text', │
│ text='{"code":"DEVICEID_OFFLINE"}', annotations=None)] isError=False │
│ │
╰─────────────────────────────────────────────────────────────────────────╯
╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│ │
│ I'm unable to control the LED device at this time. It appears to be │
│ offline. │
│ │
╰─ [rtio_led_switch] [memory] ───────────────────────────────────────────╯
4. Appendix: Showcase
In conclusion, the REST-like nature of RTIO (e.g., immediate return of device call results) makes it extremely easy for an LLM to remotely control devices via MCP (Model Context Protocol). It is even feasible to generate MCP-Server code directly through the LLM.