Skip to main content
Glama
yaniv-golan

stackchan-mcp-mod

by yaniv-golan

stackchan-mcp-mod

check License

An MCP server MOD for Stack-chan that exposes the robot's capabilities as MCP tools, so an MCP client (Claude Code, Codex, …) can drive the robot over the network: take a photo, listen to the room, move the head, light the LEDs, speak, and react to being touched.

It is a replacement for the stock "MCP Server" sample MOD, which offers only set_emotion and say_message and — because a MOD's onContextCreated replaces the host default — switches off the built-in behaviors such as head petting. This MOD runs the default behaviors first and then adds its own tools.

  • Target: M5StackChan CoreS3 running stack-chan firmware v1.1.0 (host 9.0.0+stackchan.1)

  • Transport: Streamable HTTP on port 8080, POST /mcp with Authorization: Bearer <mcp.token>, GET /health

  • No host firmware rebuild: the MOD is a JavaScript archive written to the xs flash partition

This turns your robot into a networked camera and microphone. Read SECURITY.md before installing it, especially if the robot lives in a room where people talk.

For coding agents

Start with AGENTS.md (also reachable as CLAUDE.md), then docs/architecture.md. llms.txt is a short index of the same material. The two constraints that catch people out: an uncaught exception reboots the device, and an oversized response takes the HTTP server down (~28 KB bodies are known good, ~56 KB is fatal). scripts/check.sh runs the linter without needing hardware — but only the robot can tell you whether a change actually works.

Related MCP server: stackchan-relay

Status

Working and used daily on one robot; every tool listed below has been exercised on real hardware. Only the M5StackChan CoreS3 target is tested — other Stack-chan targets may work but nobody has tried. Expect the rough edges documented under Device limits; most of them are firmware behaviour this MOD works around rather than bugs in the MOD.

Tools

Tool

What it does

set_emotion

Sets the facial expression (NEUTRAL, ANGRY, SAD, HAPPY, SLEEPY, DOUBTFUL, COLD, HOT)

say_message

Speaks text with the configured TTS engine; returns after playback ends

set_head_pose

Moves the head to an absolute yaw/pitch in degrees, optionally holding the pose

look_at / look_away

Starts or stops gaze tracking toward a 3D point in metres

get_head_pose

Reads the last known head pose (stale unless a motion or gaze is active)

set_torque

Engages or releases the servos

set_leds / blink_leds / rainbow_leds / leds_off

Drives the 12-LED head ring

set_face_color / set_mouth_open / set_eye_open

Face theme colour and mouth/eyelid position

get_recent_events / wait_for_event

Reads buffered input events, or waits for the next one

get_input_capabilities

Reports which input devices exist and which are being recorded

take_photo

Captures a photo as a PNG, grayscale or 256-colour

listen

Records and reports loudness (overall, peak, per-200 ms); does not transcribe

record_and_play

Records, then plays it back through the speaker

get_recorded_audio

Returns a short downsampled mono WAV as an MCP resource

play_tone

Plays a tone (Hz, ms, volume)

sing

Sings koe notation; needs the stackchan-voice TTS engine

show_message / hide_message

Shows a short message in a speech balloon on the robot's screen

get_robot_info

Reports MOD version, available hardware, the capture policy and the device's limits

restart_robot

Soft reboot — leaves the screen dead until a manual power-cycle (see below)

get_power_registers

Read-only AXP2101 rail and status registers, for diagnosing a dead display

get_mic_gain / set_mic_gain

Read and set the ES7210 preamp gain. Registered only where the ADC is reachable — not on CoreS3, where the firmware holds it, so these normally do not appear

It also replaces the face with one that draws a real smile for HAPPY (the stock mouth ignores emotion) and reports touches on the face area as touch events with screen coordinates.

Device limits worth knowing

Measured on an M5StackChan CoreS3 running v1.1.0:

  • A large response body does not just fail — it takes the HTTP server down with it. Bodies up to ~28 KB are served repeatably and ~56 KB reliably kills it; the threshold between those is unmeasured. Photos are therefore budgeted against 28 KB on the wire (base64 included), colour uses a 3-3-2 palette rather than truecolour, base64 is written straight into the response buffer, and the server restarts its listener if the accept loop dies.

  • A software restart leaves the display dead until the robot is physically power-cycled (hold power until off, then press again). A hardware reset — the bottom reset button, or esptool — is safe. The host's own MOD manager calls System.restart(), so installing a MOD through it has the same effect.

  • Screen touch reaches a MOD only through the face: the firmware does not hand it over, and opening the touch controller directly is refused (duplicate address) because the UI already holds it. The custom face installed by this MOD reports touches on the face area instead.

  • No A/B/C buttons exist on this hardware; only power. The CoreS3 target's virtual buttons are compiled out.

  • say_message returns only after playback finishes, which can take tens of seconds for a long sentence.

Build

Requires Moddable SDK 9.0.0 exactly — the device rejects a MOD whose XS version does not match the host — and a stack-chan checkout at tag v1.1.0 for the platform config. No ESP-IDF needed: mcrun compiles the JavaScript, it does not build the firmware.

git clone --depth 1 --branch 9.0.0 https://github.com/Moddable-OpenSource/moddable ~/moddable
# add the prebuilt tools for your host from the 9.0.0 release into $MODDABLE/build/bin/mac/release
git clone --depth 1 --branch v1.1.0 https://github.com/stack-chan/stack-chan ~/stack-chan

MODDABLE=~/moddable STACKCHAN=~/stack-chan scripts/build.sh

Install

The MOD archive goes to the xs partition at 0xfa0000 (256 KB). Installing replaces whatever MOD is there.

STACKCHAN_PORT=/dev/cu.usbmodem2101 scripts/install.sh

Set mcp.token on the robot first (Settings mode over BLE, or the preferences web tool); POST /mcp rejects every request while it is unset. To roll back, write the stock mcp.xsa from the v1.1.0 MOD gallery to the same offset.

Configure the token

The robot rejects every POST /mcp until the mcp.token preference is set. Generate a long random token and set it in the robot's Settings mode over BLE, or with the preferences web tool:

openssl rand -hex 32

Keep it out of your shell history and out of this repository; scripts/mcp.sh reads it from STACKCHAN_TOKEN, or from the macOS Keychain (stackchan-mcp-token).

Try it

STACKCHAN_HOST=192.168.1.20 STACKCHAN_TOKEN=... scripts/mcp.sh '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Register it with Claude Code:

claude mcp add --scope user --transport http stackchan http://<robot-ip>:8080/mcp --header "Authorization: Bearer <token>"

Troubleshooting

The screen is blank but the robot answers MCP calls. Display initialization after a warm reset is unreliable on this firmware, and a software restart never restores it. Try an EN reset pulse (scripts/install.sh sends two after flashing); if the screen stays dark, power-cycle by hand — hold the power button until the robot powers off, then press it again. Everything except the display keeps working meanwhile.

esptool cannot connect (No serial data received). The app can wedge the USB peripheral. Start the flash and press the bottom reset button while esptool retries.

A tool call hangs and the robot stops answering. Something produced a response larger than the device can send, which takes the HTTP server down. The server restarts its listener automatically; if it does not come back, reset the robot. Report it, since a tool should refuse an oversized result rather than attempt it.

Recordings sound almost silent. They are: the capture path is about 30 dB quiet on this hardware, for reasons not yet established (the ES7210's analog preamp has only 4.5 dB left to give, so it is not the cause). listen reports levels corrected for the measured offset, and the recording tools apply software gain by default.

Development

scripts/check.sh   # lint and format (Biome) plus shell syntax; no robot needed

Please read CONTRIBUTING.md — the short version is that a change should be tried on a real robot, nothing may throw out of a handler, and responses must stay small.

Operating it from an assistant

The MCP tools tell an assistant what it can do; they do not teach it how to behave with a physical robot. This repository also ships a Claude skill for that — which tool to reach for, the hazards, recovery when the robot stops responding, and how to report physical outcomes honestly rather than assuming a successful call meant something visibly happened.

It is packaged as a Claude Code plugin, served from this repository as its own marketplace:

/plugin marketplace add yaniv-golan/stackchan-mcp-mod
/plugin install stackchan-robot@stackchan-robot-marketplace

Or, from a local clone:

/plugin marketplace add ./stackchan-mcp-mod
/plugin install stackchan-robot@stackchan-robot-marketplace

To use it without the plugin machinery, copy the skill folder straight in:

cp -r stackchan-robot/skills/stackchan-robot ~/.claude/skills/

Working notes

docs/device-notes.md is the bring-up log for this MOD: the build toolchain, the measured device limits, the firmware bugs found along the way (an oversized response kills the HTTP server; a software restart kills the display), the unexplained microphone deficit, and the approaches that did not work.

License

Apache-2.0 — see LICENSE and NOTICE. mod/mcp-server-rich.js is derived from the stack-chan firmware (Apache-2.0); the file header lists what changed. Changes are recorded in CHANGELOG.md.

Related MCP Connectors

Related MCP Servers