{
  "markdown": "# ardupilot-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server that lets an AI agent talk to an ArduPilot vehicle over MAVLink. Read state, inspect and change parameters, switch modes, read prearm failures, and (gated) arm or disarm. SITL-first.\n\nInstall: `pipx install ardupilot-mavlink-mcp`\n\n`mcp-name: io.github.rmeadomavic/ardupilot-mavlink-mcp`\n\n![CI](https://github.com/rmeadomavic/ardupilot-mcp/actions/workflows/ci.yml/badge.svg)\n[![PyPI](https://img.shields.io/pypi/v/ardupilot-mavlink-mcp)](https://pypi.org/project/ardupilot-mavlink-mcp/)\n![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)\n![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)\n![MCP](https://img.shields.io/badge/MCP-server-2d3a2e)\n\n> [!WARNING]\n> **This tool can ARM and command a real aircraft.** A bad command can spin props or fly a vehicle away. Defaults are built to stop that: actuation is OFF unless you pass `--enable-actuation`, and even then it refuses a real (non-loopback) link unless you also pass `--allow-real-vehicle`. Develop against SITL. On hardware, bench-test with **props off** first. No warranty — you own the outcome.\n\n## Why\n\nMost ArduPilot tooling for LLMs targets post-flight log analysis. This one drives the **live link**: connect to a running vehicle, read its state and params, change modes, and diagnose why it won't arm — in the moment, not after landing. The useful case: point an agent at a vehicle that won't arm, have it read the params and the prearm `STATUSTEXT`, and tell you why, instead of you squinting at a GCS message log. The arm tool reports the real `COMMAND_ACK` result and hands back the prearm reasons on refusal; it never force-arms.\n\n## Architecture\n\n```\n  agent (MCP client)                    ardupilot-mcp                     vehicle\n ┌──────────────────┐   JSON-RPC    ┌──────────────────────┐   MAVLink   ┌──────────┐\n │ Claude / etc.    │ ───stdio────▶ │  FastMCP tools       │ ──udp/tcp/  │ ArduPilot│\n │                  │ ◀───────────  │   │                  │   serial──▶ │  (SITL   │\n └──────────────────┘               │   ▼                  │ ◀────────── │  or FC)  │\n                                    │  recv thread (1 reader)            └──────────┘\n                                    │   ├─▶ message cache (latest/type)\n                                    │   ├─▶ param store (request/collect)\n                                    │   └─▶ COMMAND_ACK + STATUSTEXT\n                                    └──────────────────────┘\n```\n\nMAVLink is an async stream; MCP tools are synchronous. One background thread owns the link and is the only reader — it caches the latest message of each type and routes `PARAM_VALUE` into a param store. Tool calls read from those caches (params block until the data arrives). No two threads ever call `recv_match`.\n\n## What it does\n\n- **Reads vehicle state from cache, instantly.** Mode, armed, GPS fix and sats, battery, attitude, position — served from cached telemetry, no blocking on the link.\n- **Diagnoses a no-arm.** `ardupilot_arm` returns the `COMMAND_ACK` result and, on refusal, the prearm `STATUSTEXT` (e.g. `AHRS: waiting for home`, `Accels inconsistent`). Safety checks are respected — no `ARMING_CHECK=0`, no force-arm magic number.\n- **Gets and sets parameters** on the real param table, with `set` confirmed by the echoed `PARAM_VALUE`.\n- **Switches flight modes** by name, mapped per vehicle type (Copter/Rover/Plane/Sub) — not hardcoded numbers.\n\n## Quick start (SITL)\n\nYou need an ArduPilot SITL instance. From an `ardupilot` checkout:\n\n```bash\n# starts ArduCopter SITL; serves MAVLink on tcp:127.0.0.1:5760\nsim_vehicle.py -v ArduCopter --console\n```\n\nInstall and run the server (read-only by default):\n\n```bash\npipx install ardupilot-mavlink-mcp          # or: uv tool install ardupilot-mavlink-mcp\nardupilot-mavlink-mcp --connect tcp:127.0.0.1:5760\n```\n\nTo allow parameter writes, mode changes, and arm/disarm against SITL, add `--enable-actuation`.\n\n## Use with an MCP client\n\nClaude Code:\n\n```bash\nclaude mcp add ardupilot -- ardupilot-mavlink-mcp --connect tcp:127.0.0.1:5760\n```\n\nClaude Desktop / any `mcpServers` config:\n\n```json\n{\n  \"mcpServers\": {\n    \"ardupilot\": {\n      \"command\": \"ardupilot-mavlink-mcp\",\n      \"args\": [\"--connect\", \"tcp:127.0.0.1:5760\"]\n    }\n  }\n}\n```\n\nConnection strings are pymavlink syntax: `tcp:127.0.0.1:5760` (SITL), `udp:127.0.0.1:14550`, `serial:/dev/ttyACM0:115200`.\n\n## Tools\n\n| Tool | Kind | What it does |\n| --- | --- | --- |\n| `ardupilot_connect` | — | Connect to a vehicle. Default is local SITL. |\n| `ardupilot_vehicle_state` | read | Mode, armed, GPS, battery, attitude, position — from cache. |\n| `ardupilot_recent_statustext` | read | Recent STATUSTEXT/prearm messages. Read this to see why arming failed. |\n| `ardupilot_get_param` | read | Read one parameter. |\n| `ardupilot_set_param` | write | Set a parameter, confirmed via echoed `PARAM_VALUE`. |\n| `ardupilot_list_params` | read | List params, optional glob (`ATC_RAT_*`). |\n| `ardupilot_set_mode` | write | Send a request to set flight mode by name. |\n| `ardupilot_arm` / `ardupilot_disarm` | write | Gated. Confirmed via `COMMAND_ACK`. |\n\nWrite tools are gated and carry the MCP `destructiveHint`. Live telemetry is also exposed as the resource `ardupilot://telemetry`.\n\n## Supported vehicles\n\n| Vehicle | Firmware | Status |\n| --- | --- | --- |\n| ArduCopter | 4.5 | ✓ validated on SITL |\n| ArduRover (UGV/USV) | 4.x | ~ mode map present, not yet validated |\n| ArduPlane | 4.x | ~ mode map present, not yet validated |\n| ArduSub | 4.x | ~ untested |\n\nMAVLink2 is assumed. Not flown on hardware — SITL only so far.\n\n## Safety model\n\n1. Actuation tools are OFF by default. Enable with `--enable-actuation`.\n2. Even enabled, actuation on a real link (serial or non-loopback network) is refused unless `--allow-real-vehicle` is also set.\n3. Link classification fails safe: anything not clearly loopback is treated as a real vehicle.\n4. `arm`, `disarm`, `set_param`, and `set_mode` all pass through this gate before sending.\n5. Arming has no force-arm option, and `set_param` rejects `ARMING_CHECK` writes (case-insensitive) on every link, even when both actuation flags are enabled.\n\n## Status\n\nWorking: reads plus gated parameter writes, mode changes, and arm/disarm against ArduCopter SITL. Validated three ways — unit tests (Python 3.10–3.12), a real-MAVLink-wire check (`scripts/wire_check.py`), and a live ArduPilot SITL run (`scripts/sitl_check.py`).\n\nOpen targets: validate Rover/Plane/Sub; mission upload/download and guided flight (takeoff/goto/land) are deferred — mission protocol is a stateful handshake and guided commands are fly-away risk. See [ROADMAP.md](ROADMAP.md).\n\n## Develop\n\n```bash\ngit clone https://github.com/rmeadomavic/ardupilot-mcp && cd ardupilot-mcp\nuv venv && uv pip install -e \".[dev]\"\npytest -q\nruff check . && ruff format --check .\npython scripts/wire_check.py            # offline real-wire check, no SITL needed\n```\n\n## License\n\n[MIT](LICENSE) — Kyle Adomavicius\n",
  "bytes": 7033,
  "sha": "914a94be30cb7598b0ea2adf14699da0b67ca801a37f70b1c6d783f005e2e96d",
  "repo_slug": "rmeadomavic/ardupilot-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_rmeadomavic_ardupilot_mavlink__3bed33b1/readme"
}