
Claude Desktop × MCP で Unity ゲームを LLM に自動プレイさせる構成を試してみた
対象読者
- 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
ゲームの仕様
このゲームは、3x3 のグリッド上でプレイヤーが移動し、鍵を拾って扉を開けることでクリアとなるシンプルな構成です。
- プレイヤー初期位置:
(0, 0)
- 鍵の位置:
ランダム
- 扉の位置:
ランダム
- 勝利条件: 鍵を所持した状態で扉のマスに到達する
移動は上下左右 (north, south, east, west) に 1 マスずつ可能です。
構成図
今回の構成では、Claude Desktop と Unity ゲームの間でコマンドをやりとりするために、以下のような流れを採用しました:
- Unity アプリに HttpListener を用意する
- TypeScript で MCP Server を建て Unity アプリに送信
- Claude Desktop から Mcp Server にアクセス
それぞれ簡単に紹介します。
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 が表示されます。
実際にやってみた
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"} を渡しています。
実際のログ
ゲームをクリアして
こんにちは!どうやらシンプルな冒険ゲームをプレイされたいようですね。まずは現在のゲーム状態を確認してみましょう。
{}
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) との連携も検討