{
  "markdown": "<!-- mcp-name: io.github.starlordz12/inav-mcp -->\n\n# iNAV MCP Server\n\n[![CI](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/starlordz12/inav-mcp/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)\n[![MCP](https://img.shields.io/badge/MCP-stdio-purple.svg)](https://modelcontextprotocol.io)\n[![PyPI](https://img.shields.io/pypi/v/inav-mcp.svg)](https://pypi.org/project/inav-mcp/)\n\nAn [MCP](https://modelcontextprotocol.io) server that lets Claude configure,\ndiagnose, and troubleshoot an **iNAV flight controller** over USB — built\n**primarily for flying wings** (elevon FPV wings like the TBS Chupito and\nMojito), and also for conventional fixed-wing planes.\n\nIt talks to the FC through a single serial connection, using the iNAV **CLI**\nfor configuration writes and a small built-in **MSP** codec for live/binary\nreads. Every write is dry-run by default, auto-backs-up first, refuses while the\nboard is armed, and reads back to verify.\n\n![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)\n\n<sub>Recorded against a live iNAV 6.1.0 flight controller over USB.</sub>\n\n> ⚠️ **Safety:** Always remove props from the aircraft before any motor test.\n> This tool never switches the FC into MSP-RX mode and never arms the aircraft.\n\n---\n\n## Requirements\n\n- Python 3.10+\n- An iNAV flight controller (developed against iNAV 8.x/9.x) connected over USB\n- The serial port the FC enumerates as (e.g. `COM3` on Windows, `/dev/ttyACM0` on Linux)\n\n## Install\n\n**From PyPI** (once the first release is published):\n\n```bash\npip install inav-mcp\n```\n\n**From source** (for development, or to run the latest unreleased code):\n\n```bash\n# from the repo root\npython -m venv .venv\n.venv/Scripts/python -m pip install -e .          # Windows\n# source .venv/bin/activate && pip install -e .    # Linux/macOS\n```\n\nFor development (tests):\n\n```bash\n.venv/Scripts/python -m pip install -e \".[dev]\"\n```\n\n### Try it without a flight controller\n\nYou don't need any hardware to confirm the project works — the test suite runs\nfully offline (no FC required):\n\n```bash\n.venv/Scripts/python -m pytest        # Windows\n# .venv/bin/pytest                     # Linux/macOS\n```\n\nAll 186 tests should pass. To actually **use** the server, though, you need a\nflight controller flashed with **iNAV firmware** (developed against iNAV\n8.x/9.x) connected over USB — without one, the connection tools have nothing to\ntalk to.\n\n## Register with Claude\n\nAdd the server to your Claude (Code or Desktop) MCP config\n(`~/.claude/settings.json` or the Claude Desktop config). **Replace the paths\nbelow with wherever you cloned this repo** — the `command` points at the Python\ninside your `.venv`, and `cwd` is the repo root.\n\nWindows:\n\n```json\n{\n  \"mcpServers\": {\n    \"inav\": {\n      \"command\": \"C:\\\\path\\\\to\\\\inav-mcp\\\\.venv\\\\Scripts\\\\python.exe\",\n      \"args\": [\"-m\", \"inav_mcp.server\"],\n      \"cwd\": \"C:\\\\path\\\\to\\\\inav-mcp\"\n    }\n  }\n}\n```\n\nLinux / macOS:\n\n```json\n{\n  \"mcpServers\": {\n    \"inav\": {\n      \"command\": \"/path/to/inav-mcp/.venv/bin/python\",\n      \"args\": [\"-m\", \"inav_mcp.server\"],\n      \"cwd\": \"/path/to/inav-mcp\"\n    }\n  }\n}\n```\n\nThe server speaks MCP over **stdio**. You can also run it directly with the\ninstalled entry point `inav-mcp`.\n\n---\n\n## Typical workflows\n\nThe server ships **prompts** that walk Claude through the common jobs — just pick one:\n\n- **`new_fixed_wing_setup`** — gather hardware details → `define_aircraft` → review → apply.\n- **`troubleshoot_no_arm`** — decode arming-prevention flags → guided fixes.\n- **`configure_modes`** — identify switches from live RC → suggest a layout → assign modes.\n\nOr drive it conversationally, e.g.:\n\n> \"Connect to my FC on COM3 and tell me why it won't arm.\"\n\n> \"Set up a 4S flying wing on DSHOT600, then show me the commands before applying.\"\n\n---\n\n## Tools (35)\n\n### Connection & identity\n| Tool | What it does |\n|---|---|\n| `list_serial_ports()` | List available serial ports. |\n| `find_fc(baud=115200, probe_all=False)` | Auto-detect which port has an FC by probing for MSP identity — no guessing the port. |\n| `connect(port, baud=115200)` | Open the FC connection, return board identity. |\n| `disconnect()` | Close the connection. |\n| `board_info()` | FC variant, firmware version, target, API version, sensors. |\n\n### Hardware setup\n| Tool | What it does |\n|---|---|\n| `define_aircraft(name, wing_type, esc_protocol, cells, …)` | Offline planner — stores a profile and generates the CLI config plan. |\n| `get_aircraft_profile()` | The current declared profile + plan. |\n| `apply_aircraft_setup(confirm=False)` | Apply the plan (gated: not armed, auto-backup, read-back verify). On iNAV, applying is inherently save+reboot. |\n| `check_config()` | `diff all` + lint against the declared profile + ARM check. |\n\n### Flight modes & switches\n| Tool | What it does |\n|---|---|\n| `suggest_mode_layout(skill_level, num_switches, has_gps=False)` | Recommend a fixed-wing switch/mode layout (offline). |\n| `set_flight_mode(mode_name, aux_channel, range_low, range_high, confirm=False)` | Assign one mode to an aux range. |\n| `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a whole 2/3/6-pos switch in one call. |\n| `clear_flight_mode(mode_name, confirm=False)` | Remove a mode's switch assignments. |\n\n### Diagnostics\n| Tool | What it does |\n|---|---|\n| `diagnose()` | Full sweep: arming, sensors, RC, battery, GPS → prioritized fixes. |\n| `why_wont_it_arm()` | Decode arming-prevention flags into plain reasons + fixes. |\n| `read_rc_channels()` | Live RC channel values — flip a switch, see which channel moves. |\n| `read_sensors()` | Live attitude, per-sensor health, battery. |\n| `get_status()` | MSP status + CLI `status`/`tasks`. |\n| `list_flight_modes()` | All modes and their current switch assignments. |\n| `check_failsafe()` | Read `failsafe_*` settings, explain the RC-loss procedure, flag risky setups (e.g. RTH without GPS). |\n\n### Bench tests & calibration\n| Tool | What it does |\n|---|---|\n| `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()`.) |\n| `calibrate_accelerometer(confirm=False)` | Zero-level the accelerometer (board flat + still). Fixes most \"not level\" arming blocks. |\n| `calibrate_magnetometer(confirm=False)` | Calibrate the compass (rotate the craft ~30s). |\n\n### Navigation & tuning\n| Tool | What it does |\n|---|---|\n| `read_gps()` | Live GPS fix/sats/position + nav-readiness assessment (read-only). |\n| `configure_gps(provider=\"UBLOX\", sbas=None, confirm=False)` | Enable the GPS feature and set provider/SBAS. |\n| `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. |\n| `read_tuning()` | Read fixed-wing PID gains, rates, and filter cutoffs. |\n| `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing P/I/D/FF gains for one axis. |\n\n### Config management\n| Tool | What it does |\n|---|---|\n| `backup_config(label=None)` | Save `diff all` to a timestamped file under `./backups/`. |\n| `list_backups()` | List saved backups (path, time, size, label), newest first. |\n| `restore_config(path, confirm=False)` | Replay a saved backup via CLI. |\n| `set_failsafe(procedure=None, throttle_us=None, confirm=False)` | Set the RC-loss procedure / throttle (atomic write; FC validates the procedure token). |\n| `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. |\n| `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. |\n| `save_and_reboot(confirm=False)` | `save` to EEPROM and reboot (marks the connection stale). |\n\n## Resources\n\n- `inav://modes-reference` — iNAV mode glossary with fixed-wing relevance.\n- `inav://current-profile` — the declared aircraft profile + generated plan.\n- `inav://last-backup` — the most recent `diff all` backup.\n\n---\n\n## Safety model\n\n1. **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.\n2. **Armed guard** — all writes (and motor tests / calibrations) refuse if the FC reports armed.\n3. **Auto-backup** before every write; the backup path is returned.\n4. **Dry-run by default** — writes return the exact commands; `confirm=True` applies.\n5. **Read-back verify** — after applying, settings are re-read and mismatches flagged.\n6. **`save` = reboot** — `save_and_reboot` warns and marks the connection stale.\n7. **No receiver-mode changes** — the FC is never switched to MSP-RX.\n\n## Reboot model — why batching matters\n\nOn iNAV, **leaving the CLI always reboots the FC** — both `save` (persist to\nEEPROM) and `exit` (discard changes) trigger a reboot, after which the USB VCP\nre-enumerates and we reconnect (~6–8 s, surfaced as `reboot_seconds`). So **every\nCLI round-trip costs one reboot**, including read-only ones (`get`, `diff`,\n`dump`, `version`). There is no way to read over the CLI without that reboot —\nthe only lever is to do fewer CLI sessions.\n\nWhat this server does to keep reboot churn down:\n\n- **Reads prefer MSP, which never reboots.** `get_status`, `read_rc_channels`,\n  `read_sensors`, `read_gps`, `list_flight_modes`, `why_wont_it_arm`, `diagnose`,\n  and the armed-guard all read structured data over MSP — zero reboots. Only data\n  that's CLI-only (`diff all`, `get failsafe`, PID/rate/filter `get`s) pays a reboot.\n- **Writes are atomic and batch internally.** Each write tool\n  (`apply_aircraft_setup`, `set_flight_mode`, `assign_switch`, `set_pid`,\n  `set_failsafe`, `restore_config`, …) opens **one** CLI session: backup → apply\n  all commands → save → reboot once. Multiple settings = one reboot.\n- **`cli_batch()` for ad-hoc runs.** Instead of calling `cli()` in a loop (one\n  reboot **per** command — the cadence that can knock a board into DFU), pass a\n  list to `cli_batch()`: one session, one reboot. Read-only batches `exit` without\n  saving; write batches back up and `save` once (rolling back if any command is\n  rejected).\n- **Resilient reconnect.** After a reboot the reconnect waits a short settle, then\n  polls with backoff; if the original COM port doesn't return it scans for a\n  re-enumerated one, and if the board came back in **DFU/bootloader mode** it says\n  so and tells you to power-cycle (USB unplug/replug) rather than hanging.\n\n## How it works\n\n- **Single serial handle** shared between MSP and CLI modes (`connection.py`),\n  tracked by a `mode` state machine. Never two handles on one port.\n- **CLI-first writes** — the CLI is stable across firmware versions; MSP command\n  IDs can drift. A thin MSP v1/v2 codec (`msp.py`) handles only the live binary\n  reads (status, RC, attitude, analog, GPS, sensor health, mode ranges, box maps).\n- **Box IDs are resolved at runtime** via `MSP_BOXNAMES` + `MSP_BOXIDS` — never hardcoded.\n- **Arming flags** are decoded from `knowledge/arming_flags.json`, calibrated to\n  iNAV 8.x/9.x bit positions. The table declares its calibrated major versions, and\n  `connect()` / `board_info()` / `why_wont_it_arm()` / `diagnose()` **warn when the\n  connected firmware is outside that range** (bit positions shift between majors, so\n  flag *names* may be mislabelled even though the raw flag value is correct).\n\n## Development\n\n```bash\n.venv/Scripts/python -m pytest          # 186 tests, all offline (no FC needed)\n```\n\nThe suite covers the MSP codec round-trips, CLI response parsing, the diagnostic\nrule engine, offline profile/command generation, mode-range read-modify-write\nlogic (against a mock connection), and resource/prompt registration.\n\nProject layout:\n\n```\ninav_mcp/\n  server.py          # FastMCP app: all tools, resources, prompts\n  connection.py      # single serial handle, MSP + CLI mode switching\n  msp.py             # MSP v1/v2 codec + parsers\n  cli.py             # CLI response parsing, write-command detection\n  modes.py           # box maps, mode-range read/write, layout planner\n  profiles.py        # AircraftProfile + offline CLI command generator\n  troubleshoot.py    # diagnose() rule engine + arming-flag decode\n  safety.py          # armed guard, backup paths\n  state.py           # connection + profile singletons\n  knowledge/         # arming_flags / modes_reference / esc_protocols / fc_targets (JSON)\ntests/               # offline pytest suite\ntools/               # gen_readme_tools.py — regenerates the tool reference below\nexamples/            # flying_wing_quickstart.md — end-to-end walkthrough\n```\n\nRelease history is in [CHANGELOG.md](CHANGELOG.md).\n\n## Full tool reference\n\nComplete, signature-accurate list — regenerate after changing tools with\n`python -m tools.gen_readme_tools` (a test fails if this drifts):\n\n<!-- TOOLS:AUTOGEN:START -->\n_35 tools — auto-generated by `tools/gen_readme_tools.py`; do not edit by hand._\n\n| Tool | Description |\n|---|---|\n| `apply_aircraft_setup(confirm=False)` | Apply the declared aircraft profile to the FC, then save and reboot. |\n| `assign_switch(switch_channel, switch_positions, mode_per_position, confirm=False)` | Map a multi-position switch's detents to flight modes in one call. |\n| `backup_config(label=None)` | Save the current FC config to a timestamped backup file. |\n| `board_info()` | Read flight-controller identity over MSP. |\n| `calibrate_accelerometer(confirm=False)` | Calibrate the accelerometer (zero-level). Fixes most 'not level' / 'accel not |\n| `calibrate_magnetometer(confirm=False)` | Calibrate the compass (magnetometer). Only useful if a compass is installed. |\n| `check_config()` | Compare the FC's actual configuration against the declared aircraft profile. |\n| `check_failsafe()` | Read and explain the failsafe configuration (what happens on RC loss). |\n| `clear_flight_mode(mode_name, confirm=False)` | Remove all switch assignments for a flight mode (disables its slots via CLI 'aux'). |\n| `cli(command, confirm_for_writes=False, props_removed=False)` | Raw CLI escape hatch — run any iNAV CLI command directly. |\n| `cli_batch(commands, confirm_for_writes=False)` | Run MANY CLI commands in ONE CLI session — a single reboot for the whole batch. |\n| `configure_gps(provider='UBLOX', sbas=None, confirm=False)` | Enable the GPS feature and set the receiver provider / SBAS (atomic CLI write). |\n| `connect(port, baud=115200)` | Open the serial connection to the FC and return board identity. |\n| `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. |\n| `diagnose()` | Full diagnostic sweep — the flagship troubleshooter. |\n| `disconnect()` | Close the serial connection to the FC. |\n| `find_fc(baud=115200, probe_all=False)` | Auto-detect which serial port has a flight controller, so you don't guess. |\n| `get_aircraft_profile()` | Return the currently declared aircraft profile. |\n| `get_status()` | Read FC status via both MSP and CLI. |\n| `list_backups()` | List saved config backups under ./backups/, newest first. No FC needed. |\n| `list_flight_modes()` | List all available flight modes and their current switch assignments. |\n| `list_serial_ports()` | List all available serial ports. |\n| `read_gps()` | Live GPS status: fix type, satellites, position, speed, HDOP + nav-readiness. |\n| `read_rc_channels()` | Read live RC channel values via MSP. |\n| `read_sensors()` | Read live sensor values: attitude, per-sensor health, and analog (battery). |\n| `read_tuning()` | Read fixed-wing PID gains, rates, and key filter cutoffs (via CLI). |\n| `restore_config(path, confirm=False)` | Restore FC config by replaying a backup file's CLI commands, then save+reboot. |\n| `save_and_reboot(confirm=False)` | Save the running config to EEPROM and reboot the FC. |\n| `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). |\n| `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'). |\n| `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). |\n| `set_pid(axis, p=None, i=None, d=None, ff=None, confirm=False)` | Set fixed-wing PID gains for ONE axis (atomic CLI write). |\n| `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. |\n| `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). |\n| `why_wont_it_arm()` | Decode the FC's arming-prevention flags into plain English. |\n<!-- TOOLS:AUTOGEN:END -->\n\n## License\n\nMIT. This project ships its own MSP codec and does **not** import GPL libraries\n(uNAVlib / YAMSPy) at runtime, keeping it permissively licensed.\n",
  "bytes": 18039,
  "sha": "32986f0508c2ef0a1cc0ff590ed2b1662d9f2f6d797483790a0758f361ff460b",
  "repo_slug": "starlordz12/inav-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_starlordz12_inav_mcp_5cb03d16/readme"
}