DroneServer v2.0.2: an LLM-agnostic, MAVLink-based drone command-and-control MCP server with an untrusted-commander safety layer
Archived copy of the DroneServer code release v2.0.2 (git tag v2.0.2, commit 3281e6c3755814318d436556690c28e669f8c2e9), the version reported in the article An LLM-Agnostic, MAVLink-Based Drone Command and Control Interface and Agentic Harness Using the Model Context Protocol (Ramos Silva and Burke; preprint arXiv:2601.15486). The live repository is github.com/PeterJBurke/droneserver; the GitHub release page for this version is releases/tag/v2.0.2. The archive is the repository tree at that tag (git archive v2.0.2). Release notes follow. This is the first published release of the v2 line. The Releases page has until now shown v1.4.0 — a build that predates the safety layer entirely, and whose documentation told the reader to expose the server to the internet through a public tunnel. v2 is a complete rewrite. If you have a v1 checkout, this replaces it; do not deploy v1. DroneServer is an MCP server that lets any MCP-capable large language model fly a MAVLink aircraft — ArduPilot or PX4, simulated or real — together with a server-side safety layer built on the premise that the commanding model is untrusted. It is the software artifact for the preprint arXiv:2601.15486. ## What v2 is - 98 registered MCP tools, up from 41 in v1. Per-tool evidence — which suite drives it, whether it has been exercised against a real autopilot, how often a model chose it in the scored campaigns — is in the generated docs/tool_test_coverage.md.- MavSDK client-side coverage: 223 implemented and 15 documented not-applicable of 238 methods, across 33 plugins — 238 of 238 addressed, 0 missing. Drone-side *_server plugin methods (92) are out of scope for a ground-side interface. The matrix is generated from the code, not typed: docs/coverage_summary.md.- A server-side safety layer the model cannot switch off. Criticality tiers on every tool (an unclassified tool is treated as critical, so a new one cannot slip in); a single-use, tool-bound, argument-bound confirmation handshake before anything critical executes; a geofence enforced by the server itself rather than delegated to the autopilot, with continuous-motion commands projected forward before being allowed; parameter bounds and state preconditions; scoped API keys (telemetry / control / admin); per-client rate limits with a smaller budget for critical calls; an append-only audit log recording who asked, what was allowed or refused, which rule fired, and how long it took. It fails closed — if a check itself raises, the command is refused, and bad safety configuration stops the server from starting rather than being discovered mid-flight.- Zero-public-ports deployment. The server is meant to be reachable only over a private WireGuard/Tailscale tailnet, behind a default-deny firewall with DOCKER-USER rules where containers are involved. Every v1 instruction to stand up an ngrok tunnel — including ngrok.service — is gone from the tree. See SECURITY.md.- Server-side mission state, so a long mission survives the client disconnecting.- A benchmark and mission harness (scripts/run_llm_missions.py, scripts/run_mission_suite.py) that runs any provider against the same server and safety layer, with verdicts computed from recorded telemetry and the audit log rather than from the model's own claim of success — plus a four-layer capture pipeline (10 Hz telemetry, a bidirectional MAVLink wire tap, the audit-log slice, the full LLM transcript) sealed per trial with a sha256 of every file. ### Test suite | Layer | Count ||---|--:|| Unit (no aircraft) | 937 || SITL integration (docker ArduPilot 4.5.7) | 116 || Adversarial / prompt-injection cases | 29 of 29 as specified | The adversarial suite exercises prompt injection in tool arguments, forged and replayed confirmation tokens, scope escalation and out-of-fence waypoints against a live SITL aircraft through the real MCP path — case by case in docs/adversarial_results.md. CI is green on this commit: ruff, ruff format, mypy and the 937 unit tests (run 33752950492); the SITL suite passed on the same commit — 107 passed, 8 skipped, 1 deselected of the 116 collected (run 33752950437). ## Changes since v2.0.1 No API change: no tool was added, removed or altered, and no safety rule changed. - The coverage matrix is back to the true 223/238. telemetry.in_air and telemetry.landed_state are dispatched dynamically — getattr(drone.telemetry, topic) in telemetry/ground_stream.py since FIX 15 — so the generator's AST matcher could not trace them and scored them as unimplemented. Both are now curated entries in docs/coverage_overrides.csv with that reason recorded, and the checked-in matrix has been regenerated, which also cleared nine rows of drift.- Two generators, so the paper cannot drift from this repository. scripts/adversarial_case_table.py reads the adversarial cases out of the AST of the test file, joins them against the status and rule id recorded by the suite while it ran against live SITL, and fails if the two disagree about which cases exist or about the headline count. scripts/mission_prompt_table.py renders the ten mission prompts through the harness's own call, and --verify checks that rendering byte-for-byte against the first operator message of every recorded trial. No row in either of the paper's tables is typed by hand.- The v1-era pages no longer contradict the v2 code. Six documents still told the reader that pause_mission() was deprecated, disabled and unsafe. It has not been since the rewrite: the tool defaults to mode="guided_hold", an altitude-safe GUIDED position hold that needs no RC transmitter, and reaches the firmware LOITER path that caused the v1.2.2 crash only through an explicit mode="native_hold", which returns the descent warning with its result. Corrected in place, with the historical crash report kept as history rather than current status.- Housekeeping. The package reported version 2.0.1 in pyproject.toml, droneserver.__version__ and uv.lock for every commit since the v2.0.1 tag; all three now say 2.0.2. CONTRIBUTING.md no longer sends pull requests to a v-next branch that has never existed on this remote. Four non-existent tool names were corrected in LMSTUDIO_SETUP.md, and no broken relative link remains in any top-level document. Full detail in CHANGELOG.md. ## Getting started ``bashgit clone https://github.com/PeterJBurke/droneserver.gitcd droneserver && git checkout v2.0.2uv synccp .env.example .env # point MAVLINK_* at your aircraft or simulatoruv run python -m droneserver.server --transport stdio` Python 3.11 or newer. To reproduce the paper's results, start at docs/reproduce.md. ## Operating safely This software has been tested in simulation. Flying a real aircraft with a language model in the loop is the operator's decision and the operator's responsibility: keep visual line of sight, keep a manual RC override live, verify GPS lock and battery before arming, fly clear of people, and configure the geofence for the site you are actually at. The safety layer refuses commands; it does not make an aircraft safe. --- Tags v2.0.0 and v2.0.1` remain tag-only by design — they are archive points for the paper, not published releases.