Unity

Unity × Claude MCP構築 (実践編: 読み書き・自律エージェント化)

Claude_Unity_MCP

前回はUnityのシーン情報を読み取るだけの「Read-Only」な構成を作りましたが、今回はさらに踏み込んで「Claudeにコードを書かせたり、Unityプロジェクトの中身を詳しく調査させる」ための本格的な環境構築を行います。

これまでは「外から見るだけ」でしたが、今回は「中に入って作業してもらう」構成になります。

今回の強化ポイント

  1. Unity情報の詳細化: 親子構造やアタッチされているコンポーネントまで把握させる。
  2. ファイル操作権限の付与: プロジェクト内のC#スクリプト一覧を見たり、中身を読んだり、ファイルを新規作成・修正させる機能を追加します。
  3. トークン節約: シーン全体だとAIの容量(トークン)がパンクするため、「選択したオブジェクトだけ」を連携する仕組みに変更します。

1. Unity側の改修 (McpExporter.cs)

前回のスクリプトでは「ルートオブジェクトの名前」しか分かりませんでした。
これでは「どのアバターに何のスクリプトが付いているか」が分かりません。
そこで、「選択したオブジェクトの階層を深く潜り(再帰)、コンポーネント情報も含めて書き出す」ように改良します。

Assets/Editor/McpExporter.cs を以下に書き換えます。

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
using System.Collections.Generic;

public class McpExporter : MonoBehaviour
{
    // 保存先
    private static string FilePath => Path.Combine(Directory.GetParent(Application.dataPath).FullName, "mcp_hierarchy.json");

    // 【重要】シーン全部だと重すぎるので「選択したものだけ」書き出す仕様に変更
    [MenuItem("MCP/Export Selected Objects")]
    public static void ExportSelected()
    {
        var selectedObjects = Selection.gameObjects;

        if (selectedObjects.Length == 0)
        {
            Debug.LogWarning("[MCP] オブジェクトが選択されていません。Hierarchyで対象を選択してください。");
            return;
        }

        var sb = new StringBuilder();
        sb.AppendLine("{ \"mode\": \"SelectedObjects\", \"objects\": [");

        for (int i = 0; i < selectedObjects.Length; i++)
        {
            Traverse(selectedObjects[i].transform, sb, 1);
            if (i < selectedObjects.Length - 1) sb.AppendLine(",");
        }

        sb.AppendLine("\n] }");

        // UTF-8 (BOMなし) で保存
        File.WriteAllText(FilePath, sb.ToString(), new UTF8Encoding(false));
        Debug.Log($"[MCP] 選択された {selectedObjects.Length} 個の情報を書き出しました: {FilePath}");
    }

    // 再帰的に子要素とコンポーネントを探索
    private static void Traverse(Transform tf, StringBuilder sb, int indentLevel)
    {
        string indent = new string(' ', indentLevel * 2);
        
        // 座標は桁数を丸めてトークン節約
        string posStr = $"({tf.position.x:F2}, {tf.position.y:F2}, {tf.position.z:F2})";

        sb.Append($"{indent}{{ \"name\": \"{EscapeJson(tf.name)}\", ");
        sb.Append($"\"active\": {tf.gameObject.activeInHierarchy.ToString().ToLower()}, ");
        sb.Append($"\"position\": \"{posStr}\", ");

        // コンポーネント一覧を取得(Transform以外)
        var components = tf.GetComponents<Component>();
        List<string> compNames = new List<string>();
        foreach (var c in components)
        {
            if (c != null && !(c is Transform)) compNames.Add(c.GetType().Name);
        }
        sb.Append($"\"components\": [{string.Join(", ", compNames.ConvertAll(c => $"\"{c}\""))}], ");

        // 子オブジェクトがあれば再帰処理
        if (tf.childCount > 0)
        {
            sb.AppendLine($"\"children\": [");
            for (int i = 0; i < tf.childCount; i++)
            {
                Traverse(tf.GetChild(i), sb, indentLevel + 1);
                if (i < tf.childCount - 1) sb.AppendLine(",");
            }
            sb.Append($"{indent}]");
        }
        else
        {
            sb.Append($"\"children\": []");
        }

        sb.Append(" }");
    }

    private static string EscapeJson(string str)
    {
        if (string.IsNullOrEmpty(str)) return "";
        return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
    }
}

これで、Claudeに見せたいオブジェクト(例えばアバターのルート)を選択して 「MCP > Export Selected Objects」 を押すだけで、詳細な構造を渡せるようになりました。

2. Pythonサーバーの強化 (server.py)

ここが今回の目玉です。 Claudeに「ファイルシステム」へのアクセス権を与え、VSCodeのように振る舞えるようにします。
具体的には以下の3つの機能(Tool)を追加します。

  1. list_scripts: どんなC#ファイルがあるか一覧を見る。
  2. read_file: 指定したファイルの中身を読む。
  3. write_file: ファイルを新規作成・上書き保存する。

server.py」を以下のように大幅アップデートします。

from fastmcp import FastMCP
import os

# サーバー定義
mcp = FastMCP("UnityContext")

# パス設定(server.pyの位置を基準に絶対パス生成)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ASSETS_DIR = os.path.join(BASE_DIR, "Assets")
HIERARCHY_FILE = os.path.join(BASE_DIR, "mcp_hierarchy.json")

# --- Resource: シーン情報の提供 ---
@mcp.resource("unity://hierarchy")
def get_hierarchy() -> str:
    """Unityの現在のシーン階層構造(Hierarchy)を読み取ります。"""
    if not os.path.exists(HIERARCHY_FILE):
        return "エラー: データなし。Unityで書き出しを実行してください。"
    try:
        with open(HIERARCHY_FILE, "r", encoding="utf-8") as f:
            return f.read()
    except Exception as e:
        return f"エラー: {str(e)}"

# --- Tool 1: ファイル一覧取得 ---
@mcp.tool()
def list_scripts() -> str:
    """
    Assetsフォルダ内にある C# スクリプト(.cs) の一覧を取得します。
    ファイル構成を把握し、既存のクラス名を知るために使用します。
    """
    if not os.path.exists(ASSETS_DIR):
        return "エラー: Assetsフォルダが見つかりません。"

    file_list = []
    for root, dirs, files in os.walk(ASSETS_DIR):
        for file in files:
            if file.endswith(".cs"):
                # フルパスからAssets以下の相対パスに変換
                full_path = os.path.join(root, file)
                rel_path = os.path.relpath(full_path, BASE_DIR)
                file_list.append(rel_path)
    
    return "\n".join(file_list) if file_list else "スクリプトが見つかりませんでした。"

# --- Tool 2: ファイル読み込み ---
@mcp.tool()
def read_file(path: str) -> str:
    """
    指定されたパスのファイルの中身を読み取ります。
    コードの修正や確認を行う前に必ず使用してください。
    path: Assets/ から始まるファイルパス
    """
    target_path = os.path.join(BASE_DIR, path)
    if not os.path.exists(target_path): return "エラー: ファイルが見つかりません。"
    if "Assets" not in os.path.abspath(target_path): return "エラー: Assets外へのアクセス禁止。"

    try:
        with open(target_path, "r", encoding="utf-8") as f:
            return f.read()
    except Exception as e:
        return f"読み込みエラー: {str(e)}"

# --- Tool 3: ファイル書き込み(作成・修正) ---
@mcp.tool()
def write_file(path: str, content: str) -> str:
    """
    指定されたパスにファイルを作成、または上書き保存します。
    新規スクリプトの作成や、コードの修正に使用します。
    
    path: Assets/ から始まるファイルパス (例: Assets/Scripts/New.cs)
    content: 書き込む完全なテキスト内容
    """
    target_path = os.path.join(BASE_DIR, path)
    if "Assets" not in os.path.abspath(target_path): return "エラー: Assets外への書き込み禁止。"

    # フォルダがなければ自動作成
    os.makedirs(os.path.dirname(target_path), exist_ok=True)

    try:
        with open(target_path, "w", encoding="utf-8") as f:
            f.write(content)
        return f"成功: 保存しました: {path}"
    except Exception as e:
        return f"書き込みエラー: {str(e)}"

if __name__ == "__main__":
    mcp.run()

仕組みの解説

ここで重要なのが @mcp.tool() とその下の コメント(Docstring) です。 Claudeはコードのロジックは見えませんが、「"""...""" で書かれた説明文を読んで「今どの道具(関数)を使うべきか?」を自分で判断します。

  • ユーザー「このスクリプト直して」
  • Claude「(中身を知る必要があるな… read_file を使おう)」
  • Claude「(修正案ができた… write_file で上書きしよう)」

3. 実際の運用フロー

準備ができたら、Claude Desktopを再起動して試してみましょう。

  1. Unity: 対象のオブジェクトを選択して「MCP > Export Selected Objects」。
  2. Claude: 「unity-connect」から Hierarchy情報を添付。
  3. 指示:「このオブジェクトについている HogeControl.cs の動きがおかしい。中身を読んで、修正案を適用して。」

これだけで、Claudeは以下の行動を自動で行います。

  1. Hierarchyを読む: コンポーネント名を確認。
  2. ファイルを探す: list_scripts で場所を特定。
  3. ファイルを読む: read_file でコード内容を取得。
  4. ファイルを直す: 修正コードを生成し、write_file で実際にUnity上のファイルを書き換え。

注意点

write_file は強力ですが、既存のコードを容赦なく上書きします。 万が一の事故に備えて、Git等でのバージョン管理は必須です。

これで、「AIにコードを相談する」だけでなく「AIに開発用PCのキーボードを渡す」ような強力な開発環境が整いました。