From 66eb849da4e5aed02fbd4a5a1b866174374934a6 Mon Sep 17 00:00:00 2001 From: mixelpixx Date: Mon, 27 Jul 2026 19:56:45 -0400 Subject: [PATCH] docs: CLAUDE.md, distributable Claude Code skill, agent-guidance README - Root CLAUDE.md: the repo knowledge that otherwise gets relearned every session - monorepo layout (arduino-ide-extension is vendored upstream), the exact-pin version-bump rule and the immutable-install check that have each broken a release, the fast backend-only iteration recipe against the installed app, the rebuild:electron cache lie, the windows-2022 CI pin, MCP architecture notes (DI child container, the single serial funnel, bridge/instructions parity), and the dev board's identity quirks. - skills/arduino-agent/SKILL.md: distributable Claude Code skill with the judgment layer the server instructions don't have room for - the board identification decision tree, UART-vs-native-USB flashing (and CDCOnBoot pairing), lossless serial capture idioms, crash-event interpretation, watchdog-safe firmware rules, the measured PSRAM/SRAM gap, the bundled profile example, and the Wokwi-simulation recipe for boardless work. - READMEs: root quick-start points Claude Code users at the prompts and the skill; the extension README gains a "Guidance for agents" section, an explicit note that hooks/custom subagents are deliberately not shipped, and a v0.7 roadmap entry for the pin-safety preflight. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 88 ++++++++++++++++++++++++++++++ README.md | 6 +++ arduino-mcp-extension/README.md | 25 +++++++++ skills/arduino-agent/SKILL.md | 96 +++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 CLAUDE.md create mode 100644 skills/arduino-agent/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..3e370cbb51b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,88 @@ +# Arduino Agent — repo guide for AI agents + +Fork of Arduino IDE 2.x (Theia/Electron) with an embedded MCP server. +`arduino-ide-extension/` is **vendored upstream code** (synced from +arduino/arduino-ide by targeted file copies — this repo shares no git history +with upstream); `arduino-mcp-extension/` is ours; `electron-app/` packages both. + +## Build + +- Yarn 4 via corepack (`corepack enable`). Node 18+ works (engines say <21 but + 22 is fine in practice). +- `yarn install` applies `.yarn/patches/*` — after editing ANY patch file you + MUST regenerate and commit `yarn.lock` in the same commit, or CI's immutable + install fails with YN0028. +- Workspace deps are pinned to **exact versions**. A version bump must touch + the workspace's package.json + every dependent's pin (`electron-app` pins + both extensions) + a regenerated `yarn.lock`, all in ONE commit. Use + `node scripts/update-version.js x.y.z` for IDE-version bumps (it also visits + arduino-mcp-extension's deps). Verify with + `corepack yarn install --immutable --mode=skip-build` — exactly what CI runs + — BEFORE pushing a release tag. Both failure modes have broken releases. +- Typecheck an extension: `npm run --prefix build` (NOT + `yarn --cwd` — Yarn 4 bin-scoping falls back to a stale global tsc). + +## Fast backend-only iteration (Windows dev box) + +The MCP extension is bundled into `electron-app/lib/backend/main.js`. To test +a backend change against the installed app at `C:\Arduino` without full +packaging: + +1. `npm run --prefix arduino-mcp-extension build` +2. In `electron-app/`: `node ../node_modules/@theia/cli/bin/theia build + --config webpack.config.js --mode production` (capture FULL output to a + file — piping through tail/grep has repeatedly swallowed the real error). + If it dies with ENOTEMPTY on `lib/backend/native-webpack-plugin`, delete + that directory and rerun (transient Windows file-lock race). +3. Kill "Arduino IDE" processes, copy `electron-app/lib/backend/main.js` over + `C:\Arduino\resources\app\lib\backend\main.js`, relaunch. +4. Confirm the copy landed by grepping the installed main.js for a distinctive + new string, and check `/health` uptime is small (a stale uptime means the + old process survived the kill). + +`theia rebuild:electron` lies: it says "already rebuilt" even when +`node_modules/node-pty/build/Release/pty.node` is missing. Force it with +`rm -rf electron-app/.browser_modules node_modules/node-pty/build`. The +production webpack build needs pty.node to exist, and rebuild:electron must +run BEFORE the webpack build. + +## CI / release + +- `check-mcp-extension.yml` runs tsc only. The real test is + `arduino-mcp-extension/test/manual/smoke-test.js` against a RUNNING IDE. +- Release: push a `v*` tag (or create the ref via + `gh api .../git/refs -f ref=refs/tags/vX -f sha=
`) → + `release.yml` builds win/linux/mac and attaches a DRAFT release; publish + with `gh release edit vX --draft=false --latest`. +- The Windows job is pinned to `windows-2022`: the -latest image ships Visual + Studio 18, which no released node-gyp can detect. Do not "fix" this by + bumping node-gyp; it does not help. +- Release checklist: immutable-install check, smoke test (includes the + bridge-instructions parity assertion), hardware pass if tools changed. + +## MCP server architecture notes + +- Theia binds CoreService/BoardsService/etc. per frontend connection; the MCP + server reaches them through its own child container + (`mcp-arduino-services.ts`). A backend singleton cannot @inject them. +- Serial output flows through ONE funnel: `ws.on('message')` in + `mcp-serial-manager.ts` (cursor accounting, line scanner, crash-signature + events, wait_for waiters all hang off it). +- Server guidance for agents lives in `src/common/mcp-instructions.ts` (sent + at initialize) and `src/common/mcp-prompts.ts`. The stdio bridge + (`bridge/arduino-agent-bridge.js`) answers initialize locally, loading the + compiled instructions with an embedded fallback — the smoke test asserts + bridge/server parity. +- Serial monitor settings only apply when the monitor service is CREATED; + `connect()` stops any existing service first. The reported baud rate always + reflects the wire (ON_SETTINGS_DID_CHANGE feedback) — never "fix" it to echo + the requested value. + +## Hardware testing + +Dev board: ESP32-S3 N16R8 on the CH343 UART port (vid 0x1A86), FQBN +`esp32:esp32:esp32s3:FlashSize=16M,PSRAM=opi,CDCOnBoot=default`. Its USB PID +(0x4001, native port) appears in no boards.txt, so it can never be +auto-identified — always pass the FQBN explicitly (or use +`arduino_board suggest_fqbn`). Uploads via the UART port need no BOOT-button +dance; the native-USB port does. diff --git a/README.md b/README.md index c382bb89d2f..69c916092b4 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ Unzip and run `Arduino IDE` (`Arduino IDE.exe` on Windows). Older builds are on > and no dependencies. See [the bridge README](arduino-mcp-extension/bridge/README.md) > for auto-launch and other options. + > **Claude Code users:** the server sends workflow guidance automatically, + > and ships three slash commands (`/bringup`, `/debug-serial`, + > `/profile-board`). For deeper hardware know-how, install the bundled + > skill: copy [`skills/arduino-agent/`](skills/arduino-agent/) into + > `~/.claude/skills/`. + 3. **Talk to your board.** - *"Create a Blink sketch and open it."* - *"What boards are connected?"* diff --git a/arduino-mcp-extension/README.md b/arduino-mcp-extension/README.md index 2b426a9a50a..4823931fb86 100644 --- a/arduino-mcp-extension/README.md +++ b/arduino-mcp-extension/README.md @@ -446,6 +446,31 @@ Returns current IDE state including: Returns task status: `pending`, `running`, `completed`, `failed`, or `cancelled`. +## Guidance for agents + +The server teaches connected agents how to use it: + +- **Instructions at initialize** — every MCP client receives ~30 lines of + workflow guidance (explicit FQBNs for unidentifiable boards, `wait:true`, + cursor-based serial, crash events, BOOT-button recovery). Source: + `src/common/mcp-instructions.ts`; the stdio bridge serves an identical copy + when the IDE is closed (parity asserted by the smoke test). +- **MCP prompts** — `bringup`, `debug-serial`, `profile-board`; Claude Code + surfaces them as slash commands. +- **Claude Code skill** — richer judgment (port choice, CDCOnBoot, watchdog + rules, Wokwi simulation): [`skills/arduino-agent/`](../skills/arduino-agent/). + +Deliberately **not** shipped: client-side hooks and custom subagents. The +instructions + prompts + skill cover the same ground without imposing +configuration on users; revisit only if a concrete need appears. + +## Roadmap + +- **v0.7 — pin-safety preflight**: validate pin choices against the target + board (existence, input-only pins, strapping pins, PWM/I2C conflicts) + before upload. Needs a real per-board pin database to be more than theater, + which is why it did not ship in v0.6. + ## Environment Variables Environment variables override the IDE preferences: diff --git a/skills/arduino-agent/SKILL.md b/skills/arduino-agent/SKILL.md new file mode 100644 index 00000000000..9d2603f899d --- /dev/null +++ b/skills/arduino-agent/SKILL.md @@ -0,0 +1,96 @@ +--- +name: arduino-agent +description: Drive Arduino-compatible hardware through the Arduino Agent MCP server - identify boards, write sketches, compile, flash, and debug over serial. Use when working with Arduino, ESP32, or embedded boards via the arduino MCP tools. +--- + +# Driving hardware with Arduino Agent + +Arduino Agent is an Arduino IDE with an MCP server embedded in it. You share +one editor, one board and one serial monitor with the user: when you write a +sketch, their editor live-reloads; when they select a board, your tools see it. + +The server's own instructions (sent at connect) cover the core workflow. This +skill adds the judgment that comes from real hardware sessions. + +## Install the bridge, not the URL + +If the MCP config points at `http://127.0.0.1:3847/mcp` directly, the whole +server shows "failed to connect" whenever the IDE is closed. Prefer the stdio +bridge (`arduino-mcp-extension/bridge/arduino-agent-bridge.js` in the repo or +install): it always connects, returns "launch the IDE" as a normal tool error, +and recovers without a client restart. + +## Identifying boards: the decision tree + +1. `arduino_board list_connected` → each port has `identified`, `vid`, `pid`. +2. `identified: true` → use the reported fqbn. Done. +3. `identified: false` → `arduino_board suggest_fqbn {port}`: + - **Native VID** (Espressif 0x303A, Arduino 0x2341, RasPi 0x2E8A): family + candidates come back; PID cannot distinguish S2/S3/C3 variants, so ask + the user or check the silkscreen if more than one fits. + - **Bridge VID** (CH34x 0x1A86, CP210x 0x10C4, FTDI 0x0403): the VID names + the *adapter*, not the MCU. Re-run with `name:` from the user/silkscreen. + - `core_to_install` set → run `install_core` (esp32 core ≈ minutes and GBs; + warn the user), then re-run suggest_fqbn if the fqbn was hidden. +4. From then on pass the fqbn EXPLICITLY to compile/upload/serial connect. + Never retry auto-identification hoping it changes - it cannot. + +## Flashing: which USB port matters more than anything + +Many dev boards have two USB-C ports. **Uploads via the UART/bridge port just +work** (esptool toggles DTR/RTS). Uploads via the **native USB port** often +fail with "No serial data received" and may need the user to hold BOOT, tap +RESET, release BOOT - and after flashing, the port re-enumerates and can +change its number. If an upload fails, read `result.explained` first; it +distinguishes these cases. When the user reports a two-port board, recommend +the UART port. + +Match `CDCOnBoot` to the cable: on the UART port use `CDCOnBoot=default` +(Serial → UART0); `CDCOnBoot=cdc` sends Serial to the native port and the +UART port goes silent while the sketch runs fine. + +## Serial: capture without loss, diagnose crashes + +- Keep the `cursor` from every read; pass it as `since` next time. `dropped` + > 0 means the 512KB buffer overflowed between reads - read more often. +- `wait_for {pattern, timeout_seconds}` beats polling. A crash/reset resolves + it early with the `event` attached - that is a feature, not a failure. +- `events` tell you what a raw stream cannot: repeated `reset` events = + crash loop (compare `detail` reasons); `panic` carries the Guru Meditation + cause and backtrace; `brownout` = power supply, not code; `watchdog` = + something starved an idle task. +- Garbage output = baud mismatch. The monitor's reported baudRate is always + the true wire rate; match the sketch's `Serial.begin()`. + +## Firmware the hardware will accept + +- Never busy-loop: `while(true){}` or a tight compute loop starves FreeRTOS' + idle task → task watchdog → reboot. Yield ~1ms every ≤200ms + (`vTaskDelay(1)`) - >99% duty cycle, no watchdog. +- ESP32 PSRAM is ~17x slower than internal SRAM (measured); keep hot buffers + internal, bulk data in PSRAM. +- After `esp_restart()` or a panic, boot-ROM lines appear on UART (not always + on native USB). Wait ~1s after CDC re-enumeration before expecting output. + +## Recipes (also available as MCP prompts) + +- **/bringup** — detect → suggest_fqbn → install core → heartbeat blink → + upload wait:true → wait_for the heartbeat. +- **/debug-serial** — connect → cursor reads → interpret events → concrete fix. +- **/profile-board** — from_example 99.ArduinoAgent/ESP32_SelfProfile → + upload → parse the JSON it streams (chip, memory bandwidth, thermal curve). + Needs nothing but USB; good demo and good smoke test of a board. + +## No board? Simulate + +Wokwi's CLI ships an experimental MCP server that runs firmware on simulated +boards (ESP32, Uno, ...) - virtual buttons, sensors, serial assertions. Use it +alongside Arduino Agent when no hardware is attached: develop and logic-test +in the simulator, then flash the real board with these tools. See +https://docs.wokwi.com/wokwi-ci/mcp-support (needs a Wokwi CLI token). + +## Timeouts + +Task waits: default 60s, max 600. Serial wait_for: default 30s, max 120. Both +return progress/cursor on timeout instead of erroring - re-issue to keep +waiting. Keep requested timeouts under your client's per-call MCP timeout.