mavlink-mcp 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ [flake8]
2
+ max-line-length = 110
3
+ extend-ignore = E203
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
@@ -0,0 +1,139 @@
1
+ # Getting started
2
+
3
+ A from-scratch walkthrough on a fresh machine: install the server, drive it from **Claude Code**
4
+ or **Codex**, and fly ArduPilot SITL by natural language. For the full tool / flag / safety
5
+ reference, see [README.md](README.md).
6
+
7
+ Claude Code and Codex are MCP clients, so all you need is this server. The Gazebo camera in step 5
8
+ is optional.
9
+
10
+ ## What you need
11
+
12
+ - Python 3.10+ and git.
13
+ - **ArduPilot SITL built** — the one heavy prerequisite. Follow ArduPilot's
14
+ [SITL on Linux](https://ardupilot.org/dev/docs/setting-up-sitl-on-linux.html) guide (clone
15
+ `ardupilot`, then `./waf configure --board sitl && ./waf copter`). You can install the server
16
+ without it, but you can only fly the in-memory fake backend (step 4) until SITL is there.
17
+ - Optional, for the camera: Gazebo Harmonic and the world repo (step 5).
18
+
19
+ ## 1. Install the server
20
+
21
+ ```bash
22
+ git clone git@github.com:deepak61296/mavlink-mcp.git
23
+ cd mavlink-mcp
24
+ python3 -m venv .venv && source .venv/bin/activate
25
+ pip install -e .
26
+ which mavlink-mcp # note this path -- you may need it in step 2
27
+ ```
28
+
29
+ Not on PyPI yet, so this is a source install. The `-e` puts a `mavlink-mcp` command on the venv's
30
+ PATH; that command is what the client configs below launch.
31
+
32
+ ## 2. Register it with your client
33
+
34
+ **Claude Code:**
35
+
36
+ ```bash
37
+ claude mcp add --transport stdio drone -- mavlink-mcp --enable-actuation
38
+ claude mcp list # should list "drone"; /mcp inside Claude Code shows it Connected
39
+ ```
40
+
41
+ The `--` is required: it stops Claude parsing `--enable-actuation` as its own flag. Default scope is
42
+ local (this project only); add `--scope project` to write a shared `.mcp.json` instead.
43
+
44
+ **Codex CLI** — add to `~/.codex/config.toml`:
45
+
46
+ ```toml
47
+ [mcp_servers.drone]
48
+ command = "mavlink-mcp"
49
+ args = ["--enable-actuation"]
50
+ default_tools_approval_mode = "auto" # simulator only; otherwise it prompts per flight tool
51
+ ```
52
+
53
+ Gotcha (the most common fresh-machine snag): the client spawns `mavlink-mcp` and has to find it on
54
+ PATH. If you installed into a venv, either launch the client with that venv activated, or replace
55
+ the bare `mavlink-mcp` with the absolute path from `which mavlink-mcp`
56
+ (e.g. `/home/you/mavlink-mcp/.venv/bin/mavlink-mcp`).
57
+
58
+ Actuation is off by default; `--enable-actuation` turns on the flight tools. Flying anything the
59
+ vehicle itself doesn't prove to be a simulator (SITL streams a `SIMSTATE` message; real firmware
60
+ never does) additionally needs `--allow-real-vehicle`.
61
+
62
+ ## 3. Start SITL (separate terminal)
63
+
64
+ ```bash
65
+ cd ~/ardupilot
66
+ python3 Tools/autotest/sim_vehicle.py -v ArduCopter --no-mavproxy -I0
67
+ ```
68
+
69
+ `--no-mavproxy` matters: ArduPilot SITL serves exactly one MAVLink client on its TCP port, so a
70
+ stray MAVProxy (or a second server) makes the vehicle look dead rather than busy.
71
+
72
+ ## 4. Fly it
73
+
74
+ In Claude Code or Codex, confirm the drone tools are present (`/mcp`), then ask something like:
75
+
76
+ > take off to 20 m, fly 40 m north, orbit here at 15 m, then RTL.
77
+
78
+ No SITL yet? `mavlink-mcp --backend fake --enable-actuation` runs an in-memory drone with no ports,
79
+ which is enough to confirm the client wiring end to end.
80
+
81
+ ## 5. Optional: the camera / vision
82
+
83
+ ```bash
84
+ git clone git@github.com:deepak61296/ardupilot_gazebo_ai.git
85
+ cd ardupilot_gazebo_ai
86
+ bash scripts/setup_plugin.sh # clones + builds ArduPilot's Gazebo plugin, patches the camera
87
+ bash scripts/sim_up.sh --check # reports what's still missing (Gazebo Harmonic, a GPU, ...)
88
+ bash scripts/sim_up.sh # brings up Gazebo + SITL together
89
+ ```
90
+
91
+ Then add `--camera gazebo` to the server args and ask it to point the camera down, fly north and
92
+ take a photo. It returns the frame as an image — Claude Code and Codex are multimodal, so they can
93
+ see it.
94
+
95
+ Gotcha: the Gazebo stream is H.264 over UDP, which OpenCV can read only through GStreamer, and the
96
+ `opencv-python` wheel is built without it. Use Ubuntu's `python3-opencv` (with `numpy<2`) for the
97
+ Gazebo camera; `rtsp://` and `file:` sources work with the wheel.
98
+
99
+ ## Verify the install
100
+
101
+ ```bash
102
+ cd mavlink-mcp
103
+ pytest # unit tests, no vehicle needed
104
+ pytest -m sitl # flies a full mission against SITL (start SITL first; ~5 min)
105
+ ```
106
+
107
+ ## Local models via Ollama (fully offline flying)
108
+
109
+ Any client that can talk to Ollama can fly through this server with no cloud at all — tested
110
+ end-to-end with gemma (an ~8B multimodal model) driving a full takeoff → move → wait → RTL
111
+ mission. Two traps cost us an evening each, so they're documented here:
112
+
113
+ 1. **Bake a bigger context into a model tag.** This server's 18 tool schemas plus a typical
114
+ client system prompt are ~10k tokens, but Ollama's OpenAI-compatible endpoint truncates
115
+ silently at its 4096-token default AND ignores `num_ctx` sent in request bodies. The model
116
+ then "never sees its tools" — symptoms are silent turns, invented tool names, or
117
+ Python-flavored pseudo-calls. The fix is one line:
118
+
119
+ ```bash
120
+ printf 'FROM gemma4:e4b\nPARAMETER num_ctx 16384\nPARAMETER temperature 0\n' | ollama create gemma-drone -f -
121
+ ```
122
+
123
+ Point your client at `gemma-drone`. (Clients using Ollama's native `/api/chat` are not
124
+ affected and can also pass `think: false` there for much faster tool calls; the
125
+ OpenAI-compat endpoint ignores that flag too.)
126
+
127
+ 2. **Phrase safety-relevant asks explicitly when the model is small.** "Disable the geofence"
128
+ can make a small model invent parameter names that don't exist; "set FENCE_ENABLE to 0"
129
+ reliably reaches `set_param` — where the server refuses it, which is the point. Judge any
130
+ local-model flight by the code-verified `[state: ...]` lines in tool results, never by the
131
+ model's own prose: small models narrate optimistically.
132
+
133
+ ## If a step breaks
134
+
135
+ - Client can't find the server -> the venv/PATH note in step 2.
136
+ - Vehicle looks dead / never arms -> a second MAVLink client is on the port (step 3), or EKF/GPS
137
+ hasn't settled yet (give it 30-60 s after SITL boots).
138
+ - Camera returns nothing -> the GStreamer OpenCV note in step 5; also make sure `sim_up.sh` is up
139
+ and rendering (it needs a GPU for the headless camera).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 deepak61296
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,334 @@
1
+ Metadata-Version: 2.5
2
+ Name: mavlink-mcp
3
+ Version: 0.1.2
4
+ Summary: MCP server for MAVLink drones: safety-gated flight tools and live camera frames for Claude, Codex and any MCP client. ArduPilot today, PX4 next.
5
+ Project-URL: Homepage, https://github.com/deepak61296/mavlink-mcp
6
+ Project-URL: Repository, https://github.com/deepak61296/mavlink-mcp
7
+ Project-URL: Issues, https://github.com/deepak61296/mavlink-mcp/issues
8
+ Project-URL: Changelog, https://github.com/deepak61296/mavlink-mcp/blob/main/STATUS.md
9
+ Author: deepak61296
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ardupilot,drone,llm,mavlink,mcp,px4,sitl,uav
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: mcp<2,>=1.2
26
+ Requires-Dist: pymavlink>=2.4.40
27
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
28
+ Provides-Extra: camera
29
+ Requires-Dist: opencv-python>=4.8; extra == 'camera'
30
+ Provides-Extra: dev
31
+ Requires-Dist: flake8>=7; extra == 'dev'
32
+ Requires-Dist: pytest>=8; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # mavlink-mcp
36
+
37
+ An MCP server for MAVLink drones. It lets an LLM agent (Claude, Codex, or anything that speaks
38
+ MCP) fly an ArduPilot vehicle and see through its camera. Works against ArduPilot SITL, so you can
39
+ try it with no hardware.
40
+
41
+ Drones are dangerous — flight tools are **off by default**. Read [Safety](#safety) first.
42
+
43
+ New here? **[GETTING_STARTED.md](GETTING_STARTED.md)** walks a fresh machine through install →
44
+ Claude Code / Codex → flying SITL.
45
+
46
+ ## Status
47
+
48
+ **Solid in simulation (beta)** — covered by the unit suite and flown by `pytest -m sitl` against
49
+ ArduPilot SITL: the read-only tools, `arm`/`disarm`/`takeoff`/`land`/`rtl`/`goto`/`move`/`orbit`/
50
+ `wait`, `set_param` with the safety guards, the geofence + GCS-heartbeat failsafe. The Gazebo
51
+ camera path is exercised manually (see below), not by the SITL suite.
52
+
53
+ **Working, less battle-tested** — the HTTP transport, `--camera` over `rtsp://`/`udp://`, the pi
54
+ bridge. Not yet flown on real hardware.
55
+
56
+ **Planned** — a PX4 backend (MAVSDK): PX4 is detected from its heartbeat today, but flight is
57
+ refused until the backend lands. And a PyPI release. See
58
+ [STATUS.md](https://github.com/deepak61296/mavlink-mcp/blob/main/STATUS.md).
59
+
60
+ ## Install
61
+
62
+ Needs **Python 3.10+**. To actually fly you also need **ArduPilot SITL** built (ArduPilot's
63
+ [SITL on Linux](https://ardupilot.org/dev/docs/setting-up-sitl-on-linux.html) guide); the optional
64
+ Gazebo camera needs Gazebo Harmonic + the [world](#with-a-camera-in-gazebo). The server itself
65
+ starts without either — the link opens lazily on the first tool call.
66
+
67
+ Not on PyPI yet — install from source. This puts the `mavlink-mcp` command on your PATH, which is
68
+ what the MCP client configs below call:
69
+
70
+ ```bash
71
+ git clone git@github.com:deepak61296/mavlink-mcp.git
72
+ cd mavlink-mcp
73
+ pip install -e . # add ".[camera]" for the Gazebo/RTSP camera tool
74
+ ```
75
+
76
+ If `mavlink-mcp` isn't on your client's PATH (e.g. it lives in a venv), give the configs the
77
+ absolute path to it instead of the bare command name.
78
+
79
+ ## Try it against SITL
80
+
81
+ Start ArduCopter SITL (no MAVProxy, so the server owns the link):
82
+
83
+ ```bash
84
+ cd ~/ardupilot
85
+ python3 Tools/autotest/sim_vehicle.py -v ArduCopter --no-mavproxy -I0
86
+ ```
87
+
88
+ Wire it into your MCP client. **Claude Code** — add it with one command:
89
+
90
+ ```bash
91
+ claude mcp add --transport stdio drone -- mavlink-mcp --enable-actuation
92
+ claude mcp list # check it's registered; /mcp inside Claude Code shows it connected
93
+ ```
94
+
95
+ `--` is required — it stops Claude parsing `--enable-actuation` as its own flag. Default scope is
96
+ local (this project only); `--scope project` writes a shared `.mcp.json` instead. Or configure it by
97
+ hand — `.mcp.json` in the project root (Claude Desktop uses the same shape):
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "drone": { "command": "mavlink-mcp", "args": ["--enable-actuation", "--camera", "gazebo"] }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Codex CLI (`~/.codex/config.toml`):
108
+
109
+ ```toml
110
+ [mcp_servers.drone]
111
+ command = "mavlink-mcp"
112
+ args = ["--enable-actuation", "--camera", "gazebo"]
113
+ # Codex asks before every flight tool by default, because they are marked destructive.
114
+ # Set this to "approve" only if you want it to fly unattended (a simulator, say).
115
+ default_tools_approval_mode = "auto"
116
+ ```
117
+
118
+ [pi](https://github.com/badlogic/pi-mono) ships no MCP client of its own, so there is a
119
+ bridge extension in this repo:
120
+
121
+ ```bash
122
+ MAVLINK_MCP_ARGS="--enable-actuation" pi -a -e integrations/pi/mavlink-mcp.ts
123
+ ```
124
+
125
+ Every tool is annotated, so a client can tell the difference between reading telemetry and
126
+ moving an aircraft: the read-only tools declare `readOnlyHint` and get auto-approved, the
127
+ flight tools declare `destructiveHint` and prompt.
128
+
129
+ Argument limits travel in the tool schema, so a client rejects an impossible request before
130
+ it reaches an aircraft — `takeoff(altitude_m=-20)` comes back as a validation error, not as
131
+ a clamped flight. The bounds come from your config, so the model sees the envelope it is
132
+ actually flying in.
133
+
134
+ Then ask it something like *"take off to 20 m, fly 40 m north, orbit here at 15 m, take a photo, then RTL."*
135
+
136
+ No SITL? `mavlink-mcp --backend fake --enable-actuation` runs an in-memory drone with no ports.
137
+
138
+ ## With a camera, in Gazebo
139
+
140
+ For a drone that can actually see something, there is a Gazebo field — roads, cars,
141
+ buildings, markers — in [ardupilot_gazebo_ai](https://github.com/deepak61296/ardupilot_gazebo_ai):
142
+
143
+ ```bash
144
+ git clone git@github.com:deepak61296/ardupilot_gazebo_ai.git
145
+ cd ardupilot_gazebo_ai && bash scripts/setup_plugin.sh && bash scripts/sim_up.sh
146
+ ```
147
+
148
+ then, in another terminal:
149
+
150
+ ```bash
151
+ mavlink-mcp --enable-actuation --camera gazebo
152
+ ```
153
+
154
+ Ask the agent to take off, point the camera down, fly north and take a photo, and it gets
155
+ back a picture of the field.
156
+
157
+ ## On a real vehicle
158
+
159
+ Nothing here has been flown on hardware yet, so treat this as the configuration you would
160
+ start from rather than a tested recipe.
161
+
162
+ Two things change from SITL, and they are configured independently — the autopilot link and
163
+ the camera are separate streams that happen to come off the same aircraft:
164
+
165
+ ```bash
166
+ mavlink-mcp \
167
+ --conn serial:/dev/ttyACM0:57600 \
168
+ --camera rtsp://192.168.1.10:8554/cam \
169
+ --enable-actuation --allow-real-vehicle
170
+ ```
171
+
172
+ - **`--conn`** — `serial:/dev/ttyACM0:57600` for a USB/telemetry radio, or `udp:...` if a
173
+ router is already fanning the link out. If a GCS is also connected, read
174
+ [Running alongside a GCS](#running-alongside-a-gcs) first: one MAVLink endpoint serves one
175
+ client.
176
+ - **`--camera`** — your camera's own RTSP URL, which the operator has to supply; the server
177
+ has no way to discover it. Unlike `--camera gazebo`, RTSP is read through OpenCV's FFMPEG
178
+ backend, so the plain `pip install ".[camera]"` wheel is enough — no GStreamer build
179
+ needed. Try `--camera file:<some.jpg>` first to confirm your client renders images at all.
180
+ - **`--allow-real-vehicle`** — required. Actuation is refused on anything that does not
181
+ identify itself as a simulator (see [Safety](#safety)), and the connection string is never
182
+ what decides that.
183
+
184
+ ## Running alongside a GCS
185
+
186
+ The server owns the link. ArduPilot SITL — and a typical serial flight controller — serve a
187
+ **single** MAVLink client, so you can't point `mavlink-mcp` and a ground station at the same
188
+ endpoint; the second one connects but never sees a heartbeat. To run both, fan the stream out with
189
+ [mavlink-router](https://github.com/mavlink-router/mavlink-router) (or mavproxy) and give each
190
+ consumer its own routed UDP port:
191
+
192
+ ```bash
193
+ # one FC in, two UDP endpoints out
194
+ mavlink-routerd -e 127.0.0.1:14550 -e 127.0.0.1:14560 /dev/ttyACM0:57600
195
+ ```
196
+
197
+ Then `mavlink-mcp --conn udp:127.0.0.1:14560` while your GCS takes `14550`.
198
+
199
+ ## Tools
200
+
201
+ Read-only (always on): `get_status`, `describe_vehicle`, `check_armable`, `get_param`,
202
+ `capture_camera`.
203
+
204
+ The server also finds out what it's talking to on its own: `describe_vehicle` reports the
205
+ autopilot and firmware version (from `AUTOPILOT_VERSION`), the vehicle type from the
206
+ heartbeat, sensor health from `SYS_STATUS`, the fence, and the protocol capabilities —
207
+ all read from the vehicle, not from configuration. The same info is published as MCP
208
+ resources (`mavlink://vehicle`, `mavlink://telemetry`) for clients that read those.
209
+
210
+ Flight (need `--enable-actuation`): `arm`, `disarm`, `takeoff`, `land`, `rtl`, `goto`, `move`,
211
+ `orbit`, `wait`, `set_mode`, `set_param`, `point_camera`, `emergency_stop`.
212
+
213
+ - `move` takes north/south/east/west, the four diagonals (northeast/…), or forward/back/left/right,
214
+ plus a distance. Blocks until it arrives.
215
+ - `orbit` flies one full circle of a given radius around the current position, holding altitude.
216
+ - `takeoff`/`goto`/`move`/`orbit`/`land`/`rtl` all block until the vehicle actually gets
217
+ there — `rtl` returns once it is down and disarmed, not when the mode switches — and every
218
+ reply ends with a `[state: alt X m, MODE, armed]` line read from live telemetry.
219
+ - `emergency_stop` interrupts a running flight command — the blocking tool unwinds immediately —
220
+ then commands RTL. The RTL itself may wait behind at most one in-flight MAVLink exchange
221
+ (each is hard-bounded at a few seconds).
222
+ - Flight commands run off the event loop, so `get_status` and `emergency_stop` still answer
223
+ immediately while the vehicle is in the middle of a long move.
224
+ - `capture_camera` returns the frame as an MCP image, so a multimodal model can look at it. Point
225
+ `--camera` at `gazebo`, an `rtsp://` URL, or `file:<path>`. `file:` is handy for a first
226
+ test: point it at any JPEG and check your client actually renders what the drone "sees".
227
+ Every other tool returns text, so the flight and telemetry tools work with any model — only
228
+ `capture_camera` needs a multimodal client (Claude, Codex).
229
+
230
+ Note on `--camera gazebo`: that stream is H.264 over UDP, which OpenCV can only read
231
+ through GStreamer, and the `opencv-python` wheel is built without it. Use Ubuntu's
232
+ `python3-opencv` (and `numpy<2` with it) for the Gazebo camera. `rtsp://` and `file:`
233
+ work fine with the wheel.
234
+
235
+ ## Safety
236
+
237
+ - Actuation is off unless you pass `--enable-actuation`. Without it, only the read-only tools
238
+ exist — and a read-only server **never writes to the flight controller**, not even failsafe
239
+ setup: connecting and reading status leaves the vehicle's configuration untouched.
240
+ - Even then, flying needs the vehicle to prove it is a simulator: ArduPilot SITL streams a
241
+ `SIMSTATE` message, real firmware never does. No `SIMSTATE` — including a real FC routed to
242
+ `127.0.0.1` by mavlink-router — means every flight tool (`emergency_stop` included) is refused
243
+ until you pass `--allow-real-vehicle`. The connection string is never trusted for this.
244
+ - Flight tools are refused on anything that isn't a multirotor (a Plane or Rover heartbeat gets
245
+ telemetry tools only), and on PX4 until its backend exists.
246
+ - `set_param` refuses writes to the safety-net parameter families at **any** value — `FENCE_*`,
247
+ `FS_*`, `ARMING_*`, battery failsafes, `FORMAT_VERSION`, `SYSID_*` — not just "off" values,
248
+ because a fence is weakened as easily by raising `FENCE_RADIUS` as by zeroing `FENCE_ENABLE`.
249
+ Opt out with `--allow-unsafe-params`.
250
+ - Altitude is clamped to a limit and to the vehicle's fence; horizontal targets are pulled back
251
+ inside the geofence. `get_status` reports whether the horizontal clamp is actually active
252
+ (it needs a home fix and a readable fence radius) instead of failing silently.
253
+ - No disarm while airborne, no takeoff while flying, no move/goto before armed and airborne —
254
+ and when altitude is unknown (position stream lost), these checks fail **closed**, not open.
255
+ - With actuation enabled, the server enables the FC geofence and a GCS-heartbeat failsafe, so
256
+ the vehicle returns to launch on its own if the agent or link dies. `describe_vehicle` lists
257
+ exactly which parameters were written at connect.
258
+ - Timed-out commands cannot fire late: a flight command that already reported failure is
259
+ cancelled before it can reach the vehicle afterwards.
260
+
261
+ None of this replaces a human with a kill switch on a real flight.
262
+
263
+ ## Options
264
+
265
+ | Flag | Env | Default | Meaning |
266
+ |------|-----|---------|---------|
267
+ | `--conn` | `MAVLINK_MCP_CONN` | `tcp:127.0.0.1:5760` | MAVLink endpoint (SITL, `udp:...`, `serial:/dev/ttyACM0:57600`) |
268
+ | `--enable-actuation` | | off | register the flight tools |
269
+ | `--allow-real-vehicle` | | off | allow actuation on non-local connections |
270
+ | `--allow-unsafe-params` | | off | let `set_param` disable fences/failsafes |
271
+ | `--camera` | `MAVLINK_MCP_CAMERA` | none | `gazebo[:port]`, `rtsp://...`, `udp://...`, `file:<path>` |
272
+ | `--backend` | `MAVLINK_MCP_BACKEND` | `auto` | `auto` (detect from heartbeat), `ardupilot`, `fake` |
273
+ | `--config` | `MAVLINK_MCP_CONFIG` | none | TOML config file, see below |
274
+ | `--transport` | | `stdio` | `stdio` or `http` |
275
+ | `--host` | | `127.0.0.1` | bind address for `--transport http`. **No auth exists on the HTTP transport**, so a non-loopback bind is refused at startup — front it with an authenticating proxy instead |
276
+ | `--port` | | `8000` | port for `--transport http` |
277
+
278
+ The link opens lazily on the first tool call, so the server starts fine before SITL is up.
279
+
280
+ Instead of flags you can keep everything in one TOML file — handy when the MCP client
281
+ entry should stay short, and the only place to tune the safety limits:
282
+
283
+ ```toml
284
+ # drone.toml — run with: mavlink-mcp --config drone.toml
285
+ [connection]
286
+ uri = "tcp:127.0.0.1:5760"
287
+
288
+ [safety]
289
+ enable_actuation = true
290
+ max_takeoff_alt_m = 50 # clamp on top of the vehicle's own fence
291
+ max_orbit_radius_m = 100
292
+
293
+ [camera]
294
+ source = "gazebo"
295
+ ```
296
+
297
+ Flags and env vars override the file.
298
+
299
+ ## Autopilot support
300
+
301
+ The default backend is `auto`: the server reads the autopilot type from the first heartbeat.
302
+ ArduPilot works today and is what the test suite flies. PX4 is recognised from its heartbeat
303
+ and flight is refused on it until the PX4 backend (MAVSDK) lands; the read-only tools are
304
+ plain MAVLink and should work, but nothing here has been run against PX4 yet — see
305
+ [STATUS.md](https://github.com/deepak61296/mavlink-mcp/blob/main/STATUS.md).
306
+
307
+ `scripts/mission_demo.py` is a small example that drives the server over MCP and flies a mission.
308
+
309
+ ## Tests
310
+
311
+ ```bash
312
+ pytest # unit tests, no vehicle needed
313
+ pytest -m sitl # flies a real mission against SITL (~5 min)
314
+ ```
315
+
316
+ The `sitl` suite is opt-in because it needs ArduCopter SITL listening on
317
+ `MAVLINK_MCP_TEST_CONN` (default `tcp:127.0.0.1:5760`). It drives the server over the real
318
+ stdio protocol and flies a full mission, which is the only way most of the interesting
319
+ failures show up at all.
320
+
321
+ Start SITL for it with no MAVProxy, so the server owns the link:
322
+
323
+ ```bash
324
+ python3 Tools/autotest/sim_vehicle.py -v ArduCopter --no-mavproxy -I0
325
+ ```
326
+
327
+ That matters more than it looks: **ArduPilot SITL serves exactly one MAVLink client on its
328
+ TCP port.** A second connection is accepted at the socket level and then never receives a
329
+ heartbeat, so a stray MAVProxy or a second server makes the vehicle look dead rather than
330
+ busy.
331
+
332
+ ## License
333
+
334
+ MIT