inav-mcp 0.3.1__tar.gz → 0.3.2__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.
Files changed (39) hide show
  1. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/PKG-INFO +351 -347
  2. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/README.md +4 -2
  3. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/PKG-INFO +351 -347
  4. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/pyproject.toml +5 -1
  5. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/setup.cfg +4 -4
  6. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/LICENSE +0 -0
  7. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/__init__.py +0 -0
  8. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/cli.py +0 -0
  9. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/connection.py +0 -0
  10. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/knowledge/__init__.py +0 -0
  11. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/knowledge/arming_flags.json +0 -0
  12. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/knowledge/esc_protocols.json +0 -0
  13. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/knowledge/fc_targets.json +0 -0
  14. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/knowledge/modes_reference.json +0 -0
  15. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/modes.py +0 -0
  16. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/msp.py +0 -0
  17. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/profiles.py +0 -0
  18. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/safety.py +0 -0
  19. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/server.py +0 -0
  20. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/state.py +0 -0
  21. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp/troubleshoot.py +0 -0
  22. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/SOURCES.txt +0 -0
  23. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/dependency_links.txt +0 -0
  24. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/entry_points.txt +0 -0
  25. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/requires.txt +0 -0
  26. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/inav_mcp.egg-info/top_level.txt +0 -0
  27. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_cli.py +0 -0
  28. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_cli_batch.py +0 -0
  29. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_cli_props_gate.py +0 -0
  30. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_diagnostics.py +0 -0
  31. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_followups.py +0 -0
  32. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_modes_m4.py +0 -0
  33. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_msp_codec.py +0 -0
  34. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_new_tools_m6.py +0 -0
  35. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_profiles.py +0 -0
  36. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_reconnect.py +0 -0
  37. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_resources_prompts.py +0 -0
  38. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_server_cli.py +0 -0
  39. {inav_mcp-0.3.1 → inav_mcp-0.3.2}/tests/test_status_v2.py +0 -0
@@ -1,347 +1,351 @@
1
- Metadata-Version: 2.4
2
- Name: inav-mcp
3
- Version: 0.3.1
4
- Summary: MCP server for iNAV flight-controller configuration and diagnostics
5
- License-Expression: MIT
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: mcp>=1.0
10
- Requires-Dist: pyserial>=3.5
11
- Provides-Extra: dev
12
- Requires-Dist: pytest>=7.0; extra == "dev"
13
- Dynamic: license-file
14
-
15
- # iNAV MCP Server
16
-
17
- [![CI](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml)
18
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
19
- [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
20
- [![MCP](https://img.shields.io/badge/MCP-stdio-purple.svg)](https://modelcontextprotocol.io)
21
- [![PyPI](https://img.shields.io/pypi/v/inav-mcp.svg)](https://pypi.org/project/inav-mcp/)
22
-
23
- An [MCP](https://modelcontextprotocol.io) server that lets Claude configure,
24
- diagnose, and troubleshoot an **iNAV fixed-wing flight controller** over USB.
25
-
26
- It talks to the FC through a single serial connection, using the iNAV **CLI**
27
- for configuration writes and a small built-in **MSP** codec for live/binary
28
- reads. Every write is dry-run by default, auto-backs-up first, refuses while the
29
- board is armed, and reads back to verify.
30
-
31
- ![Example iNAV MCP session Claude diagnosing why a flight controller won't arm](assets/demo.svg)
32
-
33
- <sub>Illustrative example. Swap in a real recording by replacing `assets/demo.svg` (e.g. ScreenToGif on Windows, or terminalizer / asciinema).</sub>
34
-
35
- > ⚠️ **Safety:** Always remove props from the aircraft before any motor test.
36
- > This tool never switches the FC into MSP-RX mode and never arms the aircraft.
37
-
38
- ---
39
-
40
- ## Requirements
41
-
42
- - Python 3.10+
43
- - An iNAV flight controller (developed against iNAV 8.x/9.x) connected over USB
44
- - The serial port the FC enumerates as (e.g. `COM3` on Windows, `/dev/ttyACM0` on Linux)
45
-
46
- ## Install
47
-
48
- **From PyPI** (once the first release is published):
49
-
50
- ```bash
51
- pip install inav-mcp
52
- ```
53
-
54
- **From source** (for development, or to run the latest unreleased code):
55
-
56
- ```bash
57
- # from the repo root
58
- python -m venv .venv
59
- .venv/Scripts/python -m pip install -e . # Windows
60
- # source .venv/bin/activate && pip install -e . # Linux/macOS
61
- ```
62
-
63
- For development (tests):
64
-
65
- ```bash
66
- .venv/Scripts/python -m pip install -e ".[dev]"
67
- ```
68
-
69
- ### Try it without a flight controller
70
-
71
- You don't need any hardware to confirm the project works — the test suite runs
72
- fully offline (no FC required):
73
-
74
- ```bash
75
- .venv/Scripts/python -m pytest # Windows
76
- # .venv/bin/pytest # Linux/macOS
77
- ```
78
-
79
- All 186 tests should pass. To actually **use** the server, though, you need a
80
- flight controller flashed with **iNAV firmware** (developed against iNAV
81
- 8.x/9.x) connected over USB — without one, the connection tools have nothing to
82
- talk to.
83
-
84
- ## Register with Claude
85
-
86
- Add the server to your Claude (Code or Desktop) MCP config
87
- (`~/.claude/settings.json` or the Claude Desktop config). **Replace the paths
88
- below with wherever you cloned this repo** — the `command` points at the Python
89
- inside your `.venv`, and `cwd` is the repo root.
90
-
91
- Windows:
92
-
93
- ```json
94
- {
95
- "mcpServers": {
96
- "inav": {
97
- "command": "C:\\path\\to\\inav-mcp\\.venv\\Scripts\\python.exe",
98
- "args": ["-m", "inav_mcp.server"],
99
- "cwd": "C:\\path\\to\\inav-mcp"
100
- }
101
- }
102
- }
103
- ```
104
-
105
- Linux / macOS:
106
-
107
- ```json
108
- {
109
- "mcpServers": {
110
- "inav": {
111
- "command": "/path/to/inav-mcp/.venv/bin/python",
112
- "args": ["-m", "inav_mcp.server"],
113
- "cwd": "/path/to/inav-mcp"
114
- }
115
- }
116
- }
117
- ```
118
-
119
- The server speaks MCP over **stdio**. You can also run it directly with the
120
- installed entry point `inav-mcp`.
121
-
122
- ---
123
-
124
- ## Typical workflows
125
-
126
- The server ships **prompts** that walk Claude through the common jobs — just pick one:
127
-
128
- - **`new_fixed_wing_setup`** — gather hardware details → `define_aircraft` → review → apply.
129
- - **`troubleshoot_no_arm`** — decode arming-prevention flags → guided fixes.
130
- - **`configure_modes`** identify switches from live RC suggest a layout assign modes.
131
-
132
- Or drive it conversationally, e.g.:
133
-
134
- > "Connect to my FC on COM3 and tell me why it won't arm."
135
-
136
- > "Set up a 4S flying wing on DSHOT600, then show me the commands before applying."
137
-
138
- ---
139
-
140
- ## Tools (35)
141
-
142
- ### Connection & identity
143
- | Tool | What it does |
144
- |---|---|
145
- | `list_serial_ports()` | List available serial ports. |
146
- | `find_fc(baud=115200, probe_all=False)` | Auto-detect which port has an FC by probing for MSP identity — no guessing the port. |
147
- | `connect(port, baud=115200)` | Open the FC connection, return board identity. |
148
- | `disconnect()` | Close the connection. |
149
- | `board_info()` | FC variant, firmware version, target, API version, sensors. |
150
-
151
- ### Hardware setup
152
- | Tool | What it does |
153
- |---|---|
154
- | `define_aircraft(name, wing_type, esc_protocol, cells, …)` | Offline planner — stores a profile and generates the CLI config plan. |
155
- | `get_aircraft_profile()` | The current declared profile + plan. |
156
- | `apply_aircraft_setup(confirm=False)` | Apply the plan (gated: not armed, auto-backup, read-back verify). On iNAV, applying is inherently save+reboot. |
157
- | `check_config()` | `diff all` + lint against the declared profile + ARM check. |
158
-
159
- ### Flight modes & switches
160
- | Tool | What it does |
161
- |---|---|
162
- | `suggest_mode_layout(skill_level, num_switches, has_gps=False)` | Recommend a fixed-wing switch/mode layout (offline). |
163
- | `set_flight_mode(mode_name, aux_channel, range_low, range_high, confirm=False)` | Assign one mode to an aux range. |
164
- | `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a whole 2/3/6-pos switch in one call. |
165
- | `clear_flight_mode(mode_name, confirm=False)` | Remove a mode's switch assignments. |
166
-
167
- ### Diagnostics
168
- | Tool | What it does |
169
- |---|---|
170
- | `diagnose()` | Full sweep: arming, sensors, RC, battery, GPS → prioritized fixes. |
171
- | `why_wont_it_arm()` | Decode arming-prevention flags into plain reasons + fixes. |
172
- | `read_rc_channels()` | Live RC channel values — flip a switch, see which channel moves. |
173
- | `read_sensors()` | Live attitude, per-sensor health, battery. |
174
- | `get_status()` | MSP status + CLI `status`/`tasks`. |
175
- | `list_flight_modes()` | All modes and their current switch assignments. |
176
- | `check_failsafe()` | Read `failsafe_*` settings, explain the RC-loss procedure, flag risky setups (e.g. RTH without GPS). |
177
-
178
- ### Bench tests & calibration
179
- | Tool | What it does |
180
- |---|---|
181
- | `test_motor(motor, throttle_us=1100, duration_s=2.0, props_removed=False, confirm=False)` | Spin ONE motor briefly. Hard-gated: `props_removed=True` + `confirm=True`, refuses while armed, always auto-stops. (No `test_servo` — iNAV has no live servo override; verify surfaces with the TX sticks + `read_rc_channels()`.) |
182
- | `calibrate_accelerometer(confirm=False)` | Zero-level the accelerometer (board flat + still). Fixes most "not level" arming blocks. |
183
- | `calibrate_magnetometer(confirm=False)` | Calibrate the compass (rotate the craft ~30s). |
184
-
185
- ### Navigation & tuning
186
- | Tool | What it does |
187
- |---|---|
188
- | `read_gps()` | Live GPS fix/sats/position + nav-readiness assessment (read-only). |
189
- | `configure_gps(provider="UBLOX", sbas=None, confirm=False)` | Enable the GPS feature and set provider/SBAS. |
190
- | `set_nav(rth_altitude_m=None, rth_climb_first=None, rth_allow_landing=None, loiter_radius_m=None, confirm=False)` | Set fixed-wing RTH altitude / climb-first / landing / loiter radius. |
191
- | `read_tuning()` | Read fixed-wing PID gains, rates, and filter cutoffs. |
192
- | `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing P/I/D/FF gains for one axis. |
193
-
194
- ### Config management
195
- | Tool | What it does |
196
- |---|---|
197
- | `backup_config(label=None)` | Save `diff all` to a timestamped file under `./backups/`. |
198
- | `list_backups()` | List saved backups (path, time, size, label), newest first. |
199
- | `restore_config(path, confirm=False)` | Replay a saved backup via CLI. |
200
- | `set_failsafe(procedure=None, throttle_us=None, confirm=False)` | Set the RC-loss procedure / throttle (atomic write; FC validates the procedure token). |
201
- | `cli(command, confirm_for_writes=False, props_removed=False)` | Raw CLI escape hatch (ONE command, one reboot). Writes need `confirm_for_writes`; a live `motor` test needs `props_removed=True` and is never saved. |
202
- | `cli_batch(commands, confirm_for_writes=False)` | Run MANY CLI commands in **one** session → **one** reboot. Read-only batch exits without saving; a write batch backs up + saves once (rolls back if any command is rejected). Motor/`save`/`exit` commands refused. |
203
- | `save_and_reboot(confirm=False)` | `save` to EEPROM and reboot (marks the connection stale). |
204
-
205
- ## Resources
206
-
207
- - `inav://modes-reference` iNAV mode glossary with fixed-wing relevance.
208
- - `inav://current-profile` — the declared aircraft profile + generated plan.
209
- - `inav://last-backup` — the most recent `diff all` backup.
210
-
211
- ---
212
-
213
- ## Safety model
214
-
215
- 1. **Props-off gate** — `test_motor()` and any live `motor` command via `cli(...)` require `props_removed=True` (the generic write-confirm cannot bypass it), refuse while the board is armed, and are never saved. `test_motor()` also clamps throttle/duration and always commands the motor back to stop.
216
- 2. **Armed guard** — all writes (and motor tests / calibrations) refuse if the FC reports armed.
217
- 3. **Auto-backup** before every write; the backup path is returned.
218
- 4. **Dry-run by default** — writes return the exact commands; `confirm=True` applies.
219
- 5. **Read-back verify** — after applying, settings are re-read and mismatches flagged.
220
- 6. **`save` = reboot** — `save_and_reboot` warns and marks the connection stale.
221
- 7. **No receiver-mode changes** the FC is never switched to MSP-RX.
222
-
223
- ## Reboot modelwhy batching matters
224
-
225
- On iNAV, **leaving the CLI always reboots the FC** both `save` (persist to
226
- EEPROM) and `exit` (discard changes) trigger a reboot, after which the USB VCP
227
- re-enumerates and we reconnect (~6–8 s, surfaced as `reboot_seconds`). So **every
228
- CLI round-trip costs one reboot**, including read-only ones (`get`, `diff`,
229
- `dump`, `version`). There is no way to read over the CLI without that reboot —
230
- the only lever is to do fewer CLI sessions.
231
-
232
- What this server does to keep reboot churn down:
233
-
234
- - **Reads prefer MSP, which never reboots.** `get_status`, `read_rc_channels`,
235
- `read_sensors`, `read_gps`, `list_flight_modes`, `why_wont_it_arm`, `diagnose`,
236
- and the armed-guard all read structured data over MSP — zero reboots. Only data
237
- that's CLI-only (`diff all`, `get failsafe`, PID/rate/filter `get`s) pays a reboot.
238
- - **Writes are atomic and batch internally.** Each write tool
239
- (`apply_aircraft_setup`, `set_flight_mode`, `assign_switch`, `set_pid`,
240
- `set_failsafe`, `restore_config`, …) opens **one** CLI session: backup apply
241
- all commands save reboot once. Multiple settings = one reboot.
242
- - **`cli_batch()` for ad-hoc runs.** Instead of calling `cli()` in a loop (one
243
- reboot **per** command — the cadence that can knock a board into DFU), pass a
244
- list to `cli_batch()`: one session, one reboot. Read-only batches `exit` without
245
- saving; write batches back up and `save` once (rolling back if any command is
246
- rejected).
247
- - **Resilient reconnect.** After a reboot the reconnect waits a short settle, then
248
- polls with backoff; if the original COM port doesn't return it scans for a
249
- re-enumerated one, and if the board came back in **DFU/bootloader mode** it says
250
- so and tells you to power-cycle (USB unplug/replug) rather than hanging.
251
-
252
- ## How it works
253
-
254
- - **Single serial handle** shared between MSP and CLI modes (`connection.py`),
255
- tracked by a `mode` state machine. Never two handles on one port.
256
- - **CLI-first writes** — the CLI is stable across firmware versions; MSP command
257
- IDs can drift. A thin MSP v1/v2 codec (`msp.py`) handles only the live binary
258
- reads (status, RC, attitude, analog, GPS, sensor health, mode ranges, box maps).
259
- - **Box IDs are resolved at runtime** via `MSP_BOXNAMES` + `MSP_BOXIDS` — never hardcoded.
260
- - **Arming flags** are decoded from `knowledge/arming_flags.json`, calibrated to
261
- iNAV 8.x/9.x bit positions. The table declares its calibrated major versions, and
262
- `connect()` / `board_info()` / `why_wont_it_arm()` / `diagnose()` **warn when the
263
- connected firmware is outside that range** (bit positions shift between majors, so
264
- flag *names* may be mislabelled even though the raw flag value is correct).
265
-
266
- ## Development
267
-
268
- ```bash
269
- .venv/Scripts/python -m pytest # 186 tests, all offline (no FC needed)
270
- ```
271
-
272
- The suite covers the MSP codec round-trips, CLI response parsing, the diagnostic
273
- rule engine, offline profile/command generation, mode-range read-modify-write
274
- logic (against a mock connection), and resource/prompt registration.
275
-
276
- Project layout:
277
-
278
- ```
279
- inav_mcp/
280
- server.py # FastMCP app: all tools, resources, prompts
281
- connection.py # single serial handle, MSP + CLI mode switching
282
- msp.py # MSP v1/v2 codec + parsers
283
- cli.py # CLI response parsing, write-command detection
284
- modes.py # box maps, mode-range read/write, layout planner
285
- profiles.py # AircraftProfile + offline CLI command generator
286
- troubleshoot.py # diagnose() rule engine + arming-flag decode
287
- safety.py # armed guard, backup paths
288
- state.py # connection + profile singletons
289
- knowledge/ # arming_flags / modes_reference / esc_protocols / fc_targets (JSON)
290
- tests/ # offline pytest suite
291
- tools/ # gen_readme_tools.py regenerates the tool reference below
292
- examples/ # flying_wing_quickstart.md end-to-end walkthrough
293
- ```
294
-
295
- Release history is in [CHANGELOG.md](CHANGELOG.md).
296
-
297
- ## Full tool reference
298
-
299
- Complete, signature-accurate list regenerate after changing tools with
300
- `python -m tools.gen_readme_tools` (a test fails if this drifts):
301
-
302
- <!-- TOOLS:AUTOGEN:START -->
303
- _35 tools — auto-generated by `tools/gen_readme_tools.py`; do not edit by hand._
304
-
305
- | Tool | Description |
306
- |---|---|
307
- | `apply_aircraft_setup(confirm=False)` | Apply the declared aircraft profile to the FC, then save and reboot. |
308
- | `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a multi-position switch's detents to flight modes in one call. |
309
- | `backup_config(label=None)` | Save the current FC config to a timestamped backup file. |
310
- | `board_info()` | Read flight-controller identity over MSP. |
311
- | `calibrate_accelerometer(confirm=False)` | Calibrate the accelerometer (zero-level). Fixes most 'not level' / 'accel not |
312
- | `calibrate_magnetometer(confirm=False)` | Calibrate the compass (magnetometer). Only useful if a compass is installed. |
313
- | `check_config()` | Compare the FC's actual configuration against the declared aircraft profile. |
314
- | `check_failsafe()` | Read and explain the failsafe configuration (what happens on RC loss). |
315
- | `clear_flight_mode(mode_name, confirm=False)` | Remove all switch assignments for a flight mode (disables its slots via CLI 'aux'). |
316
- | `cli(command, confirm_for_writes=False, props_removed=False)` | Raw CLI escape hatch run any iNAV CLI command directly. |
317
- | `cli_batch(commands, confirm_for_writes=False)` | Run MANY CLI commands in ONE CLI session a single reboot for the whole batch. |
318
- | `configure_gps(provider='UBLOX', sbas=None, confirm=False)` | Enable the GPS feature and set the receiver provider / SBAS (atomic CLI write). |
319
- | `connect(port, baud=115200)` | Open the serial connection to the FC and return board identity. |
320
- | `define_aircraft(name, wing_type, esc_protocol, cells, fc_target=None, motor_kv=None, motor_poles=14, servo_count=None, notes=None)` | Define the aircraft hardware profile and generate a configuration plan. |
321
- | `diagnose()` | Full diagnostic sweep — the flagship troubleshooter. |
322
- | `disconnect()` | Close the serial connection to the FC. |
323
- | `find_fc(baud=115200, probe_all=False)` | Auto-detect which serial port has a flight controller, so you don't guess. |
324
- | `get_aircraft_profile()` | Return the currently declared aircraft profile. |
325
- | `get_status()` | Read FC status via both MSP and CLI. |
326
- | `list_backups()` | List saved config backups under ./backups/, newest first. No FC needed. |
327
- | `list_flight_modes()` | List all available flight modes and their current switch assignments. |
328
- | `list_serial_ports()` | List all available serial ports. |
329
- | `read_gps()` | Live GPS status: fix type, satellites, position, speed, HDOP + nav-readiness. |
330
- | `read_rc_channels()` | Read live RC channel values via MSP. |
331
- | `read_sensors()` | Read live sensor values: attitude, per-sensor health, and analog (battery). |
332
- | `read_tuning()` | Read fixed-wing PID gains, rates, and key filter cutoffs (via CLI). |
333
- | `restore_config(path, confirm=False)` | Restore FC config by replaying a backup file's CLI commands, then save+reboot. |
334
- | `save_and_reboot(confirm=False)` | Save the running config to EEPROM and reboot the FC. |
335
- | `set_failsafe(procedure=None, throttle_us=None, delay_s=None, off_delay_s=None, confirm=False)` | Set the core failsafe behaviour (atomic CLI write: backup → apply → save+reboot). |
336
- | `set_flight_mode(mode_name, aux_channel, range_low, range_high, confirm=False)` | Assign a flight mode to an aux channel range (read-modify-write via CLI 'aux'). |
337
- | `set_nav(rth_altitude_m=None, rth_climb_first=None, rth_allow_landing=None, loiter_radius_m=None, confirm=False)` | Set core fixed-wing navigation / RTH parameters (atomic CLI write). |
338
- | `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing PID gains for ONE axis (atomic CLI write). |
339
- | `suggest_mode_layout(skill_level='beginner', num_switches=2, has_gps=False)` | Recommend a fixed-wing flight-mode/switch layout. Pure knowledge no FC needed. |
340
- | `test_motor(motor, throttle_us=1100, duration_s=2.0, props_removed=False, confirm=False)` | Spin ONE motor briefly for a bench test (direction / wiring / response). |
341
- | `why_wont_it_arm()` | Decode the FC's arming-prevention flags into plain English. |
342
- <!-- TOOLS:AUTOGEN:END -->
343
-
344
- ## License
345
-
346
- MIT. This project ships its own MSP codec and does **not** import GPL libraries
347
- (uNAVlib / YAMSPy) at runtime, keeping it permissively licensed.
1
+ Metadata-Version: 2.4
2
+ Name: inav-mcp
3
+ Version: 0.3.2
4
+ Summary: MCP server for iNAV flight-controller configuration and diagnostics
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/starlordz12/inav-mcp
7
+ Project-URL: Repository, https://github.com/starlordz12/inav-mcp
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: mcp>=1.0
12
+ Requires-Dist: pyserial>=3.5
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Dynamic: license-file
16
+
17
+ <!-- mcp-name: io.github.starlordz12/inav-mcp -->
18
+
19
+ # iNAV MCP Server
20
+
21
+ [![CI](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml)
22
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
23
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
24
+ [![MCP](https://img.shields.io/badge/MCP-stdio-purple.svg)](https://modelcontextprotocol.io)
25
+ [![PyPI](https://img.shields.io/pypi/v/inav-mcp.svg)](https://pypi.org/project/inav-mcp/)
26
+
27
+ An [MCP](https://modelcontextprotocol.io) server that lets Claude configure,
28
+ diagnose, and troubleshoot an **iNAV fixed-wing flight controller** over USB.
29
+
30
+ It talks to the FC through a single serial connection, using the iNAV **CLI**
31
+ for configuration writes and a small built-in **MSP** codec for live/binary
32
+ reads. Every write is dry-run by default, auto-backs-up first, refuses while the
33
+ board is armed, and reads back to verify.
34
+
35
+ ![iNAV MCP session Claude finding the flight controller and reading its status over USB](https://raw.githubusercontent.com/starlordz12/inav-mcp/master/assets/demo.gif)
36
+
37
+ <sub>Recorded against a live iNAV 6.1.0 flight controller over USB.</sub>
38
+
39
+ > ⚠️ **Safety:** Always remove props from the aircraft before any motor test.
40
+ > This tool never switches the FC into MSP-RX mode and never arms the aircraft.
41
+
42
+ ---
43
+
44
+ ## Requirements
45
+
46
+ - Python 3.10+
47
+ - An iNAV flight controller (developed against iNAV 8.x/9.x) connected over USB
48
+ - The serial port the FC enumerates as (e.g. `COM3` on Windows, `/dev/ttyACM0` on Linux)
49
+
50
+ ## Install
51
+
52
+ **From PyPI** (once the first release is published):
53
+
54
+ ```bash
55
+ pip install inav-mcp
56
+ ```
57
+
58
+ **From source** (for development, or to run the latest unreleased code):
59
+
60
+ ```bash
61
+ # from the repo root
62
+ python -m venv .venv
63
+ .venv/Scripts/python -m pip install -e . # Windows
64
+ # source .venv/bin/activate && pip install -e . # Linux/macOS
65
+ ```
66
+
67
+ For development (tests):
68
+
69
+ ```bash
70
+ .venv/Scripts/python -m pip install -e ".[dev]"
71
+ ```
72
+
73
+ ### Try it without a flight controller
74
+
75
+ You don't need any hardware to confirm the project works — the test suite runs
76
+ fully offline (no FC required):
77
+
78
+ ```bash
79
+ .venv/Scripts/python -m pytest # Windows
80
+ # .venv/bin/pytest # Linux/macOS
81
+ ```
82
+
83
+ All 186 tests should pass. To actually **use** the server, though, you need a
84
+ flight controller flashed with **iNAV firmware** (developed against iNAV
85
+ 8.x/9.x) connected over USB — without one, the connection tools have nothing to
86
+ talk to.
87
+
88
+ ## Register with Claude
89
+
90
+ Add the server to your Claude (Code or Desktop) MCP config
91
+ (`~/.claude/settings.json` or the Claude Desktop config). **Replace the paths
92
+ below with wherever you cloned this repo** — the `command` points at the Python
93
+ inside your `.venv`, and `cwd` is the repo root.
94
+
95
+ Windows:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "inav": {
101
+ "command": "C:\\path\\to\\inav-mcp\\.venv\\Scripts\\python.exe",
102
+ "args": ["-m", "inav_mcp.server"],
103
+ "cwd": "C:\\path\\to\\inav-mcp"
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ Linux / macOS:
110
+
111
+ ```json
112
+ {
113
+ "mcpServers": {
114
+ "inav": {
115
+ "command": "/path/to/inav-mcp/.venv/bin/python",
116
+ "args": ["-m", "inav_mcp.server"],
117
+ "cwd": "/path/to/inav-mcp"
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ The server speaks MCP over **stdio**. You can also run it directly with the
124
+ installed entry point `inav-mcp`.
125
+
126
+ ---
127
+
128
+ ## Typical workflows
129
+
130
+ The server ships **prompts** that walk Claude through the common jobs just pick one:
131
+
132
+ - **`new_fixed_wing_setup`** gather hardware details → `define_aircraft` → review → apply.
133
+ - **`troubleshoot_no_arm`** — decode arming-prevention flags → guided fixes.
134
+ - **`configure_modes`** identify switches from live RC suggest a layout assign modes.
135
+
136
+ Or drive it conversationally, e.g.:
137
+
138
+ > "Connect to my FC on COM3 and tell me why it won't arm."
139
+
140
+ > "Set up a 4S flying wing on DSHOT600, then show me the commands before applying."
141
+
142
+ ---
143
+
144
+ ## Tools (35)
145
+
146
+ ### Connection & identity
147
+ | Tool | What it does |
148
+ |---|---|
149
+ | `list_serial_ports()` | List available serial ports. |
150
+ | `find_fc(baud=115200, probe_all=False)` | Auto-detect which port has an FC by probing for MSP identity — no guessing the port. |
151
+ | `connect(port, baud=115200)` | Open the FC connection, return board identity. |
152
+ | `disconnect()` | Close the connection. |
153
+ | `board_info()` | FC variant, firmware version, target, API version, sensors. |
154
+
155
+ ### Hardware setup
156
+ | Tool | What it does |
157
+ |---|---|
158
+ | `define_aircraft(name, wing_type, esc_protocol, cells, …)` | Offline planner — stores a profile and generates the CLI config plan. |
159
+ | `get_aircraft_profile()` | The current declared profile + plan. |
160
+ | `apply_aircraft_setup(confirm=False)` | Apply the plan (gated: not armed, auto-backup, read-back verify). On iNAV, applying is inherently save+reboot. |
161
+ | `check_config()` | `diff all` + lint against the declared profile + ARM check. |
162
+
163
+ ### Flight modes & switches
164
+ | Tool | What it does |
165
+ |---|---|
166
+ | `suggest_mode_layout(skill_level, num_switches, has_gps=False)` | Recommend a fixed-wing switch/mode layout (offline). |
167
+ | `set_flight_mode(mode_name, aux_channel, range_low, range_high, confirm=False)` | Assign one mode to an aux range. |
168
+ | `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a whole 2/3/6-pos switch in one call. |
169
+ | `clear_flight_mode(mode_name, confirm=False)` | Remove a mode's switch assignments. |
170
+
171
+ ### Diagnostics
172
+ | Tool | What it does |
173
+ |---|---|
174
+ | `diagnose()` | Full sweep: arming, sensors, RC, battery, GPS → prioritized fixes. |
175
+ | `why_wont_it_arm()` | Decode arming-prevention flags into plain reasons + fixes. |
176
+ | `read_rc_channels()` | Live RC channel values flip a switch, see which channel moves. |
177
+ | `read_sensors()` | Live attitude, per-sensor health, battery. |
178
+ | `get_status()` | MSP status + CLI `status`/`tasks`. |
179
+ | `list_flight_modes()` | All modes and their current switch assignments. |
180
+ | `check_failsafe()` | Read `failsafe_*` settings, explain the RC-loss procedure, flag risky setups (e.g. RTH without GPS). |
181
+
182
+ ### Bench tests & calibration
183
+ | Tool | What it does |
184
+ |---|---|
185
+ | `test_motor(motor, throttle_us=1100, duration_s=2.0, props_removed=False, confirm=False)` | Spin ONE motor briefly. Hard-gated: `props_removed=True` + `confirm=True`, refuses while armed, always auto-stops. (No `test_servo` — iNAV has no live servo override; verify surfaces with the TX sticks + `read_rc_channels()`.) |
186
+ | `calibrate_accelerometer(confirm=False)` | Zero-level the accelerometer (board flat + still). Fixes most "not level" arming blocks. |
187
+ | `calibrate_magnetometer(confirm=False)` | Calibrate the compass (rotate the craft ~30s). |
188
+
189
+ ### Navigation & tuning
190
+ | Tool | What it does |
191
+ |---|---|
192
+ | `read_gps()` | Live GPS fix/sats/position + nav-readiness assessment (read-only). |
193
+ | `configure_gps(provider="UBLOX", sbas=None, confirm=False)` | Enable the GPS feature and set provider/SBAS. |
194
+ | `set_nav(rth_altitude_m=None, rth_climb_first=None, rth_allow_landing=None, loiter_radius_m=None, confirm=False)` | Set fixed-wing RTH altitude / climb-first / landing / loiter radius. |
195
+ | `read_tuning()` | Read fixed-wing PID gains, rates, and filter cutoffs. |
196
+ | `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing P/I/D/FF gains for one axis. |
197
+
198
+ ### Config management
199
+ | Tool | What it does |
200
+ |---|---|
201
+ | `backup_config(label=None)` | Save `diff all` to a timestamped file under `./backups/`. |
202
+ | `list_backups()` | List saved backups (path, time, size, label), newest first. |
203
+ | `restore_config(path, confirm=False)` | Replay a saved backup via CLI. |
204
+ | `set_failsafe(procedure=None, throttle_us=None, confirm=False)` | Set the RC-loss procedure / throttle (atomic write; FC validates the procedure token). |
205
+ | `cli(command, confirm_for_writes=False, props_removed=False)` | Raw CLI escape hatch (ONE command, one reboot). Writes need `confirm_for_writes`; a live `motor` test needs `props_removed=True` and is never saved. |
206
+ | `cli_batch(commands, confirm_for_writes=False)` | Run MANY CLI commands in **one** session → **one** reboot. Read-only batch exits without saving; a write batch backs up + saves once (rolls back if any command is rejected). Motor/`save`/`exit` commands refused. |
207
+ | `save_and_reboot(confirm=False)` | `save` to EEPROM and reboot (marks the connection stale). |
208
+
209
+ ## Resources
210
+
211
+ - `inav://modes-reference` — iNAV mode glossary with fixed-wing relevance.
212
+ - `inav://current-profile` — the declared aircraft profile + generated plan.
213
+ - `inav://last-backup` — the most recent `diff all` backup.
214
+
215
+ ---
216
+
217
+ ## Safety model
218
+
219
+ 1. **Props-off gate** — `test_motor()` and any live `motor` command via `cli(...)` require `props_removed=True` (the generic write-confirm cannot bypass it), refuse while the board is armed, and are never saved. `test_motor()` also clamps throttle/duration and always commands the motor back to stop.
220
+ 2. **Armed guard** — all writes (and motor tests / calibrations) refuse if the FC reports armed.
221
+ 3. **Auto-backup** before every write; the backup path is returned.
222
+ 4. **Dry-run by default** — writes return the exact commands; `confirm=True` applies.
223
+ 5. **Read-back verify**after applying, settings are re-read and mismatches flagged.
224
+ 6. **`save` = reboot** — `save_and_reboot` warns and marks the connection stale.
225
+ 7. **No receiver-mode changes** the FC is never switched to MSP-RX.
226
+
227
+ ## Reboot model why batching matters
228
+
229
+ On iNAV, **leaving the CLI always reboots the FC** both `save` (persist to
230
+ EEPROM) and `exit` (discard changes) trigger a reboot, after which the USB VCP
231
+ re-enumerates and we reconnect (~6–8 s, surfaced as `reboot_seconds`). So **every
232
+ CLI round-trip costs one reboot**, including read-only ones (`get`, `diff`,
233
+ `dump`, `version`). There is no way to read over the CLI without that reboot —
234
+ the only lever is to do fewer CLI sessions.
235
+
236
+ What this server does to keep reboot churn down:
237
+
238
+ - **Reads prefer MSP, which never reboots.** `get_status`, `read_rc_channels`,
239
+ `read_sensors`, `read_gps`, `list_flight_modes`, `why_wont_it_arm`, `diagnose`,
240
+ and the armed-guard all read structured data over MSP — zero reboots. Only data
241
+ that's CLI-only (`diff all`, `get failsafe`, PID/rate/filter `get`s) pays a reboot.
242
+ - **Writes are atomic and batch internally.** Each write tool
243
+ (`apply_aircraft_setup`, `set_flight_mode`, `assign_switch`, `set_pid`,
244
+ `set_failsafe`, `restore_config`, ) opens **one** CLI session: backup apply
245
+ all commands save reboot once. Multiple settings = one reboot.
246
+ - **`cli_batch()` for ad-hoc runs.** Instead of calling `cli()` in a loop (one
247
+ reboot **per** command the cadence that can knock a board into DFU), pass a
248
+ list to `cli_batch()`: one session, one reboot. Read-only batches `exit` without
249
+ saving; write batches back up and `save` once (rolling back if any command is
250
+ rejected).
251
+ - **Resilient reconnect.** After a reboot the reconnect waits a short settle, then
252
+ polls with backoff; if the original COM port doesn't return it scans for a
253
+ re-enumerated one, and if the board came back in **DFU/bootloader mode** it says
254
+ so and tells you to power-cycle (USB unplug/replug) rather than hanging.
255
+
256
+ ## How it works
257
+
258
+ - **Single serial handle** shared between MSP and CLI modes (`connection.py`),
259
+ tracked by a `mode` state machine. Never two handles on one port.
260
+ - **CLI-first writes** the CLI is stable across firmware versions; MSP command
261
+ IDs can drift. A thin MSP v1/v2 codec (`msp.py`) handles only the live binary
262
+ reads (status, RC, attitude, analog, GPS, sensor health, mode ranges, box maps).
263
+ - **Box IDs are resolved at runtime** via `MSP_BOXNAMES` + `MSP_BOXIDS` never hardcoded.
264
+ - **Arming flags** are decoded from `knowledge/arming_flags.json`, calibrated to
265
+ iNAV 8.x/9.x bit positions. The table declares its calibrated major versions, and
266
+ `connect()` / `board_info()` / `why_wont_it_arm()` / `diagnose()` **warn when the
267
+ connected firmware is outside that range** (bit positions shift between majors, so
268
+ flag *names* may be mislabelled even though the raw flag value is correct).
269
+
270
+ ## Development
271
+
272
+ ```bash
273
+ .venv/Scripts/python -m pytest # 186 tests, all offline (no FC needed)
274
+ ```
275
+
276
+ The suite covers the MSP codec round-trips, CLI response parsing, the diagnostic
277
+ rule engine, offline profile/command generation, mode-range read-modify-write
278
+ logic (against a mock connection), and resource/prompt registration.
279
+
280
+ Project layout:
281
+
282
+ ```
283
+ inav_mcp/
284
+ server.py # FastMCP app: all tools, resources, prompts
285
+ connection.py # single serial handle, MSP + CLI mode switching
286
+ msp.py # MSP v1/v2 codec + parsers
287
+ cli.py # CLI response parsing, write-command detection
288
+ modes.py # box maps, mode-range read/write, layout planner
289
+ profiles.py # AircraftProfile + offline CLI command generator
290
+ troubleshoot.py # diagnose() rule engine + arming-flag decode
291
+ safety.py # armed guard, backup paths
292
+ state.py # connection + profile singletons
293
+ knowledge/ # arming_flags / modes_reference / esc_protocols / fc_targets (JSON)
294
+ tests/ # offline pytest suite
295
+ tools/ # gen_readme_tools.py regenerates the tool reference below
296
+ examples/ # flying_wing_quickstart.md — end-to-end walkthrough
297
+ ```
298
+
299
+ Release history is in [CHANGELOG.md](CHANGELOG.md).
300
+
301
+ ## Full tool reference
302
+
303
+ Complete, signature-accurate list regenerate after changing tools with
304
+ `python -m tools.gen_readme_tools` (a test fails if this drifts):
305
+
306
+ <!-- TOOLS:AUTOGEN:START -->
307
+ _35 tools auto-generated by `tools/gen_readme_tools.py`; do not edit by hand._
308
+
309
+ | Tool | Description |
310
+ |---|---|
311
+ | `apply_aircraft_setup(confirm=False)` | Apply the declared aircraft profile to the FC, then save and reboot. |
312
+ | `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a multi-position switch's detents to flight modes in one call. |
313
+ | `backup_config(label=None)` | Save the current FC config to a timestamped backup file. |
314
+ | `board_info()` | Read flight-controller identity over MSP. |
315
+ | `calibrate_accelerometer(confirm=False)` | Calibrate the accelerometer (zero-level). Fixes most 'not level' / 'accel not |
316
+ | `calibrate_magnetometer(confirm=False)` | Calibrate the compass (magnetometer). Only useful if a compass is installed. |
317
+ | `check_config()` | Compare the FC's actual configuration against the declared aircraft profile. |
318
+ | `check_failsafe()` | Read and explain the failsafe configuration (what happens on RC loss). |
319
+ | `clear_flight_mode(mode_name, confirm=False)` | Remove all switch assignments for a flight mode (disables its slots via CLI 'aux'). |
320
+ | `cli(command, confirm_for_writes=False, props_removed=False)` | Raw CLI escape hatch run any iNAV CLI command directly. |
321
+ | `cli_batch(commands, confirm_for_writes=False)` | Run MANY CLI commands in ONE CLI session a single reboot for the whole batch. |
322
+ | `configure_gps(provider='UBLOX', sbas=None, confirm=False)` | Enable the GPS feature and set the receiver provider / SBAS (atomic CLI write). |
323
+ | `connect(port, baud=115200)` | Open the serial connection to the FC and return board identity. |
324
+ | `define_aircraft(name, wing_type, esc_protocol, cells, fc_target=None, motor_kv=None, motor_poles=14, servo_count=None, notes=None)` | Define the aircraft hardware profile and generate a configuration plan. |
325
+ | `diagnose()` | Full diagnostic sweep the flagship troubleshooter. |
326
+ | `disconnect()` | Close the serial connection to the FC. |
327
+ | `find_fc(baud=115200, probe_all=False)` | Auto-detect which serial port has a flight controller, so you don't guess. |
328
+ | `get_aircraft_profile()` | Return the currently declared aircraft profile. |
329
+ | `get_status()` | Read FC status via both MSP and CLI. |
330
+ | `list_backups()` | List saved config backups under ./backups/, newest first. No FC needed. |
331
+ | `list_flight_modes()` | List all available flight modes and their current switch assignments. |
332
+ | `list_serial_ports()` | List all available serial ports. |
333
+ | `read_gps()` | Live GPS status: fix type, satellites, position, speed, HDOP + nav-readiness. |
334
+ | `read_rc_channels()` | Read live RC channel values via MSP. |
335
+ | `read_sensors()` | Read live sensor values: attitude, per-sensor health, and analog (battery). |
336
+ | `read_tuning()` | Read fixed-wing PID gains, rates, and key filter cutoffs (via CLI). |
337
+ | `restore_config(path, confirm=False)` | Restore FC config by replaying a backup file's CLI commands, then save+reboot. |
338
+ | `save_and_reboot(confirm=False)` | Save the running config to EEPROM and reboot the FC. |
339
+ | `set_failsafe(procedure=None, throttle_us=None, delay_s=None, off_delay_s=None, confirm=False)` | Set the core failsafe behaviour (atomic CLI write: backup apply → save+reboot). |
340
+ | `set_flight_mode(mode_name, aux_channel, range_low, range_high, confirm=False)` | Assign a flight mode to an aux channel range (read-modify-write via CLI 'aux'). |
341
+ | `set_nav(rth_altitude_m=None, rth_climb_first=None, rth_allow_landing=None, loiter_radius_m=None, confirm=False)` | Set core fixed-wing navigation / RTH parameters (atomic CLI write). |
342
+ | `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing PID gains for ONE axis (atomic CLI write). |
343
+ | `suggest_mode_layout(skill_level='beginner', num_switches=2, has_gps=False)` | Recommend a fixed-wing flight-mode/switch layout. Pure knowledge — no FC needed. |
344
+ | `test_motor(motor, throttle_us=1100, duration_s=2.0, props_removed=False, confirm=False)` | Spin ONE motor briefly for a bench test (direction / wiring / response). |
345
+ | `why_wont_it_arm()` | Decode the FC's arming-prevention flags into plain English. |
346
+ <!-- TOOLS:AUTOGEN:END -->
347
+
348
+ ## License
349
+
350
+ MIT. This project ships its own MSP codec and does **not** import GPL libraries
351
+ (uNAVlib / YAMSPy) at runtime, keeping it permissively licensed.