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.
- platformio_mcp-0.1.0/PKG-INFO +213 -0
- platformio_mcp-0.1.0/README.md +191 -0
- platformio_mcp-0.1.0/pyproject.toml +52 -0
- platformio_mcp-0.1.0/src/platformio_mcp/__init__.py +3 -0
- platformio_mcp-0.1.0/src/platformio_mcp/cli.py +132 -0
- platformio_mcp-0.1.0/src/platformio_mcp/core.py +113 -0
- platformio_mcp-0.1.0/src/platformio_mcp/monitor.py +211 -0
- platformio_mcp-0.1.0/src/platformio_mcp/parsers.py +186 -0
- platformio_mcp-0.1.0/src/platformio_mcp/pio.py +201 -0
- platformio_mcp-0.1.0/src/platformio_mcp/server.py +41 -0
- platformio_mcp-0.1.0/src/platformio_mcp/toolchain.py +326 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/__init__.py +8 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/analysis.py +283 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/build.py +130 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/devices.py +147 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/packages.py +103 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/project.py +194 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/quality.py +134 -0
- platformio_mcp-0.1.0/src/platformio_mcp/tools/system.py +47 -0
|
@@ -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 & 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 & 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,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()
|