Live demo: wipe an ESP32 and write a meteo station back — MCP tools vs bare hands

One board, one job — erase the flash, install the firmware, deploy the meteo station's main.py, verify — done two ways. First with bare pyserial/esptool and no harness. Then through the ironharness MCP server: the same operations, plus the two things a bare script cannot give you — a gate that refused to destroy the board until explicitly told to, and a journal that replays every step.

The gate refusing its own destruction (raw journal, first event)

{"ts": 1789910900.024, "seq": 1, "actor": "mcp", "kind": "esp_denied",
 "op": "erase", "port": "COM6",
 "error": "IRONHARNESS_ALLOW_REAL_FLASH=1 is required for live flash/erase
           — these operations modify real hardware"}

The esp_erase call was made without the opt-in flag first. The tool refused in 0.0 s and journaled its own refusal. The bare-hands road has no such concept: the first run of the script simply erased the flash.

Road A — bare hands (esptool CLI + a ~60-line raw-REPL client on pyserial)

StepCommandTime
wipeesptool --chip esp32 --port COM6 erase-flash5.3 s
flashesptool write-flash 0x1000 firmware.bin (hash verified)21.3 s
stationmain.py over the raw REPL: 15 hex chunks, byte-check24.6 s
verifyRTS reset + 15 s of boot readPASS

First run of this road FAILED at verify — and the lesson is the point: pyserial asserts DTR/RTS on open, and on this board's reset circuit a held DTR blocks the RTS→EN pair, so the board goes silent (0 bytes in two 8-second windows). The harness kills DTR/RTS right after open; the bare script has to know that. It also has to know the Ctrl-B/Ctrl-C×2/Ctrl-A dance, the hex-chunk raw-REPL protocol and the cooked-mode limits. Evidence trail of road A: whatever the script happened to write into its own log files.

Road B — MCP tools (gated, journaled)

StepToolTime
open / reset / read boot signatureserial_open, serial_reset, serial_read_until0.1–2.1 s
release the port for esptoolserial_close0.0 s
wipeesp_erase4.8 s
flashesp_flash @0x1000 (hash verified inside)19.7 s
main.py into the sandboxfile_write0.0 s
main.py onto the boardserial_put 3787 B (raw REPL)47.3 s
reset + boot signatureserial_reset + serial_read_until2.1 s
independent round-tripserial_get 3787 B → byte-for-byte compare — PASS41.5 s

143 journal events (who / when / what / answer): every step — including the refusal and every hex chunk in both directions — replays from journal.jsonl. The meteo station was verified by an independent full round-trip, not by trusting the put's return value.

What the comparison actually says

Honesty notes

Raw data (verify, don't trust)