8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-04-27

🛫 ドローン操縦の未来を、"自然言語"で

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

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

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

論よりRUN!!

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

📺 デモ動画はこちら →

仕組み

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サーバーを作ってみて気づいたこと

今回、初めて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」 をモットーに、小さな一歩を形にすることができました!

次は…

今後の展望(TODO)

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

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

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

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

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?