火曜日, 4月 29, 2025
ホームニューステックニュースドローンシミュレータ(箱庭)をMCPサーバー/Claude連携した! #Claude - Qiita

ドローンシミュレータ(箱庭)をMCPサーバー/Claude連携した! #Claude – Qiita



ドローンシミュレータ(箱庭)をMCPサーバー/Claude連携した! #Claude - Qiita

「ドローンを飛ばして!」とAI(Claude)に話しかけるだけで、
仮想空間上でリアルなドローンを自在に操縦できたら──。

そんな夢を、箱庭ドローンシミュレータ × MCPサーバー × Claude連携で実現しました!

本記事では、
箱庭ドローンシミュレータをMCPサーバー化し、Claude(AI)から自然言語コマンドで操縦できる仕組みを紹介します。

まずは、動く様子をご覧ください!

📺 デモ動画はこちら →

image.png

全体アーキテクチャ上図の通りです。

この連携で重要なキーとなる技術要素は、たった2つだけ。

  • Claude.ai は、MCPプロトコルで MCPサーバーと通信できる
  • 箱庭ドローンシミュレータは、Python API経由でドローンを操作できる

つまり、
「自然言語 → MCP経由コマンド → Python API → ドローン操作」
というシンプルな連携構成が実現できた、というわけです!

箱庭MCPサーバー

箱庭はPythonと相性が良いので、今回作成したMCPサーバーはPythonで作っています。

こちらの記事を参考にして作成しました(感謝)。

今回やりたいことは、ツールの外部起動になりますので、list_toolsとcall_toolのみを追加することで対応しました。

コード断片:


@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="describe_hakoniwa_drone",
            description="Provides an overview of the Hakoniwa Drone Simulator",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            },
        ),
        types.Tool(
            name="hakoniwa_drone_simulator_start",
            description="Starts the Hakoniwa Drone Simulator",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            }
        ),
        types.Tool(
            name="hakoniwa_drone_simulator_stop",
            description="Stops the Hakoniwa Drone Simulator",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            }
        ),
        types.Tool(
            name="provide_hakoniwa_drone_manual",
            description="Provides the Hakoniwa Drone Simulator operation manual (PDF)",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            }
        ),
        types.Tool(
            name="drone_command_takeoff",
            description="Executes a takeoff command on the drone simulator",
            inputSchema={
                "type": "object",
                "properties": {
                    "height": {
                        "type": "number",
                        "description": "The target height for takeoff (default 1.0m)"
                    }
                },
                "required": []
            }
        ),
        types.Tool(
            name="drone_command_move_to_position",
            description="Move drone to specified (x, y, z) position (ROS coordinate system, meters)",
            inputSchema={
                "type": "object",
                "properties": {
                    "x": {"type": "number", "description": "Forward direction, meters"},
                    "y": {"type": "number", "description": "Left direction, meters"},
                    "z": {"type": "number", "description": "Upward direction, meters"},
                    "speed": {"type": "number", "description": "Movement speed, meters/second"}
                },
                "required": ["x", "y", "z", "speed"]
            }
        ),
        types.Tool(
            name="drone_command_land",
            description="Land the drone",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            }
        )    

    ]

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict | None) -> list[types.TextContent]:
    if name == "describe_hakoniwa_drone":
        return [
            types.TextContent(
                type="text",
                text=(
                    "Hakoniwa Drone Simulatorは、仮想環境上でドローンの自律飛行や荷物輸送タスクを模擬できる高機能シミュレータです。"
                    "ROS座標系およびUnity座標系の両方に対応し、リアルタイムなカメラ画像取得、Lidarデータ取得、API制御による移動・離着陸・物体把持が可能です。"
                    "訓練用途、研究用途、またはAI開発向けのプロトタイピングに適しています。"
                )
            ),
        ]
    elif name == "hakoniwa_drone_simulator_start":
        return [
            types.TextContent(
                type="text",
                text=hakoniwa_drone_simulator_start()
            )
        ]
    elif name == "hakoniwa_drone_simulator_stop":
        return [
            types.TextContent(
                type="text",
                text=hakoniwa_drone_simulator_stop()
            )
        ]
    elif name == "provide_hakoniwa_drone_manual":
        return [
            types.TextContent(
                type="text",
                text=(
                    "箱庭ドローンシミュレータの公式マニュアルはこちらです!\n"
                    "👉 [Hakoniwa Drone Manual PDF](https://www.jasa.or.jp/dl/tech/simulator_operation_edition.pdf)"
                )
            )
        ]
    elif name == "drone_command_takeoff":
        height = 1.0
        if arguments and "height" in arguments:
            height = float(arguments["height"])
        return [
            types.TextContent(
                type="text",
                text=drone_command("takeoff", [str(height)])
            )
        ]

    elif name == "drone_command_move_to_position":
        if arguments is None:
            raise ValueError("Arguments required for moveToPosition")
        x = str(arguments["x"])
        y = str(arguments["y"])
        z = str(arguments["z"])
        speed = str(arguments["speed"])
        return [
            types.TextContent(
                type="text",
                text=drone_command("moveToPosition", [x, y, z, speed])
            )
        ]

    elif name == "drone_command_land":
        return [
            types.TextContent(
                type="text",
                text=drone_command("land")
            )
        ]
    else:
        raise ValueError(f"Unknown tool: {name}")

今回、初めてMCPサーバーを作ってみて、いくつかハマりポイントがありましたので、共有しておきます!

標準出力にメッセージを出してはいけない

Claude側(MCPクライアント)は、標準出力を純粋なJSON通信に使っています。

そのため、print() などでログを出すと、JSONパースエラーになり通信が途絶えます。

➔ ログ出ししたい場合は、ファイルに書き出すか、サーバー側だけで完結させる必要あり。

ちなみに、今回は、箱庭のPython APIを外部プロセスとして外出して、subprocess.Popen()で実行かける対応をしました。

環境変数(PYTHONPATH / DYLD_LIBRARY_PATH)に注意

サーバープロセスを起動する際、Unityや箱庭ライブラリにパスを通しておかないと、モジュールインポートや動的ライブラリ読込みに失敗します。

➔ subprocess.Popen()のときに、環境変数を適切に引き継ぐ必要あり。

MCPサーバーからUnityプロセスの起動で落ちる…

Unityプロセスをバックグラウンドで起動しても、バックグラウンド実行コマンドが終了すると、一緒に落ちてしまうことがわかりました。

そのため、Unityを起動するためのデーモンプロセスを用意して、MCPサーバーと通信させて今回のデモを実現させています。

ドローンシミュレータ(箱庭)をMCPサーバー化し、
Claudeと自然言語で連携できる未来を手に入れました!

まだまだ機能追加や改善の余地はたくさんありますが、
まずは 「論よりRUN」 をモットーに、小さな一歩を形にすることができました!

次は…

🛫 自律飛行・WayPoint巡回モードを追加したい

🎒 荷物輸送ミッション(グラブ&リリース)に挑戦したい

🎥 飛行中のカメラ画像を取得して、AI解析に使いたい

🤖 Claudeともっと高度な「ドローン対話」シナリオを作りたい



フラッグシティパートナーズ海外不動産投資セミナー 【DMM FX】入金

Source link

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -

Most Popular

Recent Comments