Install
$ agentstack add mcp-sensorsiot-embedded-ai-harness ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
The Harness — AI Closed-Loop Programming for Embedded Systems
[](https://github.com/SensorsIot/Embedded-AI-Harness/actions/workflows/ci.yml)
Spec to silicon, hands off.
A horse is strong, fast, and willing — and useless for heavy loads until you harness it. The harness is not a part of the horse and not a part of the cart: it is the coupling that turns raw strength into pulled weight.
An AI is the same. It can write firmware all day — but it can't flash a board, can't see it boot, can't know whether its fix actually worked on real hardware. Unharnessed, it generates code and hopes. This repository is the harness: strap the AI in, and it pulls — writes the code, compiles it, flashes it onto a real ESP32, tests it against real WiFi, MQTT, BLE and RF, reads the failures, corrects itself, and goes again — until the tests run clean.
🔄 AI Closed-Loop Programming
Today's AI coding is open-loop: prompt → code → hope. No feedback, so errors accumulate uncorrected — which is exactly why people don't trust AI-written firmware. AI Closed-Loop Programming (AICLP) closes the loop with reality:
FSD ────── the setpoint: what "done" means
│
▼
┌──── code → build → flash ────┐ forward path
│ ▼
│ real hardware
│ │
└── correct ◄── tests ◄────────┘ feedback path
the loop exits when the error signal is zero: tests green
Every embedded engineer knows this diagram — it's a control loop. The spec (FSD) is the setpoint, the firmware on the chip is the plant, the tests are the sensor, failing tests are the error signal, and the AI is the controller that corrects until the error reaches zero.
True TDD, enabled by AI. For twenty-five years, developers drove and tests advised — written after the code, skipped under deadline, tuned until they passed. Here the tests drive for the first time: derived from the spec, run on real silicon, and the only way the AI gets to stop.
🗺️ The Journey — from idea to shipped product
| Phase | You do | You get | Gate | |-------|--------|---------|------| | 0 · Definition — /define | Describe the product; answer an interview, one question at a time | An FSD where every requirement already says how it will be proven | Load defined | | 1 · Harness — /harness | One command; answer the two questions only you can | The project strapped in: docs, test plan, firmware hooks, CI, runner | AI harnessed | | 2 · Commissioning — /commission | Plug the board into a slot, wire any peers | Your board and peers proven working here — a failing test now means the code | DUT ready | | 3 · Build — /build | Start sessions; approve the occasional spec question | Requirements turning green, one by one, on real hardware | Ready for shipment | | ⚑ Shipment — git tag | Push the version tag — the one act that stays human | A release built in a pinned container and verified on the testbench: the journey runs once more on the exact bytes users download | Shipped |
Each phase ends at a gate, derived from project state and never declared — nobody types "phase complete". A gate is not a marker you pass: if anything it requires is unmet, the work loops back to the step that owns it and the whole check runs again. And after shipment the same journey repeats in miniature for every new feature: describe it in a sentence, the loop refuses to code anything no requirement covers, the spec absorbs the delta, and the phases collapse to minutes. No code without a clause is what keeps the spec true for the product's whole life.
🧰 What the Harness consists of
- The method — four Claude Code skills, one per phase:
/define(the
FSD: atomic, falsifiable requirements, each with its verification contract), /harness (one-time setup), /commission and /build (the loop's driver: test design, the plan, audit, what's next).
- The testbench — a Raspberry Pi test instrument that gives the AI hands
and eyes on real hardware. Described below.
- The dev skills — ESP-IDF and PlatformIO lifecycles, logging, WiFi,
BLE, MQTT, debugging, RF, CI — the loop's individual muscles.
✅ Prerequisites — what the loop needs
| You need | For | Where | |---|---|---| | A Raspberry Pi testbench (Pi 3/4/5, or Zero 2 W + USB hub + Ethernet adapter) with an ESP32 board in a slot | The loop's hands and eyes — flash, reset, observe on real hardware | Build it: [Quick Start](#-quick-start--building-the-bench) below | | A GitHub account, git + gh authenticated | This is where the firmware is built — see below | github.com | | Claude Code with this repo's skills | The AI that pulls; the skills are the method | npm i -g @anthropic-ai/claude-code, then copy .claude/skills/ from this repo into your project | | Same LAN | Your dev machine or devcontainer must reach the bench | curl http://$BENCH:8080/api/devices answers |
Throughout this README, $BENCH is the bench's IP address — never a .local name, which does not resolve from inside a container. Find it once:
BENCH=$(python3 .claude/skills/esp-idf-handling/discover-testbench.py | jq -r .ip)
Nothing else — and in particular no local ESP-IDF or PlatformIO installation. The forward path runs through GitHub Actions:
git push ─► CI builds in a pinned container ─► gh run download ─► flash to a slot ─► observe
The loop flashes the artefact CI produced, never a binary that happens to be sitting in a local build/ directory. That is not a convenience, it is what makes the evidence mean anything: the bytes on the chip are the bytes in the run you can point at, built by a toolchain whose version is pinned in the workflow rather than whatever is installed on somebody's laptop. The same property is what lets a tag ship — the release-verify job flashes the released artefact to the bench and reruns the journey, so a red journey is a release that does not happen.
The toolchain skills (esp-idf-handling, esp-pio-handling) drive that pipeline and know how to build locally too, which is useful when you are iterating by hand. The loop does not depend on it.
The FSD, tests, firmware, CI and documentation are what the loop produces, not what you bring.
🔌 The Testbench — the loop's hands and eyes
Working on an ESP32 normally means being physically attached to it — and an AI can't hold a USB cable. The testbench puts the boards on a Raspberry Pi and turns everything into HTTP:
LAN (192.168.0.x)
|
| eth0 (wired)
v
Raspberry Pi ---- wlan0 (WiFi test AP: 192.168.4.x)
testbench_7e71 hci0 (Bluetooth LE)
| UDP :5555 (log receiver)
| USB hub (internal on Pi 3/4/5, external on Zero)
|
+----+----+----+----+
| | | |
:4001 :4002 :4003 :4004 **On a Pi Zero 2 W, do the memory hardening first.** With 512 MB the board
> OOM-crashes under load, and hard crashes corrupt the SD card. See
> [User Manual §2.2](docs/Harness-User-Manual.md#22-first-boot--system-hardening).
## 🔧 Usage
**Watch a board boot** — no client library, just HTTP:
```bash
curl -X POST http://$BENCH:8080/api/serial/reset \
-H 'Content-Type: application/json' -d '{"slot":"SLOT1"}'
Point your existing tools at it. PlatformIO needs one line (upload_port = rfc2217://$BENCH:4001); esptool takes the same URL, and the binaries stay on your machine:
esptool --port rfc2217://$BENCH:4001 --chip esp32c3 \
write-flash 0x10000 firmware.bin
Write a test that uses the whole bench — reset the board, give it a network to join, wait for it to appear, then talk to it:
from testbench_driver import TestbenchDriver
wt = TestbenchDriver("http://$BENCH:8080")
wt.serial_reset("SLOT1")
wt.serial_monitor("SLOT1", pattern="WiFi connected", timeout=30)
wt.ap_start("TestAP", "password123")
station = wt.wait_for_station(timeout=30)
wt.http_get(f"http://{station['ip']}/status")
🤖 Driving It From Claude
An MCP server exposes the whole API as 70 tools, so Claude Desktop or Claude Code can operate the bench conversationally — "flash this to slot 1 and tell me why it's crashing". Pure Python standard library, so there's nothing to pip install. For Claude Desktop, drag [mcp/embedded-ai-harness-testbench.mcpb](mcp/embedded-ai-harness-testbench.mcpb) onto Settings → Extensions and enter your testbench URL.
The AICLP skills (/define, /harness, /commission, /build) and the instrument skills all live under .claude/skills/. Setup for both: [User Manual §15](docs/Harness-User-Manual.md#15-driving-the-bench-from-claude).
🩺 Troubleshooting
| Symptom | Cause | Fix | |---------|-------|-----| | Device not detected | Charge-only USB cable | Use a data cable; check lsusb on the Pi | | Wrong boot mode (0x13) when flashing | Bridge-chip board — RFC2217 can't drive its auto-reset | Flash with POST /api/flash instead | | Rapid connect/disconnect | Erased or corrupt flash, boot loop | Auto-recovers via GPIO; force with POST /api/serial/recover | | ESP32-C3 stuck in download mode | DTR asserted when the port opened | POST /api/serial/reset | | GDB won't connect | Classic ESP32 has no USB-JTAG | Wire an ESP-Prog and declare it in testbench.json | | SDR decodes noise or all zeros | Transmitter too close, AGC overloading | Add distance, set a fixed gain | | Pi reboots at random | Out of memory (Pi Zero 2 W) | Apply the §2.2 hardening; check free -h |
Full table, with the diagnostics to run on the Pi → [User Manual §17](docs/Harness-User-Manual.md#17-troubleshooting).
📡 Under the Hood
Serial travels over RFC2217, a Telnet extension that carries serial line control — baud rate, DTR, RTS — over TCP. That's why it needs no kernel modules and passes through firewalls, and why esptool and pyserial speak it natively.
Hotplug is event-driven, not polled: a udev rule fires on USB add/remove and POSTs to the portal, which starts or stops that slot's proxy. Station events on the test AP arrive the same way, via dnsmasq DHCP lease callbacks. Boards with native USB-Serial/JTAG need care — Linux asserts DTR and RTS the moment the port opens, dropping the chip into download mode mid-boot — so the portal delays opening and drives the reset sequence itself.
Two consumers may want the same board at once — a flash, a debug session, a monitor — so a slot access manager arbitrates mode, never the data path. A caller acquires a slot, holds a renewable lease and releases it; a conflicting request is refused with who holds it and since when, and is never served by yanking the board from whoever has it. A holder that stops renewing loses the lease, so a client that dies does not wedge the slot until someone restarts the portal.
POST /api/bench/reset returns the whole bench to a known state and is meant as the first call of every run — a test that starts from what the last one left behind is measuring history.
Everything is one JSON HTTP API on :8080; every response carries "ok".
curl -X POST .../api/wifi/ap_start -d '{"ssid":"TestAP","password":"secret"}'
curl -X POST .../api/gpio/set -d '{"pin":18,"value":0}'
curl -X POST .../api/sdr/capture -d '{"freq_hz":433920000,"duration_s":10}'
📚 Documentation
The plane map is [docs/00-Overview.md](docs/00-Overview.md) — three documents, one per question, and everything is in one of them:
| Question | Document | Read it for | |----------|----------|-------------| | What must be true? | [Functional Specification](docs/Harness-FSD.md) | AICLP, the journey, and what the bench does clause by clause. [Appendix D](docs/Harness-FSD.md#appendix-d-http-api--mcp-reference) is the complete HTTP API and MCP tool reference. | | How is it built? | [Method](docs/Method/00-Overview.md) | The build contract for contributors and AI agents — [workflow](docs/Method/AI-Workflow.md), [architecture](docs/Method/project/architecture.md), conventions, testing standard. | | How do I run it? | [User Manual](docs/Harness-User-Manual.md) | Building the Pi, wiring, and driving every service — install, serial, flashing, debug, WiFi, RF, test automation, troubleshooting. |
🙏 Attributions
Built on pyserial (RFC2217), esptool and OpenOCD from Espressif, bleak, rtl433, hostapd / dnsmasq, mosquitto, and the Model Context Protocol.
📄 License
MIT — see [LICENSE](LICENSE).
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: SensorsIot
- Source: SensorsIot/Embedded-AI-Harness
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.