本頁面由英文機器翻譯而成。閱讀英文原文。

自訂工具

用 C# 與 AiTool 屬性撰寫你自己的 Unity MCP 工具、提示、資源與技能,在主執行緒上執行,並在建置後的遊戲中使用。

更新於 2026年11月4日 以 Markdown 檢視

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此工具會與外部系統(例如網路)互動。
Visiblefalse 會在首次探索時將此工具從代理的清單中隱藏;以名稱呼叫時仍會執行。
TargetToolTarget.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* 屬性。