前回はUnityのシーン情報を読み取るだけの「Read-Only」な構成を作りましたが、今回はさらに踏み込んで「Claudeにコードを書かせたり、Unityプロジェクトの中身を詳しく調査させる」ための本格的な環境構築を行います。
これまでは「外から見るだけ」でしたが、今回は「中に入って作業してもらう」構成になります。
今回の強化ポイント
- Unity情報の詳細化: 親子構造やアタッチされているコンポーネントまで把握させる。
- ファイル操作権限の付与: プロジェクト内のC#スクリプト一覧を見たり、中身を読んだり、ファイルを新規作成・修正させる機能を追加します。
- トークン節約: シーン全体だと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)を追加します。
list_scripts: どんなC#ファイルがあるか一覧を見る。read_file: 指定したファイルの中身を読む。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を再起動して試してみましょう。
- Unity: 対象のオブジェクトを選択して「MCP > Export Selected Objects」。
- Claude: 「unity-connect」から Hierarchy情報を添付。
- 指示:「このオブジェクトについている
HogeControl.csの動きがおかしい。中身を読んで、修正案を適用して。」
これだけで、Claudeは以下の行動を自動で行います。
- Hierarchyを読む: コンポーネント名を確認。
- ファイルを探す:
list_scriptsで場所を特定。 - ファイルを読む:
read_fileでコード内容を取得。 - ファイルを直す: 修正コードを生成し、
write_fileで実際にUnity上のファイルを書き換え。
注意点
write_file は強力ですが、既存のコードを容赦なく上書きします。 万が一の事故に備えて、Git等でのバージョン管理は必須です。
これで、「AIにコードを相談する」だけでなく「AIに開発用PCのキーボードを渡す」ような強力な開発環境が整いました。
