カスタムツール
AiTool 属性を使って独自の Unity MCP ツール、プロンプト、リソース、スキルを C# で書き、メインスレッドで実行してビルド済みゲームでも使えるようにします。
Unity MCP は、プロジェクト内の任意の C# メソッドを AI エージェントが呼び出せるツールに変えることができます。これにより、エージェントにプロジェクト固有の能力を与えられます。「敵のウェーブを生成する」「この JSON からレベルを再構築する」「すべてのアイテムプレハブを検証する」などです。このページでは、ツール、プロンプト、リソース、スキル向けの属性 API、Unity API 呼び出しを安全に実行する方法、実行中のゲームからツールを公開する方法を説明します。
ツールを書く前に
RQL はすでに独自コンポーネントのメンバーの読み取りと設定、およびそのパブリックメソッドの呼び出しができるため、多くのタスクではカスタムツールはまったく不要です。カスタムツールを書くのは、検証を伴う単一の、よく説明された操作が必要な場合や、モデルが毎回即興で書くべきでないロジックが必要な場合です。代わりに RQL に独自の型をよりよく理解させるには、カスタム RQL アダプターを参照してください。
最初のツール
ツールとは、[AiToolType] を付けたクラス内にある [AiTool] を付けたメソッドです。どちらの属性も com.IvanMurzak.McpPlugin 名前空間にあります。
using System.ComponentModel;
using com.IvanMurzak.McpPlugin;
using com.IvanMurzak.ReflectorNet.Utils;
using UnityEngine;
[AiToolType]
public static class Tool_Enemies
{
[AiTool("enemies-spawn-wave", Title = "Enemies / Spawn wave")]
[Description("Spawns a wave of enemy prefabs in a circle around the origin. Returns how many were spawned.")]
public static string SpawnWave
(
[Description("Asset path of the enemy prefab, e.g. 'Assets/Prefabs/Enemy.prefab'.")]
string prefabPath,
[Description("How many enemies to spawn.")]
int count = 5,
[Description("Circle radius in meters.")]
float radius = 10f
)
{
return MainThread.Instance.Run(() =>
{
var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefab == null)
return $"[Error] No prefab at '{prefabPath}'.";
for (var i = 0; i < count; i++)
{
var angle = i * Mathf.PI * 2f / count;
var position = new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * radius;
Object.Instantiate(prefab, position, Quaternion.identity);
}
return $"[Success] Spawned {count} enemies.";
});
}
}このような Editor 専用のコード(UnityEditor を使用)は、Editor フォルダーまたは Editor アセンブリに置いてください。プラグインは起動時と再コンパイルのたびにプロジェクトのアセンブリをスキャンするため、ツールは登録なしでエージェントの一覧に表示されます。エージェントがツール一覧を自動で更新しない場合は、再接続してください。
命名と説明
[AiTool]の最初の引数は、エージェントが呼び出すツール ID です。組み込みツール(rql-query、tests-run)のように kebab-case を使ってください。Titleはツール一覧に表示される人間が読める名前です。- メソッドと各パラメーターに付ける
[Description](System.ComponentModelのもの)は、モデルが読む内容です。ツールが何をするか、いつ呼び出すか、各値が何を意味するかを例を挙げて書いてください。 - 既定値のあるパラメーターや、
string? tag = nullのような null 許容のパラメーターは、モデルにとって省略可能です。
戻り値
ツールは string、任意のシリアライズ可能なオブジェクト(プラグインが JSON に変換し、そのスキーマをモデルに説明します)、非同期処理用の Task<T>、または明示的な制御が必要な場合は com.IvanMurzak.McpPlugin.Common.Model の ResponseCallTool を返せます。たとえば ResponseCallTool.Success("…")、ResponseCallTool.Error("…")、ResponseCallTool.Image(bytes, "image/png") などです。明確な成功メッセージとエラーメッセージを返してください。モデルはそれを使って次に何をするかを判断します。
メインスレッド
ツールは Unity のメインスレッド以外から呼び出されることがあり、ほとんどの Unity API は別スレッドから呼び出すと例外を投げます。Unity の呼び出しは MainThread.Instance.Run(...)(名前空間 com.IvanMurzak.ReflectorNet.Utils)で囲むか、非同期ツールでは MainThread.Instance.RunAsync(...) を使ってください。大きなファイルの解析など Unity に触れない重い処理はラッパーの外に置き、Editor の応答性を保ってください。
ツールのオプション
[AiTool] は、MCP クライアントにツールの動作を伝える任意のヒントを受け付けます:
| プロパティ | 意味 |
|---|---|
ReadOnlyHint | ツールはデータを読み取るだけです。 |
DestructiveHint | ツールはデータを削除または上書きする可能性があります。 |
IdempotentHint | 同じ引数で再度呼び出しても、それ以上何も変わりません。 |
OpenWorldHint | ツールは Web などの外部システムとやり取りします。 |
Visible | false にすると、初回検出時にエージェントの一覧からツールが隠されます。名前で呼び出せば実行はされます。 |
Target | ToolTarget.Editor、ToolTarget.Runtime、または ToolTarget.Both(既定)。[AiToolType] にも設定できます。 |
例: [AiTool("level-validate", Title = "Level / Validate", ReadOnlyHint = true, Visible = false)]。
カスタムプロンプト
プロンプトは、ユーザーが AI クライアントから呼び出せる再利用可能な指示で、多くの場合スラッシュコマンドとして使われます。クラスに [AiPromptType] を、文字列を返すメソッドに [AiPrompt] を付けます:
using System.ComponentModel;
using com.IvanMurzak.McpPlugin;
using com.IvanMurzak.McpPlugin.Common.Model;
[AiPromptType]
public static class Prompt_ProjectRules
{
[AiPrompt(Name = "project-conventions", Role = Role.User)]
[Description("Coding and scene conventions for this project.")]
public static string Conventions()
=> "Use PascalCase for public members. Put new UI under the MainCanvas object. Prefer object pooling for bullets.";
}Role は Role.User または Role.Assistant です。Enabled = false にすると、既定で非表示のプロンプトとして提供されます。組み込みのプロンプトはこうしています。
カスタムリソース
リソースは、クライアントが URI で取得できる読み取り専用のデータです。クラスに [AiResourceType] を、メソッドに [AiResource] を付け、さらに一覧取得用のメソッドを用意します:
using System.ComponentModel;
using com.IvanMurzak.McpPlugin;
using com.IvanMurzak.McpPlugin.Common.Model;
[AiResourceType]
public static class Resource_Balance
{
const string Uri = "game://balance";
[AiResource(Route = Uri, Name = "balance", MimeType = "application/json", ListResources = nameof(List))]
[Description("Current game balance values as JSON.")]
public static ResponseResourceContent[] Get(string uri)
=> new[] { ResponseResourceContent.CreateText(uri, "{\"enemyHp\":100}", "application/json") };
public static ResponseListResource[] List()
=> new[] { new ResponseListResource(uri: Uri, name: "balance", mimeType: "application/json", description: "Game balance") };
}カスタムスキル
スキルは、エージェントのスキルフォルダー(たとえば .claude/skills)に生成される Markdown ファイルです。独自のスキルを追加するには、静的クラスに [AiSkillType] を、Markdown を保持する静的 string プロパティまたはフィールドに [AiSkill] を付けます:
using com.IvanMurzak.McpPlugin;
[AiSkillType]
public static class Skill_MyGame
{
[AiSkill("my-game-levels", "How levels are structured in this project and which tools build them.")]
public static string Markdown => "# Levels\n\nEvery level is a scene under Assets/Levels ...";
}ツールメソッドでは、[AiSkillDescription] と [AiSkillBody] によって、そのツール用に生成されるスキルに短い要約と長文の Markdown が追加されます。スキルを再生成するには、ウィンドウの Skills > Generate または unity-mcp-cli setup-skills <agent> を使います。
実行中のゲーム内のツール
同じ属性はプレイヤービルドでも機能します。ゲーム内ではデフォルトで何も接続されません。ランタイムプラグインを自分で初期化し、必要なツールを登録します。ゲーム専用のツールは Target = ToolTarget.Runtime(名前空間 com.IvanMurzak.McpPlugin.Common)でマークします。すると Editor はそれらをスキップし、Editor が接続されている間でもサーバーはそれらの呼び出しを実行中のゲームにルーティングします。
using System.ComponentModel;
using System.Reflection;
using System.Threading.Tasks;
using com.IvanMurzak.McpPlugin;
using com.IvanMurzak.McpPlugin.Common;
using com.IvanMurzak.ReflectorNet.Utils;
using com.IvanMurzak.Unity.MCP;
using UnityEngine;
[AiToolType(Target = ToolTarget.Runtime)]
public static class ChessGameAI
{
[AiTool("chess-do-turn", Title = "Do the turn")]
[Description("Moves a figure. Returns true if the move was accepted.")]
public static Task<bool> DoTurn(int figureId, Vector2Int position)
=> MainThread.Instance.RunAsync(() => ChessGameController.Instance.DoTurn(figureId, position));
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void SetupMcp()
{
var mcpPlugin = UnityMcpPluginRuntime.Initialize(builder =>
{
builder.WithConfig(c => c.Host = "http://localhost:8080");
builder.WithToolsFromAssembly(Assembly.GetExecutingAssembly());
})
.Build();
_ = mcpPlugin.Connect();
}
}ChessGameController はあなた自身のゲームコードを表します。サーバーに対して認証するには、c.CredentialProvider にトークンを返す関数を設定します。ランタイム接続をシャットダウンするには UnityMcpPluginRuntime.DisposeInstance() を呼び出します。プレイヤーは到達可能な MCP サーバーを必要とするため、どこで実行するかを計画してください(セルフホスティング を参照)。
カスタムツールのトラブルシューティング
- ツールが表示されない。 Console でコンパイルエラーを確認し、両方の属性が存在することを確かめ、ウィンドウで Tools > Open を開いてください。ツールが非表示になっている可能性があります。名前が
Unity.、UnityEngine、UnityEditor、System、Microsoftで始まるアセンブリはスキャンされないため、アセンブリに別の名前を付けてください。 - 「Can only be called from the main thread」エラー。 Unity の呼び出しを
MainThread.Instance.Runでラップしてください。 - モデルがツールを誤って呼び出す。
[Description]のテキストを改善し、有効な値の例を追加してください。
古い [McpPluginToolType] / [McpPluginTool] という名前もまだコンパイルできますが非推奨です。Ai* 属性を使用してください。
関連ページ
- 組み込みツールについては Tools and RQL を参照してください。
- カスタム RQL アダプター。
- ツールの表示/非表示については Configuration を参照してください。