Skip to content

v2.0.2 — DroneServer v2: LLM-agnostic MAVLink control with an untrusted-commander safety layer

Latest

Choose a tag to compare

@PeterJBurke PeterJBurke released this 04 Sep 13:19
· 2 commits to main since this release

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

git clone https://github.com/PeterJBurke/droneserver.git
cd droneserver && git checkout v2.0.2
uv sync
cp .env.example .env      # point MAVLINK_* at your aircraft or simulator
uv 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.