{"id":1976,"date":"2026-05-06T06:21:07","date_gmt":"2026-05-05T23:21:07","guid":{"rendered":"https:\/\/daiilynews.cu.ma\/i-built-an-mcp-server-so-ai-agents-can-flash-1000-embedded-boards\/"},"modified":"2026-05-06T06:21:07","modified_gmt":"2026-05-05T23:21:07","slug":"i-built-an-mcp-server-so-ai-agents-can-flash-1000-embedded-boards","status":"publish","type":"post","link":"https:\/\/daiilynews.cu.ma\/?p=1976","title":{"rendered":"I built an MCP server so AI agents can flash 1,000+ embedded boards"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<p>npx pio-mcp dashboard<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>That&#8217;s the install. Open a terminal anywhere \u2014 your laptop, a fresh VM, a coworker&#8217;s machine \u2014 type one line, and you get a React dashboard wired to PlatformIO Core. From there an LLM can compile firmware, flash it to a real board, and stream serial back to the same browser tab.<\/p>\n<p>platformio-mcp v2.0.0 shipped to npm. Here&#8217;s why and how.<\/p>\n<p>  The gap<\/p>\n<p>LLMs are stupidly good at writing firmware. Hand Claude a datasheet and it&#8217;ll spit out C++ that compiles. Hand it the FreeRTOS docs and it&#8217;ll wire up a queue without breaking a sweat.<\/p>\n<p>The next step always falls apart.<\/p>\n<p>&#8220;Great, now flash it to the ESP32 sitting on my desk.&#8221;<\/p>\n<p>You get back a markdown wall of &#8220;first install pyenv, then bootstrap a venv, then pip install platformio, then check your USB-C cable supports data, then make sure the right udev rule is in place on Linux, then&#8230;&#8221; It&#8217;s a setup-doc generator. The agent has read every PlatformIO tutorial ever written. It still can&#8217;t push bytes to flash memory because it has no hands.<\/p>\n<p>MCP is the hands. The agent calls a tool, the tool runs on your machine, the result comes back. PlatformIO Core is already a CLI that knows how to talk to ~1,000 boards across 30+ platforms (ESP32-S3, RP2040, STM32H7, nRF52840, ATmega328P, Teensy 4.1, SAMD21, ATtiny85, and so on). I exposed it through MCP. That&#8217;s the whole product.<\/p>\n<p>  What v2.0.0 actually does<\/p>\n<p>Nine MCP tools. Each one is a thin wrapper around a pio subcommand:<\/p>\n<p>list_boards        \u2192 pio boards<br \/>\ninit_project       \u2192 pio project init<br \/>\nbuild_project      \u2192 pio run                       (background mode + status polling)<br \/>\nupload_firmware    \u2192 pio run &#8211;target upload       (optional start_monitor)<br \/>\nlist_devices       \u2192 pio device list<br \/>\nserial_monitor     \u2192 pio device monitor            (non-blocking, streamed)<br \/>\nsearch_libraries   \u2192 pio pkg search<br \/>\ninstall_library    \u2192 pio pkg install<br \/>\nlist_libraries     \u2192 pio pkg list<br \/>\nget_dashboard_url  \u2192 returns localhost URL with bound auth token<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>Plus init_project, the unsung hero. PlatformIO project scaffolding is the thing agents got wrong every single time before this \u2014 they&#8217;d hand-write a platformio.ini with three subtle bugs in the board_build section. The MCP tool just shells out to pio project init and the bugs vanish.<\/p>\n<p>  The demo that closes the deal<\/p>\n<p>Real prompt, real ESP32, real flash:<\/p>\n<p>> Initialize a new Arduino project for an ESP32 Dev Board in \/tmp\/esp32-blink.<br \/>\n  Build it, flash it, and start the serial monitor.<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>The agent&#8217;s tool calls, in order:<\/p>\n<p>list_boards          { filter: &#8220;esp32&#8221; }                                 \u2192 esp32dev<br \/>\ninit_project         { board: &#8220;esp32dev&#8221;, framework: &#8220;arduino&#8221;,<br \/>\n                       projectDir: &#8220;\/tmp\/esp32-blink&#8221; }<br \/>\nbuild_project        { projectDir: &#8220;\/tmp\/esp32-blink&#8221; }                  \u2192 SUCCESS<br \/>\nupload_firmware      { projectDir: &#8220;\/tmp\/esp32-blink&#8221;,<br \/>\n                       start_monitor: true }                             \u2192 flashed<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>End-to-end on a clean machine: ~90 seconds. Most of that is the PlatformIO toolchain pulling esptool and the Espressif SDK on first run. Subsequent flashes are sub-10s.<\/p>\n<p>  Install in one command<\/p>\n<p>We make it easy to integrate PIO MCP into your choice of coding agent. v2.0.0 ships a one-shot installer:<\/p>\n<p>npx platformio-mcp install &#8211;cline       # Cline (VS Code extension or CLI)<br \/>\nnpx platformio-mcp install &#8211;claude      # Claude Desktop<br \/>\nnpx platformio-mcp install &#8211;vscode      # VS Code native MCP support<br \/>\nnpx platformio-mcp install &#8211;antigravity # Google Antigravity<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>Each installer:<\/p>\n<p>Resolves the host&#8217;s config path per OS. macOS goes to ~\/Library\/Application Support, Windows reads %APPDATA%, Linux falls back to ~\/.config. There&#8217;s a 9-line appDataDir() helper that does the dispatch.<br \/>\nReads the existing config if one&#8217;s already there.<br \/>\nIf the JSON is corrupted, copies it to .bak before rewriting. I learned this the hard way.<br \/>\nIdempotently merges an mcpServers.platformio block. Re-running the installer is a no-op.<br \/>\nPrints the path it touched so you can grep for it later.<\/p>\n<p>For any other MCP host, this is the manual config block:<\/p>\n<p>{<br \/>\n  &#8220;mcpServers&#8221;: {<br \/>\n    &#8220;platformio&#8221;: {<br \/>\n      &#8220;command&#8221;: &#8220;npx&#8221;,<br \/>\n      &#8220;args&#8221;: (&#8220;-y&#8221;, &#8220;platformio-mcp&#8221;, &#8220;&#8211;open-dashboard-on-start&#8221;)<br \/>\n    }<br \/>\n  }<br \/>\n}<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>  The dashboard<\/p>\n<p>The dashboard is the part nobody asks for and everybody uses once they have it.<\/p>\n<p>Reason: build output is the worst possible thing to feed back to an LLM. A clean pio run for an ESP32 project is 40+ kilobytes of toolchain noise \u2014 arm-none-eabi-gcc flags, linker incantations, every single .o file. Pour that into the agent&#8217;s context and you&#8217;ve spent a third of your token budget on text the agent doesn&#8217;t need.<\/p>\n<p>So the MCP tools return short, structured summaries to the LLM. The full output streams over Socket.io to a React dashboard the human can watch:<\/p>\n<p>A per-process random UUID is injected as PORTAL_AUTH_TOKEN at boot. Every HTTP request and every Socket.io connection requires it. The dashboard URL looks like http:\/\/localhost:8080?token= and that token isn&#8217;t in any config file or env var the LLM has access to. If you launch the dashboard, only you (and the agent that spawned it) can hit the API.<\/p>\n<p>The auto-launch is gated behind &#8211;open-dashboard-on-start (or the PIO_MCP_OPEN_DASH_ON_START=true env var). Browser launch goes through the open package, so the same call works on macOS, Linux, and Windows. The previous version had a hardcoded exec(&#8216;open &#8230;&#8217;) that only fired on macOS \u2014 patched in v2.0.0.<\/p>\n<p>  Things I&#8217;m proud of that nobody will notice<\/p>\n<p>The tarball is 499 kB. 114 files. build\/ + web\/dist\/ + scripts\/installers\/ + LICENSE + README. No node_modules, no tests, no web\/src\/. The minified UI bundle is 921 kB \/ 291 kB gzip on its own; everything else is rounding error.<\/p>\n<p>prepublishOnly runs the full TypeScript build, the Vite UI build, and a smoke check that asserts build\/index.js, web\/dist\/index.html, and scripts\/installers\/index.js exist before npm allows the publish to proceed. Hard to ship a broken artifact.<br \/>\nWorkspace state is mediated through proper-lockfile. Two agent processes can&#8217;t race each other on the same project. If you&#8217;ve ever had two MCP servers fight over the same serial port, you know why this matters.<br \/>\nThe pio-mcp alias package is 842 bytes. Three files: a 283-byte bin.js that does import(&#8220;platformio-mcp&#8221;), a package.json with one dependency, a README. Same binary, shorter to type.<br \/>\nThe default npx platformio-mcp (no subcommand) still boots the MCP stdio server. Existing configs that point at build\/index.js keep working unchanged. v2 is additive.<\/p>\n<p>  Get started in five seconds<\/p>\n<p># Open the dashboard right now. No clone, no build, no install.<br \/>\nnpx pio-mcp dashboard<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p># Wire it into your AI agent of choice.<br \/>\nnpx platformio-mcp install &#8211;cline<br \/>\nnpx platformio-mcp install &#8211;claude<br \/>\nnpx platformio-mcp install &#8211;vscode<br \/>\nnpx platformio-mcp install &#8211;antigravity<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>Repo: github.com\/jl-codes\/platformio-mcpnpm: platformio-mcp \u00b7 pio-mcpRelease notes: v2.0.0<\/p>\n<p>I&#8217;m @forkbombETH on X. Issues and PRs welcome on GitHub. If you build something cool with this, lmk.<\/p>\n<p>npx pio-mcp dashboard<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>A huge warm thank you to Matt Mcneill for being an amazing collaborator and pushing for the features that make v2 amazing!<\/p>\n<p><br \/>\n<br \/><a href=\"https:\/\/dev.to\/tonythehacker\/i-built-an-mcp-server-so-ai-agents-can-flash-1000-embedded-boards-5bbd\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>npx pio-mcp dashboard Enter fullscreen mode Exit fullscreen mode That&#8217;s the install. Open a terminal anywhere \u2014 your laptop, a fresh VM, a coworker&#8217;s machine \u2014 type one line, and you get a React dashboard wired to PlatformIO Core. From there an LLM can compile firmware, flash it to a real board, and stream serial [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1977,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[676],"tags":[835,761,765,762,763,836,764,833,834,760],"class_list":["post-1976","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tech-ai","tag-ai","tag-coding","tag-community","tag-development","tag-engineering","tag-firmware","tag-inclusive","tag-iot","tag-mcp","tag-software"],"_links":{"self":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts\/1976","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1976"}],"version-history":[{"count":0,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts\/1976\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/media\/1977"}],"wp:attachment":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1976"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1976"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1976"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}