platformio.mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: platformio.mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server that lets AI coding agents drive PlatformIO: boards, projects, build, flash, serial monitor, tests, static analysis, libraries.
5
+ Keywords: mcp,platformio,embedded,esp32,arduino,firmware,ai-agent
6
+ Author: Mihir Gandecha
7
+ Author-email: Mihir Gandecha <mihirgandecha.2002@gmail.com>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Embedded Systems
14
+ Requires-Dist: mcp>=1.0
15
+ Requires-Dist: pyserial>=3.5
16
+ Requires-Dist: platformio>=6.1 ; extra == 'platformio'
17
+ Requires-Python: >=3.12
18
+ Project-URL: Homepage, https://github.com/powerdragonfire/platformio.mcp
19
+ Project-URL: Issues, https://github.com/powerdragonfire/platformio.mcp/issues
20
+ Provides-Extra: platformio
21
+ Description-Content-Type: text/markdown
22
+
23
+ <h1 align="center">platformio.mcp</h1>
24
+
25
+ <p align="center">
26
+ <b>Give your AI coding agent hands on real hardware.</b><br>
27
+ An <a href="https://modelcontextprotocol.io">MCP</a> server for <a href="https://platformio.org">PlatformIO</a>: build, flash, watch serial, run tests, decode crashes, shrink firmware.
28
+ </p>
29
+
30
+ <p align="center">
31
+ <a href="https://github.com/powerdragonfire/platformio.mcp/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/powerdragonfire/platformio.mcp/actions/workflows/ci.yml/badge.svg"></a>
32
+ <a href="https://pypi.org/project/platformio.mcp/"><img alt="PyPI" src="https://img.shields.io/pypi/v/platformio.mcp?color=blue"></a>
33
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-blue">
34
+ <img alt="Tools" src="https://img.shields.io/badge/MCP%20tools-29-6f42c1">
35
+ <a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-green"></a>
36
+ </p>
37
+
38
+ <p align="center">
39
+ Python native · no Node · one line to install · works with Claude Code, Claude Desktop, Cursor, Codex, Windsurf, Cline
40
+ </p>
41
+
42
+ ---
43
+
44
+ ## ⚡ 60-second install
45
+
46
+ You need [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`). Then:
47
+
48
+ ```bash
49
+ uvx platformio.mcp install --claude-code # or --cursor --claude-desktop --codex --windsurf
50
+ ```
51
+
52
+ No PlatformIO on this machine? Add `--with-platformio` and the server brings PlatformIO Core along.
53
+
54
+ <details>
55
+ <summary><b>Any other MCP client</b> (Cline, Zed, Continue, ...)</summary>
56
+
57
+ ```bash
58
+ uvx platformio.mcp install --print
59
+ ```
60
+
61
+ ```json
62
+ { "mcpServers": { "platformio": { "command": "uvx", "args": ["platformio.mcp"] } } }
63
+ ```
64
+
65
+ Use `"args": ["platformio.mcp[platformio]"]` to bundle PlatformIO Core.
66
+ </details>
67
+
68
+ <details>
69
+ <summary><b>Already have PlatformIO?</b></summary>
70
+
71
+ The server finds `platformio` / `pio` on your PATH or in `~/.platformio/penv`. Override with `PLATFORMIO_MCP_PIO=/path/to/pio`. Run `uvx platformio.mcp doctor` to see what the agent will see.
72
+ </details>
73
+
74
+ ## 🎬 What it looks like
75
+
76
+ > **You:** flash the `view` env and make sure it boots.
77
+ >
78
+ > **Agent** → `pio_flash_and_verify(env="view", expect="setup done")`
79
+ >
80
+ > ```
81
+ > FAIL: firmware flashed but the boot log matched the failure pattern:
82
+ > 'Guru Meditation Error: Core 1 panic'ed (LoadProhibited)'.
83
+ > Cause: LoadProhibited. 3/3 addresses resolved:
84
+ > DisplayTask::run() (display_task.cpp:22) <- DisplayTask::run() (display_task.cpp:23) <- vPortTaskWrapper (port.c:143)
85
+ > ```
86
+ >
87
+ > **Agent:** null pointer on line 22 of `display_task.cpp`, `tft_` is used before `begin()`. Fixing, rebuilding, flashing again.
88
+ >
89
+ > ```
90
+ > PASS: flashed env view in 14.2s and saw 'setup done' on /dev/cu.usbserial-0001 after 2.1s of boot output.
91
+ > ```
92
+
93
+ No 40 KB build logs in the context window. No human reading the serial monitor. The agent gets a verdict, a file and a line.
94
+
95
+ ## 🔁 The loop the agent runs
96
+
97
+ ```mermaid
98
+ flowchart LR
99
+ A[pio_project_envs] --> B[edit code]
100
+ B --> C[pio_build]
101
+ C -- errors with file:line --> B
102
+ C -- ok --> D[pio_flash_and_verify]
103
+ D -- PASS --> E([done])
104
+ D -- FAIL: decoded backtrace --> B
105
+ D -- TIMEOUT --> F[pio_monitor_capture]
106
+ F --> B
107
+ ```
108
+
109
+ ## 🧰 The 29 tools
110
+
111
+ <table>
112
+ <tr><th>Group</th><th>Tools</th><th>What the agent gets back</th></tr>
113
+ <tr>
114
+ <td>🔍 <b>Discover</b></td>
115
+ <td><code>pio_system_info</code> · <code>pio_list_boards</code> · <code>pio_board_info</code> · <code>pio_list_devices</code></td>
116
+ <td>PlatformIO version and policy; ~1,700 boards with MCU, clock, RAM and flash sizes; serial ports with the likely dev boards flagged</td>
117
+ </tr>
118
+ <tr>
119
+ <td>📁 <b>Project</b></td>
120
+ <td><code>pio_project_init</code> · <code>pio_project_envs</code> · <code>pio_project_metadata</code></td>
121
+ <td>A real <code>pio project init</code> (never a hand-written ini); every env with board, framework, monitor and upload settings; defines and include paths</td>
122
+ </tr>
123
+ <tr>
124
+ <td>🔨 <b>Build &amp; flash</b></td>
125
+ <td><code>pio_build</code> · <code>pio_upload</code> · <code>pio_clean</code> · <code>pio_list_targets</code> · <code>pio_run_target</code></td>
126
+ <td>Status, parsed errors and warnings (file, line, column), RAM/Flash %, last 40 lines, full log path. Extra targets like <code>buildfs</code>, <code>erase</code></td>
127
+ </tr>
128
+ <tr>
129
+ <td>📟 <b>Serial</b></td>
130
+ <td><code>pio_monitor_start</code> / <code>read</code> / <code>write</code> / <code>stop</code> / <code>list</code> · <code>pio_monitor_capture</code></td>
131
+ <td>Background sessions with a ring buffer, cursor reads, and <code>wait_for</code> regex; or a one-shot capture with nothing to manage</td>
132
+ </tr>
133
+ <tr>
134
+ <td>✅ <b>Verify</b></td>
135
+ <td><code>pio_test</code> · <code>pio_check</code></td>
136
+ <td>Unity tests with per-case pass/fail and messages; cppcheck / clang-tidy defects by severity with CWE ids</td>
137
+ </tr>
138
+ <tr>
139
+ <td>📦 <b>Packages</b></td>
140
+ <td><code>pio_pkg_search</code> / <code>install</code> / <code>uninstall</code> / <code>list</code> / <code>outdated</code> / <code>update</code></td>
141
+ <td>Registry search and dependency changes that keep <code>platformio.ini</code> in sync</td>
142
+ </tr>
143
+ <tr>
144
+ <td>🧠 <b>Analyse</b></td>
145
+ <td><code>pio_flash_and_verify</code> · <code>pio_decode_backtrace</code> · <code>pio_size_report</code></td>
146
+ <td>Hardware-in-the-loop pass/fail; crash dumps resolved to file:line; where every byte of flash and RAM goes</td>
147
+ </tr>
148
+ </table>
149
+
150
+ Every tool returns `ok`, a one-paragraph `summary` written for the model, structured fields, and a `log_path` to the full output. Long output stays on disk under `~/.platformio-mcp/logs` (newest 200 files kept).
151
+
152
+ ### The three tools that go beyond the CLI
153
+
154
+ | | What it does | Under the hood |
155
+ |---|---|---|
156
+ | 🚀 **`pio_flash_and_verify`** | Flash, open the port, read until `expect` matches (**pass**), a crash signature matches (**fail**, auto-decoded), or the timeout passes (**timeout**) | `pio run -t upload` + pyserial; `fail_on` defaults to Guru Meditation, HardFault, `abort()`, `assert failed`, watchdog, brownout, heap corruption |
157
+ | 🩺 **`pio_decode_backtrace`** | Turn an ESP32 `Backtrace: 0x400d...` dump or a Cortex-M `pc`/`lr` dump into function, file, line, inlined frames, cause, reset reason | Toolchain located from `pio project metadata`, then `<target>-addr2line -pfiaC` on `firmware.elf`; fixes Xtensa `A0` window bits |
158
+ | 📊 **`pio_size_report`** | Why is the firmware this big? Flash/RAM %, loaded sections, biggest symbols with `file:line`, per-file totals, regex `filter` | `pio run -t checkprogsize` (partition-aware) + GNU `size -A` + `nm -S -C -l --size-sort` |
159
+
160
+ ## 🔒 Safety policy
161
+
162
+ Set `PLATFORMIO_MCP_POLICY` in the server's `env`, or pass `--policy` to `install`:
163
+
164
+ | Policy | Can build | Can flash / erase / write serial | Use it for |
165
+ |---|:-:|:-:|---|
166
+ | `full` (default) | ✅ | ✅ | Your own bench |
167
+ | `build_only` | ✅ | ❌ | Shared labs, CI, "look but don't touch" |
168
+ | `read_only` | ❌ | ❌ | Code review, onboarding, untrusted prompts |
169
+
170
+ MCP clients also prompt before each tool call. Policies are the second layer, not the only one.
171
+
172
+ ## ⚙️ Settings
173
+
174
+ | Variable | Purpose | Default |
175
+ |---|---|---|
176
+ | `PLATFORMIO_MCP_POLICY` | `full`, `build_only`, `read_only` | `full` |
177
+ | `PLATFORMIO_MCP_PROJECT_DIR` | Project used when a tool is called without `project_dir` | server's cwd |
178
+ | `PLATFORMIO_MCP_PIO` | Explicit path to the `pio` executable | auto-detect |
179
+ | `PLATFORMIO_MCP_LOG_DIR` | Where full command logs go | `~/.platformio-mcp/logs` |
180
+ | `PLATFORMIO_MCP_MAX_LOGS` | How many log files to keep | `200` |
181
+
182
+ ## 📝 Serial monitor notes
183
+
184
+ Sessions talk to the port with pyserial directly, because PlatformIO's own monitor needs an interactive terminal. PlatformIO monitor filters such as `esp32_exception_decoder` therefore do not apply; `pio_decode_backtrace` does that job. Baud and port default from `monitor_speed` / `monitor_port` in `platformio.ini` when `project_dir` is passed, otherwise the single detected dev board at 115200. Opening the port resets most dev boards, which is why `pio_flash_and_verify` sees the boot log from the top.
185
+
186
+ ## 🛠️ Development
187
+
188
+ ```bash
189
+ git clone https://github.com/powerdragonfire/platformio.mcp && cd platformio.mcp
190
+ uv sync
191
+ uv run pytest # unit tests, no hardware or network
192
+ uv run pytest -m integration # builds the bundled native fixture with your PlatformIO
193
+ uv run platformio-mcp doctor # what the agent's pio_system_info sees
194
+ npx @modelcontextprotocol/inspector uv run platformio-mcp # poke tools interactively
195
+ ```
196
+
197
+ To use your checkout in Claude Code instead of the PyPI release:
198
+
199
+ ```bash
200
+ claude mcp add platformio -- uv run --directory /path/to/platformio.mcp platformio-mcp
201
+ ```
202
+
203
+ Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
204
+
205
+ ## 🤝 Prior art
206
+
207
+ [jl-codes/platformio-mcp](https://github.com/jl-codes/platformio-mcp) is a TypeScript server with the same goal, a web dashboard, and a GPIO pin audit. This project exists for people who want a Python-only install through `uvx`, one that can bundle PlatformIO itself, and crash decoding and size budgeting built in.
208
+
209
+ ## License
210
+
211
+ MIT
212
+
213
+ <!-- mcp-name: io.github.powerdragonfire/platformio.mcp -->
@@ -0,0 +1,191 @@
1
+ <h1 align="center">platformio.mcp</h1>
2
+
3
+ <p align="center">
4
+ <b>Give your AI coding agent hands on real hardware.</b><br>
5
+ An <a href="https://modelcontextprotocol.io">MCP</a> server for <a href="https://platformio.org">PlatformIO</a>: build, flash, watch serial, run tests, decode crashes, shrink firmware.
6
+ </p>
7
+
8
+ <p align="center">
9
+ <a href="https://github.com/powerdragonfire/platformio.mcp/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/powerdragonfire/platformio.mcp/actions/workflows/ci.yml/badge.svg"></a>
10
+ <a href="https://pypi.org/project/platformio.mcp/"><img alt="PyPI" src="https://img.shields.io/pypi/v/platformio.mcp?color=blue"></a>
11
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-blue">
12
+ <img alt="Tools" src="https://img.shields.io/badge/MCP%20tools-29-6f42c1">
13
+ <a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-green"></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ Python native · no Node · one line to install · works with Claude Code, Claude Desktop, Cursor, Codex, Windsurf, Cline
18
+ </p>
19
+
20
+ ---
21
+
22
+ ## ⚡ 60-second install
23
+
24
+ You need [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`). Then:
25
+
26
+ ```bash
27
+ uvx platformio.mcp install --claude-code # or --cursor --claude-desktop --codex --windsurf
28
+ ```
29
+
30
+ No PlatformIO on this machine? Add `--with-platformio` and the server brings PlatformIO Core along.
31
+
32
+ <details>
33
+ <summary><b>Any other MCP client</b> (Cline, Zed, Continue, ...)</summary>
34
+
35
+ ```bash
36
+ uvx platformio.mcp install --print
37
+ ```
38
+
39
+ ```json
40
+ { "mcpServers": { "platformio": { "command": "uvx", "args": ["platformio.mcp"] } } }
41
+ ```
42
+
43
+ Use `"args": ["platformio.mcp[platformio]"]` to bundle PlatformIO Core.
44
+ </details>
45
+
46
+ <details>
47
+ <summary><b>Already have PlatformIO?</b></summary>
48
+
49
+ The server finds `platformio` / `pio` on your PATH or in `~/.platformio/penv`. Override with `PLATFORMIO_MCP_PIO=/path/to/pio`. Run `uvx platformio.mcp doctor` to see what the agent will see.
50
+ </details>
51
+
52
+ ## 🎬 What it looks like
53
+
54
+ > **You:** flash the `view` env and make sure it boots.
55
+ >
56
+ > **Agent** → `pio_flash_and_verify(env="view", expect="setup done")`
57
+ >
58
+ > ```
59
+ > FAIL: firmware flashed but the boot log matched the failure pattern:
60
+ > 'Guru Meditation Error: Core 1 panic'ed (LoadProhibited)'.
61
+ > Cause: LoadProhibited. 3/3 addresses resolved:
62
+ > DisplayTask::run() (display_task.cpp:22) <- DisplayTask::run() (display_task.cpp:23) <- vPortTaskWrapper (port.c:143)
63
+ > ```
64
+ >
65
+ > **Agent:** null pointer on line 22 of `display_task.cpp`, `tft_` is used before `begin()`. Fixing, rebuilding, flashing again.
66
+ >
67
+ > ```
68
+ > PASS: flashed env view in 14.2s and saw 'setup done' on /dev/cu.usbserial-0001 after 2.1s of boot output.
69
+ > ```
70
+
71
+ No 40 KB build logs in the context window. No human reading the serial monitor. The agent gets a verdict, a file and a line.
72
+
73
+ ## 🔁 The loop the agent runs
74
+
75
+ ```mermaid
76
+ flowchart LR
77
+ A[pio_project_envs] --> B[edit code]
78
+ B --> C[pio_build]
79
+ C -- errors with file:line --> B
80
+ C -- ok --> D[pio_flash_and_verify]
81
+ D -- PASS --> E([done])
82
+ D -- FAIL: decoded backtrace --> B
83
+ D -- TIMEOUT --> F[pio_monitor_capture]
84
+ F --> B
85
+ ```
86
+
87
+ ## 🧰 The 29 tools
88
+
89
+ <table>
90
+ <tr><th>Group</th><th>Tools</th><th>What the agent gets back</th></tr>
91
+ <tr>
92
+ <td>🔍 <b>Discover</b></td>
93
+ <td><code>pio_system_info</code> · <code>pio_list_boards</code> · <code>pio_board_info</code> · <code>pio_list_devices</code></td>
94
+ <td>PlatformIO version and policy; ~1,700 boards with MCU, clock, RAM and flash sizes; serial ports with the likely dev boards flagged</td>
95
+ </tr>
96
+ <tr>
97
+ <td>📁 <b>Project</b></td>
98
+ <td><code>pio_project_init</code> · <code>pio_project_envs</code> · <code>pio_project_metadata</code></td>
99
+ <td>A real <code>pio project init</code> (never a hand-written ini); every env with board, framework, monitor and upload settings; defines and include paths</td>
100
+ </tr>
101
+ <tr>
102
+ <td>🔨 <b>Build &amp; flash</b></td>
103
+ <td><code>pio_build</code> · <code>pio_upload</code> · <code>pio_clean</code> · <code>pio_list_targets</code> · <code>pio_run_target</code></td>
104
+ <td>Status, parsed errors and warnings (file, line, column), RAM/Flash %, last 40 lines, full log path. Extra targets like <code>buildfs</code>, <code>erase</code></td>
105
+ </tr>
106
+ <tr>
107
+ <td>📟 <b>Serial</b></td>
108
+ <td><code>pio_monitor_start</code> / <code>read</code> / <code>write</code> / <code>stop</code> / <code>list</code> · <code>pio_monitor_capture</code></td>
109
+ <td>Background sessions with a ring buffer, cursor reads, and <code>wait_for</code> regex; or a one-shot capture with nothing to manage</td>
110
+ </tr>
111
+ <tr>
112
+ <td>✅ <b>Verify</b></td>
113
+ <td><code>pio_test</code> · <code>pio_check</code></td>
114
+ <td>Unity tests with per-case pass/fail and messages; cppcheck / clang-tidy defects by severity with CWE ids</td>
115
+ </tr>
116
+ <tr>
117
+ <td>📦 <b>Packages</b></td>
118
+ <td><code>pio_pkg_search</code> / <code>install</code> / <code>uninstall</code> / <code>list</code> / <code>outdated</code> / <code>update</code></td>
119
+ <td>Registry search and dependency changes that keep <code>platformio.ini</code> in sync</td>
120
+ </tr>
121
+ <tr>
122
+ <td>🧠 <b>Analyse</b></td>
123
+ <td><code>pio_flash_and_verify</code> · <code>pio_decode_backtrace</code> · <code>pio_size_report</code></td>
124
+ <td>Hardware-in-the-loop pass/fail; crash dumps resolved to file:line; where every byte of flash and RAM goes</td>
125
+ </tr>
126
+ </table>
127
+
128
+ Every tool returns `ok`, a one-paragraph `summary` written for the model, structured fields, and a `log_path` to the full output. Long output stays on disk under `~/.platformio-mcp/logs` (newest 200 files kept).
129
+
130
+ ### The three tools that go beyond the CLI
131
+
132
+ | | What it does | Under the hood |
133
+ |---|---|---|
134
+ | 🚀 **`pio_flash_and_verify`** | Flash, open the port, read until `expect` matches (**pass**), a crash signature matches (**fail**, auto-decoded), or the timeout passes (**timeout**) | `pio run -t upload` + pyserial; `fail_on` defaults to Guru Meditation, HardFault, `abort()`, `assert failed`, watchdog, brownout, heap corruption |
135
+ | 🩺 **`pio_decode_backtrace`** | Turn an ESP32 `Backtrace: 0x400d...` dump or a Cortex-M `pc`/`lr` dump into function, file, line, inlined frames, cause, reset reason | Toolchain located from `pio project metadata`, then `<target>-addr2line -pfiaC` on `firmware.elf`; fixes Xtensa `A0` window bits |
136
+ | 📊 **`pio_size_report`** | Why is the firmware this big? Flash/RAM %, loaded sections, biggest symbols with `file:line`, per-file totals, regex `filter` | `pio run -t checkprogsize` (partition-aware) + GNU `size -A` + `nm -S -C -l --size-sort` |
137
+
138
+ ## 🔒 Safety policy
139
+
140
+ Set `PLATFORMIO_MCP_POLICY` in the server's `env`, or pass `--policy` to `install`:
141
+
142
+ | Policy | Can build | Can flash / erase / write serial | Use it for |
143
+ |---|:-:|:-:|---|
144
+ | `full` (default) | ✅ | ✅ | Your own bench |
145
+ | `build_only` | ✅ | ❌ | Shared labs, CI, "look but don't touch" |
146
+ | `read_only` | ❌ | ❌ | Code review, onboarding, untrusted prompts |
147
+
148
+ MCP clients also prompt before each tool call. Policies are the second layer, not the only one.
149
+
150
+ ## ⚙️ Settings
151
+
152
+ | Variable | Purpose | Default |
153
+ |---|---|---|
154
+ | `PLATFORMIO_MCP_POLICY` | `full`, `build_only`, `read_only` | `full` |
155
+ | `PLATFORMIO_MCP_PROJECT_DIR` | Project used when a tool is called without `project_dir` | server's cwd |
156
+ | `PLATFORMIO_MCP_PIO` | Explicit path to the `pio` executable | auto-detect |
157
+ | `PLATFORMIO_MCP_LOG_DIR` | Where full command logs go | `~/.platformio-mcp/logs` |
158
+ | `PLATFORMIO_MCP_MAX_LOGS` | How many log files to keep | `200` |
159
+
160
+ ## 📝 Serial monitor notes
161
+
162
+ Sessions talk to the port with pyserial directly, because PlatformIO's own monitor needs an interactive terminal. PlatformIO monitor filters such as `esp32_exception_decoder` therefore do not apply; `pio_decode_backtrace` does that job. Baud and port default from `monitor_speed` / `monitor_port` in `platformio.ini` when `project_dir` is passed, otherwise the single detected dev board at 115200. Opening the port resets most dev boards, which is why `pio_flash_and_verify` sees the boot log from the top.
163
+
164
+ ## 🛠️ Development
165
+
166
+ ```bash
167
+ git clone https://github.com/powerdragonfire/platformio.mcp && cd platformio.mcp
168
+ uv sync
169
+ uv run pytest # unit tests, no hardware or network
170
+ uv run pytest -m integration # builds the bundled native fixture with your PlatformIO
171
+ uv run platformio-mcp doctor # what the agent's pio_system_info sees
172
+ npx @modelcontextprotocol/inspector uv run platformio-mcp # poke tools interactively
173
+ ```
174
+
175
+ To use your checkout in Claude Code instead of the PyPI release:
176
+
177
+ ```bash
178
+ claude mcp add platformio -- uv run --directory /path/to/platformio.mcp platformio-mcp
179
+ ```
180
+
181
+ Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
182
+
183
+ ## 🤝 Prior art
184
+
185
+ [jl-codes/platformio-mcp](https://github.com/jl-codes/platformio-mcp) is a TypeScript server with the same goal, a web dashboard, and a GPIO pin audit. This project exists for people who want a Python-only install through `uvx`, one that can bundle PlatformIO itself, and crash decoding and size budgeting built in.
186
+
187
+ ## License
188
+
189
+ MIT
190
+
191
+ <!-- mcp-name: io.github.powerdragonfire/platformio.mcp -->
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "platformio.mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server that lets AI coding agents drive PlatformIO: boards, projects, build, flash, serial monitor, tests, static analysis, libraries."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Mihir Gandecha", email = "mihirgandecha.2002@gmail.com" }
9
+ ]
10
+ requires-python = ">=3.12"
11
+ keywords = ["mcp", "platformio", "embedded", "esp32", "arduino", "firmware", "ai-agent"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Topic :: Software Development :: Embedded Systems",
18
+ ]
19
+ dependencies = [
20
+ "mcp>=1.0",
21
+ "pyserial>=3.5",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ # `uvx "platformio.mcp[platformio]"` brings PlatformIO Core along for machines that have never installed it.
26
+ platformio = ["platformio>=6.1"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/powerdragonfire/platformio.mcp"
30
+ Issues = "https://github.com/powerdragonfire/platformio.mcp/issues"
31
+
32
+ [project.scripts]
33
+ platformio-mcp = "platformio_mcp.cli:main"
34
+ "platformio.mcp" = "platformio_mcp.cli:main"
35
+
36
+ [tool.uv.build-backend]
37
+ module-name = "platformio_mcp"
38
+
39
+ [build-system]
40
+ requires = ["uv_build>=0.11.6,<0.12.0"]
41
+ build-backend = "uv_build"
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "pytest>=9.1.1",
46
+ "pytest-asyncio>=1.4.0",
47
+ ]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+ markers = ["integration: needs a working `pio` on PATH and network access"]
52
+ addopts = "-m 'not integration'"
@@ -0,0 +1,3 @@
1
+ """PlatformIO MCP server: gives AI coding agents hands on real embedded hardware."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,132 @@
1
+ """`platformio.mcp` command line: serve (default), install, doctor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import platform
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ from . import __version__
15
+
16
+ CLIENTS = ("claude-code", "claude-desktop", "cursor", "codex", "windsurf", "print")
17
+
18
+
19
+ def server_command(with_platformio: bool, policy: str | None) -> dict:
20
+ pkg = 'platformio.mcp[platformio]' if with_platformio else "platformio.mcp"
21
+ entry = {"command": "uvx", "args": [pkg]}
22
+ if policy:
23
+ entry["env"] = {"PLATFORMIO_MCP_POLICY": policy}
24
+ return entry
25
+
26
+
27
+ def _merge_json(path: Path, key_path: list[str], entry: dict) -> None:
28
+ data = {}
29
+ if path.exists():
30
+ try:
31
+ data = json.loads(path.read_text(encoding="utf-8") or "{}")
32
+ except json.JSONDecodeError as exc:
33
+ sys.exit(f"{path} is not valid JSON ({exc}); fix it or add the entry by hand:\n{json.dumps(entry, indent=2)}")
34
+ node = data
35
+ for key in key_path[:-1]:
36
+ node = node.setdefault(key, {})
37
+ node[key_path[-1]] = entry
38
+ path.parent.mkdir(parents=True, exist_ok=True)
39
+ path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
40
+
41
+
42
+ def claude_desktop_config() -> Path:
43
+ system = platform.system()
44
+ if system == "Darwin":
45
+ return Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json"
46
+ if system == "Windows":
47
+ return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming")) / "Claude" / "claude_desktop_config.json"
48
+ return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
49
+
50
+
51
+ def install(client: str, with_platformio: bool, policy: str | None, scope: str) -> None:
52
+ entry = server_command(with_platformio, policy)
53
+ if client == "print":
54
+ print(json.dumps({"mcpServers": {"platformio": entry}}, indent=2))
55
+ return
56
+ if client == "claude-code":
57
+ claude = shutil.which("claude")
58
+ if not claude:
59
+ sys.exit("`claude` CLI not found on PATH. Install Claude Code, or run: uvx platformio.mcp install --print")
60
+ cmd = [claude, "mcp", "add", "--scope", scope, "--transport", "stdio"]
61
+ for k, v in entry.get("env", {}).items():
62
+ cmd += ["--env", f"{k}={v}"]
63
+ cmd += ["platformio", "--", entry["command"], *entry["args"]]
64
+ print("$ " + " ".join(cmd))
65
+ subprocess.run(cmd, check=False)
66
+ return
67
+ if client == "claude-desktop":
68
+ path = claude_desktop_config()
69
+ _merge_json(path, ["mcpServers", "platformio"], entry)
70
+ print(f"Added 'platformio' to {path}. Restart Claude Desktop.")
71
+ return
72
+ if client == "cursor":
73
+ path = Path.home() / ".cursor" / "mcp.json" if scope == "user" else Path.cwd() / ".cursor" / "mcp.json"
74
+ _merge_json(path, ["mcpServers", "platformio"], entry)
75
+ print(f"Added 'platformio' to {path}. Reload Cursor's MCP settings.")
76
+ return
77
+ if client == "windsurf":
78
+ path = Path.home() / ".codeium" / "windsurf" / "mcp_config.json"
79
+ _merge_json(path, ["mcpServers", "platformio"], entry)
80
+ print(f"Added 'platformio' to {path}. Restart Windsurf.")
81
+ return
82
+ if client == "codex":
83
+ path = Path.home() / ".codex" / "config.toml"
84
+ block = "\n[mcp_servers.platformio]\ncommand = \"uvx\"\nargs = [" + ", ".join(json.dumps(a) for a in entry["args"]) + "]\n"
85
+ if entry.get("env"):
86
+ block += "[mcp_servers.platformio.env]\n" + "".join(f'{k} = "{v}"\n' for k, v in entry["env"].items())
87
+ existing = path.read_text(encoding="utf-8") if path.exists() else ""
88
+ if "[mcp_servers.platformio]" in existing:
89
+ print(f"{path} already has [mcp_servers.platformio]; edit it by hand if needed:\n{block}")
90
+ return
91
+ path.parent.mkdir(parents=True, exist_ok=True)
92
+ path.write_text(existing.rstrip("\n") + "\n" + block, encoding="utf-8")
93
+ print(f"Appended [mcp_servers.platformio] to {path}. Restart Codex.")
94
+ return
95
+ sys.exit(f"unknown client {client}; choose from {', '.join(CLIENTS)}")
96
+
97
+
98
+ def doctor() -> None:
99
+ from .tools.system import pio_system_info
100
+
101
+ print(json.dumps(pio_system_info(), indent=2))
102
+
103
+
104
+ def main(argv: list[str] | None = None) -> None:
105
+ parser = argparse.ArgumentParser(prog="platformio.mcp", description="MCP server for PlatformIO. With no subcommand it serves over stdio.")
106
+ parser.add_argument("--version", action="version", version=f"platformio.mcp {__version__}")
107
+ sub = parser.add_subparsers(dest="cmd")
108
+ sub.add_parser("serve", help="run the MCP server on stdio (default)")
109
+ p_install = sub.add_parser("install", help="register this server with an MCP client")
110
+ p_install.add_argument("--client", choices=CLIENTS, help="which client to configure")
111
+ for c in CLIENTS:
112
+ p_install.add_argument(f"--{c}", dest="client", action="store_const", const=c, help=f"shortcut for --client {c}")
113
+ p_install.add_argument("--with-platformio", action="store_true", help="bundle PlatformIO Core via the [platformio] extra so nothing else needs installing")
114
+ p_install.add_argument("--policy", choices=("full", "build_only", "read_only"), help="set PLATFORMIO_MCP_POLICY for the server")
115
+ p_install.add_argument("--scope", choices=("user", "project"), default="user", help="where the client stores the config (claude-code, cursor)")
116
+ sub.add_parser("doctor", help="print pio_system_info as JSON")
117
+
118
+ args = parser.parse_args(argv)
119
+ if args.cmd == "install":
120
+ if not args.client:
121
+ p_install.error("choose a client, e.g. --claude-code, --cursor, --claude-desktop, --codex, --windsurf, or --print")
122
+ install(args.client, args.with_platformio, args.policy, args.scope)
123
+ elif args.cmd == "doctor":
124
+ doctor()
125
+ else:
126
+ from .server import serve
127
+
128
+ serve()
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()