Claude Desktop × MCP で Unity ゲームを LLM に自動プレイさせる構成を試してみた

Claude Desktop × MCP で Unity ゲームを LLM に自動プレイさせる構成を試してみた

Claude Desktop から Unity ゲームを操作する MCP Server を TypeScript で実装しました。LLM に Tool を定義し、自然言語でゲームを進行させる構成を検証しています。

対象読者

  • LLM を使ってアプリケーションを操作してみたい人
  • Unity ゲームと外部ツールを連携させたい人
  • Claude Desktop や MCP (Model Context Protocol) に興味がある人

はじめに

最近、Claude Desktop や GitHub Copilot の Agent モードなど、LLM に外部ツールを使わせる構成が注目されています。その中核にあるのが MCP (Model Context Protocol) です。この記事では、MCP の思想に沿って、Claude Desktop から Unity ゲームを操作する MCP Server を TypeScript で実装してみました。

キーワード: MCP / Unity / 自動デバッグ / 自動テスト / ゲームプレイ AI エージェント

MCP とは

MCP (Model Context Protocol) は、LLM に対して以下のことを可能にするプロトコルです:

  • 外部ツール (Tool) を呼び出す
  • リソース (ファイルやデータ) を取得する
  • プロンプト (指示文) を提供する

MCP の特徴は以下の通りです:

  • Tool の定義は JSON スキーマで記述される
  • 通信は STDIO または HTTP+SSE で行われる
  • LLM 側 (MCP Client) は Tool の一覧を取得し、文脈に応じて適切な Tool を選択して呼び出す

MCP Server は「LLM に使ってもらうための軽量なプログラム」であり、AI エージェントのような存在と捉えるとわかりやすいです。

「MCP × ゲーム」が産み出す価値

MCP とゲームの組み合わせによって、以下のような価値が生まれます:

  • デバッグの効率化
    LLM に「この状態でどう動くべきか?」と尋ねながら、ゲームの挙動を確認できる

  • 自動プレイによるバランス調整
    LLM に何千回もプレイさせて、勝率や行動パターンを分析し、ゲームバランスの調整に活用できる

  • 非エンジニアとの共同開発
    LLM を介して自然言語でゲームの状態を確認・操作できるため、プランナーやデザイナーとの連携がスムーズになる

Unity ゲームを Claude Desktop に操作させてみた

今回の実験では、Unity で作成した 3x3 グリッドのシンプルなゲームを、Claude Desktop に操作させてみました。

  • プレイヤーは上下左右に移動可能
  • 鍵を拾って、扉を開けるとクリア
  • ゲームの状態はテキストで表現され、JSON 形式で返される
Player: (1,1)
HasKey: true
Key: (1,0)
Door: (1,1)
LastInput: move north
Status: cleared

1

ゲームの仕様

このゲームは、3x3 のグリッド上でプレイヤーが移動し、鍵を拾って扉を開けることでクリアとなるシンプルな構成です。

  • プレイヤー初期位置: (0, 0)
  • 鍵の位置: ランダム
  • 扉の位置: ランダム
  • 勝利条件: 鍵を所持した状態で扉のマスに到達する

移動は上下左右 (north, south, east, west) に 1 マスずつ可能です。

構成図

今回の構成では、Claude Desktop と Unity ゲームの間でコマンドをやりとりするために、以下のような流れを採用しました:

  1. Unity アプリに HttpListener を用意する
  2. TypeScript で MCP Server を建て Unity アプリに送信
  3. Claude Desktop から Mcp Server にアクセス

6

それぞれ簡単に紹介します。

Unity 側の準備

Unity 側では、HttpListener を使って簡易的な HTTP サーバーを立てています。MCP Server からのリクエスト (例: move north) を受け取り、ゲームを進行し、状態を返します。

{
  "role": "system",
  "name": "game_state",
  "content": "Player: (1,0)\nHasKey: true\nKey: (0,-1)\nDoor: (2,2)\nLastInput: move east\nStatus: in_progress"
}

TypeScript で MCP Server を実装する

MCP Server の役割

MCP Server は、Claude Desktop (MCP Client) から呼び出される「Tool」を提供する軽量なプログラムです。今回は、Unity ゲームを操作するための "move" "game_state" Tool を定義し、プレイヤーを上下左右に動かしたりゲーム内の状況を取得したりできるようにします。

Tool 定義

Tool とは、LLM (Claude) が「このプロンプトにはこの処理が必要だ」と判断したときに呼び出す外部処理のことです。MCP Server 側では、Tool の名前・説明・引数スキーマ・実行関数を定義します。Claude は Tool の一覧を取得し、プロンプトに応じて適切な Tool を選択して呼び出します。

このコードは、MCP Server を初期化し、"move" "get_state" という Tool を定義して、Unity 側の HTTP サーバーにリクエストを送る構成です。Tool の定義には zod を使って引数のバリデーションを行っています。

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch";

const server = new McpServer({
  name: "UnityController",
  version: "0.1.0",
});

// Tool: プレイヤーを移動させる
server.tool(
    "move",  // Tool 名
    "Move the player in a specified direction.",  // 説明
    {
        direction: z.enum(["north", "south", "east", "west"]),  // 引数スキーマ
    },
    async ({ direction }) => {
        const res = await fetch("http://127.0.0.1:8080/mcp", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                role: "user",
                name: "player_move",
                content: `move ${direction}`,
            }),
        });

        const json = await res.json() as {
            role: string;
            name: string;
            content: string;
        };
        return {
            content: [
                {
                    type: "text",
                    text: json.content,
                },
            ],
        };
    }
);

// Tool: ゲームの状態を取得する
server.tool(
    "get_state",  // Tool 名
    "Get the current game state.",  // 説明
    {},  // 引数スキーマ
    async () => {
        const res = await fetch("http://127.0.0.1:8080/mcp", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                role: "user",
                name: "player_move",
                content: "get_state",
            }),
        });

        const json = await res.json() as {
            role: string;
            name: string;
            content: string;
        };
        return {
            content: [
                {
                    type: "text",
                    text: json.content,
                },
            ],
        };
    }
);

// MCP Server を起動
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server running on stdio");
}

main().catch((err) => {
  console.error("Fatal error:", err);
  process.exit(1);
});

Claude Desktop に MCP Server を登録

Claude Desktop に MCP Server を登録するには、設定ファイル claude_desktop_config.json に以下のように記述します:

{
  "mcpServers": {
    "unity-controller": {
      "command": "node",
      "args": [
        "/absolute/path/to/build/index.js"
      ]
    }
  }
}

設定を保存したら、Claude Desktop を再起動します。正常に認識されると、設定画面に登録した MCP Server が表示されます。

7

実際にやってみた

Claude Desktop に以下のように入力してみます:

ゲームをクリアして

以下の GIF は、Claude が "move" "gate_state" Tool を使って Unity ゲームを自動でプレイしている様子です。Claude はプロンプトの意図を理解し、"gate_state" Tool を呼び出します。 MCP Server は、Unity 側の HTTP サーバーに "get_state" を送信します。Unity 側は現在の状態を JSON で返します。Claude はその状態を読み取り、次の行動を判断します。今回は東に鍵が見つかったので、次に "move" Tool を呼び出し、引数として {"direction": "east"} を渡しています。

TestMcpGridGame3

実際のログ

ゲームをクリアして

こんにちは!どうやらシンプルな冒険ゲームをプレイされたいようですね。まずは現在のゲーム状態を確認してみましょう。

{}
Player: (0,0)
HasKey: false
Key: (1,0)
Door: (1,1)
LastInput: move unknown
Status: in_progress

現在の状態は次のとおりです:

  • あなたは座標(0,0)にいます
  • 鍵は座標(1,0)にあります (まだ持っていません)
  • ドアは座標(1,1)にあります
  • ゲームの状態: 進行中

ゲームをクリアするには、まず鍵を取りに行って、それからドアに向かう必要があります。

まず東に移動して鍵を取りましょう:

{
  `direction`: `east`
}
Player: (1,0)
HasKey: true
Key: (1,0)
Door: (1,1)
LastInput: move east
Status: in_progress

素晴らしい!東に移動して鍵を入手しました。

次に北に移動してドアを目指しましょう:

{
  `direction`: `north`
}
Player: (1,1)
HasKey: true
Key: (1,0)
Door: (1,1)
LastInput: move north
Status: cleared

おめでとうございます!ゲームをクリアしました!

ゲームの最終状態:

  • あなたは座標(1,1)にいます (ドアのある場所)
  • 鍵を持っています
  • ゲームステータス: クリア完了

シンプルな冒険を楽しんでいただけたでしょうか?他に何かお手伝いできることがあれば、お気軽にお知らせください。

感想と今後の展望

Claude Desktop × MCP Server × Unity という構成で、LLM に自然な形でアプリケーションを操作させることができました。特に印象的だったのは、Tool を定義するだけで、Claude が文脈に応じて適切に呼び出してくれる点です。Tool の説明やスキーマを工夫することで、LLM の行動をより自然に制御できることがわかりました。

今後の展望

  • reset_game, take_key, open_door などの Tool を追加して、より複雑なゲーム制御を実現
  • Unity 側の状態を JSON で詳細に返すことで、LLM の判断精度を向上させる
  • Claude Desktop 以外の MCP Client (例: VS Code, Copilot Agent) との連携も検討

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.