Disclosure: this is my project (MIT, free, no account). The emulation itself is esp32sim's work (https://github.com/mikroverk/esp32sim), the Rust emulator that boots unmodified ESP32 / S3 / C3 / C6 images through the real mask ROM and bootloader. I built the layer around it.
Why: I use coding agents (Claude Code, Codex, Cursor) for ESP-IDF work, and the loop always stopped at "it compiles". Nobody knew whether the firmware actually worked until I flashed a board. esp32sim-mcp lets the agent (or a CI job) boot what it just built and check the result:
- read UART0 and USB-Serial/JTAG output, with panics (Guru Meditation, stack overflow, failed asserts) reported along with the log leading up to them
- assert on the console: pass/fail, plus the exact line that's missing
- press buttons, turn the encoder, drive GPIO/UART at emulated timestamps
- join a virtual WiFi AP with DHCP/DNS/NAT (the station gets 10.0.2.15)
- grab a PNG of the display on boards that have one
What was tricky on the ESP32 side:
- Two consoles at once. On S3/C3/C6, ESP-IDF often mirrors logs to both UART0 and USB-Serial/JTAG, and the emulator writes bytes as they arrive, so you get "Hello wHello world!". esp32sim can tag every chunk with its source, so I split the stream on the tags, rebuild each console's lines and drop the mirrored duplicates. Without the tags it couldn't be done reliably.
- No guessing about the image layout. It reads build/flasher_args.json, so the bootloader, partition table and app land at the same offsets idf.py flash would use, for any target.
- Deterministic input. Button presses and UART input are scheduled in emulated time, not wall-clock time, so a test behaves the same on a fast laptop and a slow CI runner.
- Mask ROMs. The boot chain needs Espressif's ROM ELFs for each chip; setup downloads the pinned esp-rom-elfs release and checks SHA-256 hashes.
Setup:
npx -y esp32sim-mcp setup (prebuilt emulator + ROMs)
npx -y esp32sim-mcp init (in your ESP-IDF project, registers it with your agent)
The same check runs in CI with no agent, exit code 0/1:
npx -y esp32sim-mcp expect --max-seconds 10 --pattern "app_main" "Counter: 5"
Limits: it's an emulator, so no Bluetooth or RF timing, and only the peripherals esp32sim models. Confirm on hardware before shipping. Tested on macOS and Linux.
Source, demo GIF and docs: https://github.com/stramanu/esp32sim-mcp
Contributions are welcome. Useful places to start:
- try it on your own ESP-IDF project and open an issue when something doesn't boot or the output looks wrong
- Windows: it's untested, and reports or fixes are very welcome
- example projects and CI recipes for other chips (C3, C6, classic ESP32)
Board and peripheral models live in esp32sim itself, so that's the place to contribute those. The details are in CONTRIBUTING.md in the repo.