自訂工具
用 C# 與 AiTool 屬性撰寫你自己的 Unity MCP 工具、提示、資源與技能,在主執行緒上執行,並在建置後的遊戲中使用。
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。請使用 kebab-case,就像內建工具(rql-query、tests-run)一樣。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 的繁重工作(例如解析大型檔案)請留在包裝之外,讓編輯器保持回應。
工具選項
[AiTool] 可接受選用的提示,用來告訴 MCP 用戶端工具的行為:
| 屬性 | 含義 |
|---|---|
ReadOnlyHint | 此工具只讀取資料。 |
DestructiveHint | 此工具可能會刪除或覆寫資料。 |
IdempotentHint | 以相同引數再次呼叫不會再造成任何變更。 |
OpenWorldHint | 此工具會與外部系統(例如網路)互動。 |
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 的靜態字串屬性或欄位上加上 [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* 屬性。
相關頁面
- 工具與 RQL:內建工具的說明。
- 自訂 RQL 適配器。
- 組態設定:顯示或隱藏工具。