MAVLink MCP Server について
MAVLink MCP Server は、MAVLink 対応機体と通信する LLM エージェント向けの Python 製 MCP サーバーです。主に MAVSDK 経由の PX4 を対象とし、まず PX4 SITL で試すことを推奨しています。航線を機体に送る前にレビューできる工程も備えています。
主な機能
- 01MAVLink による機体との接続
- 02非同期で接続状態を返す仕組み
- 03測量用の地理参照付き衛星ビュー
- 04蛇行スイープによる測量航線の生成
- 05航線の検証、事前チェック、アップロード後の照合
- 06stdio と streamable-http のトランスポート
活用シーン
- 01PX4 SITL での LLM によるドローン操作の検証
- 02測量用の往復航線の生成とレビュー
- 03ローカルまたはリモートの MCP クライアントからの飛行ツール呼び出し
MCP 対応
MCP サーバーとして、機体接続、航線計画、ミッション実行に関するツール、リソース、プロンプトを MCP クライアントに公開します。HTTP トランスポートには認証がないため、ネットワークアクセスは別途制御が必要です。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
MAVLink MCP サーバー
LLM エージェントがMAVLink対応機体(通常はMAVSDK経由のPX4)と通信するためのPython製Model Context Protocol(MCP)サーバーです。
前提条件
- Python 3.10以降
- MAVLinkエンドポイント(最初に試す方法としてPX4 SITLを推奨します。人間が介在しない状態で、信頼できないエージェントを実機に接続しないでください)
インストール
- リポジトリをクローンします。
git clone https://github.com/ion-g-ion/MAVLinkMCP.git
cd MAVLinkMCP
- プロジェクトをインストールします(このリポジトリには
pyproject.tomlとuv.lockが含まれています。ルートにrequirements.txtはありません)。
# with pip (editable)
pip install -e .
# or with uv
uv sync
設定(SITL/接続)
サーバーは次の値を読み取ります。
| 環境変数 | デフォルト | 意味 |
|---|---|---|
MAVLINK_ADDRESS | 空文字列 | 送信先のピアホストです。空の場合は代わりに待ち受けます。 |
MAVLINK_PORT | 14540 | UDPポート(PX4 SITLでは通常14540を使用します) |
MAVLINK_CONNECT_TIMEOUT | 60 | 到達不能と判断するまで、機体への接続を試みる秒数です |
MAVLINK_ADDRESSによってリンクの方向が決まります。
- 空(デフォルト) →
udpin://0.0.0.0:$MAVLINK_PORT。サーバーはポートをバインドして待ち受けます。これはPX4 SITLで必要な設定です。SITLは14540に送信し、接続先で待ち受けるプロセスを必要とします。 - ホストを設定 →
udpout://$MAVLINK_ADDRESS:$MAVLINK_PORT。サーバーは、実機のコンパニオンリンクなど、すでに待ち受けているピアに送信します。
ローカルのPX4での例:
export MAVLINK_PORT=14540
# MAVLINK_ADDRESS can stay empty: SITL talks to us, so we listen
リンクの確立はノンブロッキングです
MCPハンドシェイクは機体からの応答を待ちません。セッションはすぐに開始され、リンクはバックグラウンドで確立されるため、ツールは停止せずにリンク状態を報告します。
{"status": "failed", "error": "MAVLink link is still coming up; retry shortly", "connected": false, "link_state": "connecting"}
オートパイロットから応答があると、ツールは実際の結果を返すようになります。MAVLINK_CONNECT_TIMEOUT以内に応答がない場合は、接続失敗が確定し、試行したアドレスが示されます。
{"status": "failed", "error": "no MAVLink vehicle reachable on udpin://0.0.0.0:14540 after 60s", "connected": false, "link_state": "failed"}
GPS/ホーム位置の推定値を待つ処理は、意図的にこの判定に含めていません。ログには記録されますが、位置情報の収束によってコマンドがブロックされることはありません。
1セッションにつき機体リンクは1つです。 HTTPトランスポートでは、MCPセッションごとに独自のMAVSDK接続が開かれます。そのため、2つ目のクライアントが同じUDPポートをバインドすることはできず、上記のフェイルクローズ時のペイロードが返されます。
使い方
プロジェクトをインストールすると、環境にmavlinkmcpコンソールスクリプトが追加されます。stdio経由でMCPを提供します。
mavlinkmcp
# equivalently
python -m mavlinkmcp
別の作業ディレクトリから起動する
MCPクライアントには、コンソールスクリプトの絶対パスを指定してください。スクリプトのシバンにはプロジェクトのインタープリターが指定されているため、独自の環境を使用し、どの場所から起動しても動作します。
/abs/path/to/MAVLinkMCP/.venv/bin/mavlinkmcp
チャットアプリのstdio MCPサーバー設定には、この形式を指定します。
uv runは指定したパスではなく、現在のディレクトリからプロジェクトを解決する点に注意してください。そのため、別の場所からuv run /abs/path/to/src/mavlinkmcp/server.pyを実行すると、このプロジェクトの依存関係を含まない環境が構築され、インポート時に失敗します。uv経由で実行する場合は、プロジェクトを明示的に指定してください。
uv run --project /abs/path/to/MAVLinkMCP mavlinkmcp
uv --directory /abs/path/to/MAVLinkMCP run mavlinkmcp
HTTP経由で実行する
同じツールをstdioの代わりにネットワークソケット経由で提供できます。
# current MCP HTTP transport, on http://127.0.0.1:8000/mcp
mavlinkmcp --transport streamable-http
# pick the socket and endpoint path
mavlinkmcp --transport streamable-http --host 127.0.0.1 --port 9000 --path /drone
| オプション | 適用対象 | デフォルト | 意味 |
|---|---|---|---|
--transport | すべて | stdio | stdio、streamable-http、またはsse |
--host | HTTP | 127.0.0.1 | バインドするアドレス |
--port | HTTP | 8000 | バインドするポート |
--path | streamable-http | /mcp | エンドポイントのパス |
--allowed-host | HTTP | — | 受け入れるHostヘッダー。複数回指定でき、ワイルドカードも使用できます |
--allow-any-host | HTTP | オフ | Host/Originのチェックを完全に無効にします |
sseは旧式のトランスポートで、MCP仕様では非推奨です。sseが必要なクライアントを使用している場合を除き、streamable-httpを推奨します。--transport stdioでHTTPオプションを指定すると、無視されるのではなくエラーになります。
localhost 以外へのバインド
localhost にバインドする場合、DNS リバインディング対策(Host/Origin の検証)は自動的に有効になります。localhost 以外では自動判定できないため、許可する対象を指定する必要があります。指定しないとサーバーは起動を拒否します。
# declare the Host headers clients will send
mavlinkmcp --transport streamable-http --host 0.0.0.0 --port 8000 \
--allowed-host drone.lan:8000
# or turn the check off deliberately
mavlinkmcp --transport streamable-http --host 0.0.0.0 --allow-any-host
Host がリストにないリクエストには、421 Misdirected Request が返されます。
公開する前に
HTTP トランスポートには認証がありません。 ここでポートを開放するということは、機体のアーム、離陸、移動が可能になるポートを開放するということです。ローカルクライアントだけが通信できる stdio パイプよりも、実質的に大きなリスクを伴います。localhost 上にとどめるか、自分で管理するネットワークの内側に置き、独自の認証レイヤーを追加してください。
また、MAVSDK への接続はプロセスごとではなく、MCP セッションごとに確立されます。stdio ではクライアントプロセスごとに接続が 1 つです。HTTP では、接続するすべてのセッションが同じ機体への独自のリンクを開き、それらを調停する仕組みはありません。つまり、クライアントが 2 つあれば、1 機に対して独立したコマンド送信元が 2 つ存在します。どちらの場合もサーバーはただちにバインドし、起動時に機体を待つことはありません。
エージェントの使用例
examples/README.md を参照し、次を実行してください。
python examples/example_agent.py
# or
uv run examples/example_agent.py
examples/ の説明に従って LLM プロバイダーのキーを環境変数に設定してください(シークレットは絶対にコミットしないでください)。この例では fast-agent-mcp/FastAgent と mavlink_mcp サーバーエントリーを使用します。
フライトプラン
サーバーは、カバレッジミッションを生成、保存、検証し、飛行させることができます。この分割の目的は、ルートを機体に送る前に確認できるようにすることです。
get_map_view -> create_survey_plan -> render_plan_view
-> validate_plan -> preflight_check -> upload_plan
-> verify_uploaded_plan -> start_mission
地上の様子を見る
get_map_view は、正確な変換情報とともに、ジオリファレンス付きの衛星画像を画像として返します。画像内の地物を特定し、その四隅をピクセル座標で報告すると、サーバーが座標を変換します。ビジョンモデルはフィールドを指し示すのは得意ですが、緯度を推測するのは苦手です。そのため、緯度を考える必要はありません。
get_map_view() # centred on the vehicle
-> [image, {view_id, center, meters_per_pixel, drone: {pixel, heading_deg}, ...}]
create_survey_plan(name="Front Field", view_id=..., polygon_pixels=[[430,180], ...],
altitude_m=40, camera={...})
map_transform は、保存されたビューに対してどちらの方向にも変換します。orientation="heading_up" を指定すると、機体の進行方向が上になるようにビューを回転させます。これにより、「ドローンの前にあるフィールドはどこか」を、方位の計算ではなく画像上で確認できます。
latitude_deg/longitude_deg を指定すると、任意の場所を表示できます。機体は必要ありません。プランは机上で作成してコストを算出し、後で飛行させることができます。
地図画像
ベースマップの取得は、このサーバーで唯一の外部ネットワーク通信です。タイルは必要に応じて取得され、ディスクにキャッシュされ、設定済みプロバイダーのホストに固定されます。タイルの取得に失敗すると、平坦なグレーで描画され、その旨がレスポンスに示されます。ジオリファレンスの精度は変わりません。
| 変数 | 意味 |
|---|---|
MAVLINKMCP_MAP_PROVIDER | esri(デフォルト、全世界の航空画像、キー不要)・osm(道路地図、画像ではありません)・mapbox/maptiler(キーが必要)・custom・none |
MAVLINKMCP_MAP_API_KEY | キーが必要なプロバイダー用のキー |
MAVLINKMCP_MAP_TILE_URL | custom 用の XYZ テンプレート。例:セルフホストのタイルサーバー |
MAVLINKMCP_MAP_IMAGE_MODE | image(デフォルト)または path。画像を表示できないクライアント向け |
none にするとすべてのネットワークアクセスが無効になり、ジオリファレンスが正確な空白のキャンバスが描画されます。ジオメトリとオーバーレイは引き続き使用できますが、地上の地物を特定するための画像はありません。通信できない場所へ行く前に prefetch_map_area を実行すると、タイルキャッシュを事前に準備できます。
帰属表示は必須です。 各プロバイダーの利用規約で義務付けられています。通知はすべてのビューに描画され、ペイロードにも含まれます。
ビューは JPEG として描画されます。デフォルトは 768 px で、上限は 1024 px です。この上限はバイト数ではなくトークン数を考慮したものです。画像のコストはピクセル面積に応じて増えるため、768 px のビューは約 790 トークン、1024 px のビューは約 1400 トークンです。
プランの保存場所
$MAVLINKMCP_PLANS_DIR/ # default $XDG_DATA_HOME/mavlinkmcp
├── plans/north-field/
│ ├── meta.json # name, head revision, timestamps
│ ├── rev-001.json
│ └── rev-002.json
├── views/ # rendered map views + their geotransforms
└── tiles/ # tile cache
プランは通常の JSON 形式で、MCP セッション終了後も保持されるため、このサーバーを起動しなくても、人が読み取り、差分を確認し、編集できます。
リビジョンの生成コンテンツは不変です。revise_plan は、新しいパラメーターでジェネレーターを再実行し、新しいリビジョンを作成します。ウェイポイントを編集することはないため、保存されたパラメーターと保存された経路が食い違うことはありません。インプレースで変更されるのは、ライフサイクルの注釈(status、checks、estimate、uploaded)だけです。
アップロードのゲート
プランは draft -> validated -> uploaded と進み、upload_plan は validate_plan に合格していないプランのアップロードを拒否します。どのリビジョンもドラフトから始まるため、変更を加えると必ず以前のチェックは無効になります。
validate_plan は検出事項を報告し、アップロードをブロックするのは error だけです。最も重要なのは FAR_FROM_HOME です。間違った地図上にポリゴンを描くと、世界の反対側に、形式としては完全に正しいプランができてしまいます。ホームからの距離を調べることで、これを検出できます。次に preflight_check が、ヘルス状態、GPS 測位、バッテリー、着陸状態に基づくリアルタイムの飛行可否判定を追加します。また、verify_uploaded_plan は機体からミッションをダウンロードし、レビュー済みの内容と差分を確認します。
プランのライフサイクル状態は plan_status として報告されます。status キーは、このサーバーのすべてのツールが返す呼び出しのエンベロープ(success / failed)を示すもので、こちらはそのまま維持されます。
測量ジオメトリー
create_survey_plan は、往復走査(芝刈りパターン)の経路を生成します。ライン間隔は line_spacing_m または camera から決まり、両方を指定すると、片方を暗黙に優先するのではなく、リクエストが拒否されます。カメラを指定すると、写真撮影のトリガー距離も設定され、地上サンプル距離も報告されます。
camera = {"sensor_width_mm": 13.2, "focal_length_mm": 8.8,
"image_width_px": 5472, "image_height_px": 3648,
"front_overlap": 0.75, "side_overlap": 0.65}
# at 40 m: 1.10 cm/px, 60 m footprint, 21 m line spacing, 10 m trigger distance
sweep_angle_deg を省略すると、エリアの長軸に沿って走査します。旋回回数を最小限にできるため、測量時間とバッテリーの大部分を節約できます。
安全に関する注意
MCP ツールを使うと、機体のアーム、離陸、移動を行えます。SITL を優先してください。緊急停止スイッチを操作する、または着陸させる準備のできた人を配置してください。ツールの失敗時は、クライアント側でフェイルクローズとして扱ってください。
フライトプランはレビュー手順を追加するものであり、レビューの必要性をなくすものではありません。validate_plan と preflight_check で検出できるのは、機械的に判断できるミス(誤った場所の経路、GPS 測位がない、バッテリー不足)であり、判断を要するミスではありません。飛行前に render_plan_view を確認してください。
プロンプト
ツールの説明には、1 回の呼び出しで何が行われるかが記載されています。しかし、パイプラインが分割されている理由や、upload_plan がドラフトを拒否する理由、モデルが緯度ではなくピクセルで答えるべき理由を説明するものはありません。こうした背景情報は MCP のプロンプトとして提供され、多くのクライアントではスラッシュコマンドとして表示されます。
| プロンプト | 内容 |
|---|---|
mavlink_overview | サーバーの構成、status / plan_status のエンベロープ、バックグラウンドリンク、安全方針 |
telemetry_guide | 取得できる測定値と、読み取りの失敗がゼロではなく不明を意味する理由 |
map_view_guide | ジオリファレンスされた画像:緯度ではなくピクセル、向き、トークンコスト、プロバイダー、帰属表示 |
plan_lifecycle_guide | プランの状態、不変のリビジョン、アップロードゲートの目的 |
survey_walkthrough(area?, altitude_m?) | 地上の確認からミッションの実行までの一連の流れ |
preflight_briefing(plan_id) | 保存済みプラン 1 件に対して実行する飛行可否判定の手順 |
文面は guides.py に記載されており、制限値はそれを適用するモジュールから補間されます。そのため、ブリーフィングで示される数値が、実際にチェックされる数値と食い違うことはありません。
リソース
ディスク上にすでに存在する情報を読み取り専用で記述した、URI でアドレス指定可能なリソースです。ツールの呼び出しにはトークンコストがかかりますが、リソースはクライアントが独自に参照できます。これらの取得処理でタイルを取得したり、機体にアクセスしたりすることはありません。
| URI | 内容 |
|---|---|
mavlinkmcp://map/providers | 既知のすべてのタイルプロバイダーと、帰属表示を含む有効な設定 |
mavlinkmcp://map/limits | マップ呼び出しで検証される上限:サイズ、半径、ズーム、タイル上限、ストアの上限 |
mavlinkmcp://map/cache | タイルキャッシュとビューのストアに保存されている内容と、その保存場所 |
mavlinkmcp://map/views | ディスク上に残っている、レンダリング済みのすべてのビュー。新しいものから順に表示 |
mavlinkmcp://map/views/{view_id} | 1 つのビューの完全な地理参照情報:中心、縮尺、回転、バウンディングボックス、地上の四隅 |
mavlinkmcp://map/views/{view_id}/image | レンダリング済みの JPEG。タイルを再取得せずにビューを再表示できます |
mavlinkmcp://plans/{plan_id}/geojson | プランの最新リビジョンを GeoJSON 形式で表示:エリア、パス、番号付きウェイポイント |
API キーがリソースに表示されることはありません。 セッション内のすべてのクライアントがすべてのリソースを読み取れるため、プロバイダーブロックではキーが設定されているかどうかのみを報告し、キーの値は決して表示しません。また、タイルテンプレートからは出力時にクエリー文字列を除去します。
インデックスリソースは、設定の誤りがあっても例外を発生させず、ペイロード内で報告します。mavlinkmcp://map/providers を読む人が求めているのは、「プロバイダーに必要なキーが設定されていない」という情報だからです。ID でアドレス指定するリソースは例外を発生させます。存在しないビューについて、部分的な回答はありません。
テスト
ユニットテストはオフラインで実行されます。純粋な検証、ジオメトリ、正規化のヘルパーをテストし、mavsdk をインポートせず、機体と通信せず、ネットワークリクエストも行いません。タイル取得は偽のフェッチャーを注入してテストし、レンダリング処理は MAVLINKMCP_MAP_PROVIDER=none のもとで実行します。リポジトリのルートから次のコマンドを実行してください。
python -m unittest discover
テストでは実際のパッケージパスからヘルパーをインポートするため(from mavlinkmcp.endpoint import ...)、テストの実行環境にプロジェクトをインストールしておく必要があります(uv sync または pip install -e .)。
SITL 統合テスト
tests_sitl/ では、オフラインテストでは構造上テストできない MAVSDK のハンドシェイク、接続ガード、ミッションプロトコルの往復を検証します。実際のオートパイロットが必要となるため、sitl/ は SIH 動力学モデルを実行する PX4 イメージをビルドします。Gazebo も GPU も不要で、約 120 MB、RAM は約 7 MB です。
docker build -t px4-sih:v1.14.3 sitl/
docker run -d --rm --name px4 --network host \
-e PX4_HOME_LAT=473977420 -e PX4_HOME_LON=85455940 \
px4-sih:v1.14.3
python -m unittest discover
network_mode: host は必須です。PX4 はループバック上の 14540 番ポートに送信し、MAV_2_BROADCAST=1 がないと localhost の外とは通信しないため、ポートを公開しても役に立ちません。これは Linux と GitHub のランナーでは動作しますが、macOS の Docker Desktop では動作しません。
ホーム座標は 小数度ではなく、1e-7 度単位のスケーリング済み int32 です。px4-rc.simulator は PX4_HOME_LAT をそのまま SIH_LOC_LAT0 に渡します。47.397742 を渡すと 47 に切り捨てられ、機体は気付かないうちに 150 km 離れた場所を飛行します。
機体から応答がない場合、これらのテストは失敗ではなくスキップされます(約 1.5 秒のプローブ後)。そのため、Docker がないマシンでも python -m unittest discover は引き続き実行できます。コンテナーの起動が遅く、検出されない場合は MAVLINKMCP_SITL_PROBE_TIMEOUT を大きくしてください。
コントリビューション
コントリビューションを歓迎します。リポジトリをフォークし、プルリクエストを送信してください。
ライセンス
このプロジェクトは MIT ライセンスのもとでライセンスされています。詳細は LICENSE ファイルを参照してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。