English | 日本語
stackchan-mcp
An MCP (Model Context Protocol) bridge for the M5Stack official StackChan (2025 Kickstarter shipping kit), letting any LLM client drive the device.
Born out of the stack-chan project community (originated by Shinya Ishikawa in 2021). This repository targets the M5Stack official StackChan kit that grew out of that lineage.
┌─────────────┐ stdio MCP ┌──────────────┐ WebSocket MCP ┌──────────────┐
│ MCP client │ ─────────────────▶ │ gateway │ ──────────────────▶ │ ESP32 (CoreS3│
│ (e.g.Claude)│ ◀───────────────── │ (Python) │ ◀────────────────── │ +StackChan) │
└─────────────┘ │ │ └──────────────┘
│ /capture │ ◀── HTTP POST (JPEG) ──┘
└──────────────┘
From any MCP client (Claude Code / Claude Desktop / others) you can call StackChan operations such as head movement, camera capture, touch sensor reads, and avatar expression switches.
Repository layout
This repository is a monorepo.
| Directory | Contents |
|---|
firmware/ | Full git subtree of 78/xiaozhi-esp32. The custom StackChan board lives at firmware/main/boards/stackchan/. |
gateway/ | Python MCP gateway. stdio MCP server (LLM side) + WebSocket MCP client (ESP32 side) + HTTP capture server. |
docs/ | architecture.md: full component diagram, tool name mapping, photo flow, auth, phase roadmap. firmware-sync.md: upstream xiaozhi-esp32 sync playbook. remote-access.md: Tailscale Funnel setup for non-LAN use. |
examples/ | Optional, unmaintained examples. cloudflare-relay/: Cloudflare Workers WebSocket relay for reaching the gateway from outside the local LAN. |
Target hardware
M5Stack official StackChan kit (Kickstarter 2025 shipping version). The firmware in this repository is meant to replace the kit's factory firmware.
| Part | Spec |
|---|
| Body | M5Stack CoreS3 (ESP32-S3, 16MB Flash, 8MB PSRAM) |
| Neck servos | SCS0009 ×2 (yaw + pitch, serial bus, TX=GPIO6, RX=GPIO7) |
| Camera | GC0308 (DVP, 320×240) |
| Touch | FT6336 / Si12T |
| Display | ILI9342 (SPI, 320×240) |
A self-built stack-chan (following the original stack-chan project design) may also work as long as the pin assignments and I2C addresses match. Reports and PRs welcome.
Tools (callable by MCP clients via the gateway)
| Tool | Description | Status |
|---|
get_status | Gateway connection state | ✅ |
get_device_info | ESP32 device state (battery / volume / WiFi / etc.) | ✅ |
take_photo(question?) | Capture a frame, save as JPEG, return the path | ✅ |
set_volume(volume) | Speaker volume (0-100) | ✅ |
set_brightness(brightness) | Screen brightness (0-100) | ✅ |
move_head(yaw, pitch, speed?) | Move the neck (servos). pitch is constrained to 5..85 — the M5Stack-recommended operating range. For the wider firmware hard clamp (0..88), use the firmware-side set_head_angles device tool instead. | ✅ |
get_touch_state | Touch sensor state (press / release / stroke / etc.) | ✅ |
set_avatar(face) | Switch avatar expression (idle / happy / thinking / sad / surprised / embarrassed), or off to hide the avatar and disable blink so the underlying WiFi config / OTA / settings screens are visible. Any other face brings the avatar back and restores blink. | ✅ |
set_blink(state) | Blink on/off | ✅ |
set_mouth(state) | Mouth open/close (one-shot, held until next call) | ✅ |
set_mouth_sequence(steps) | Queue and play a list of {shape, duration_ms} steps locally for TTS lip-sync — no per-step WebSocket RTT jitter | ✅ |
check_vm_en | Check servo power supply (VM EN HIGH) state | ✅ |
set_led(index, r, g, b) | Set one of the 12 base RGB LEDs (index 0..11, channels 0..255) | ✅ |
set_all_leds(r, g, b) | Set all 12 base RGB LEDs to the same color | ✅ |
set_leds(colors) | Batch-set the first N LEDs from a [[r,g,b], ...] array in a single I2C burst (use this for animations / multi-color patterns); trailing LEDs keep their previous color | ✅ |
clear_leds | Turn all 12 base RGB LEDs off | ✅ |