Repository navigation
v2.0.2 — DroneServer v2: LLM-agnostic MAVLink control with an untrusted-commander safety layer
LatestThis 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
*_serverplugin 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 anythingcriticalexecutes; 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-USERrules where containers are involved. Every v1 instruction to stand up an ngrok tunnel — includingngrok.service— is gone from the tree. SeeSECURITY.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_airandtelemetry.landed_stateare dispatched dynamically —getattr(drone.telemetry, topic)intelemetry/ground_stream.pysince FIX 15 — so the generator's AST matcher could not trace them and scored them as unimplemented. Both are now curated entries indocs/coverage_overrides.csvwith 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.pyreads 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.pyrenders the ten mission prompts through the harness's own call, and--verifychecks 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 tomode="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 explicitmode="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__anduv.lockfor every commit since the v2.0.1 tag; all three now say 2.0.2.CONTRIBUTING.mdno longer sends pull requests to av-nextbranch that has never existed on this remote. Four non-existent tool names were corrected inLMSTUDIO_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 stdioPython 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.