本页由英文机器翻译而成。阅读英文原文。

自定义工具

使用 AiTool 特性以 C# 编写自己的 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.";
        });
    }
}

把这类仅限编辑器使用的代码(它用到了 UnityEditor)放在 Editor 文件夹或 Editor 程序集中。插件在启动时以及每次重新编译后都会扫描项目的程序集,因此工具无需任何注册就会出现在智能体的工具列表中。如果智能体没有自行刷新工具列表,请重新连接它。

命名与描述

  • [AiTool] 的第一个参数是智能体调用时使用的工具 id。请使用 kebab-case,与内置工具(rql-query、tests-run)保持一致。
  • Title 是显示在工具列表中的可读名称。
  • 方法和每个参数上的 [Description](来自 System.ComponentModel)才是模型读到的东西。请说明工具做什么、何时调用它、每个值代表什么,并给出一个示例。
  • 带默认值的参数,或可为空的参数(如 string? tag = null),对模型来说是可选的。

返回值

工具可以返回 string、任意可序列化对象(插件会把它转换为 JSON,并向模型描述其 schema)、用于异步工作的 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 时,首次发现该工具时它不会出现在智能体的列表中;但按名称调用时它仍会执行。
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”错误。 请用 MainThread.Instance.Run 包裹 Unity 调用。
  • 模型错误地调用了工具。 改进 [Description] 文本,并添加有效值的示例。

旧的 [McpPluginToolType] / [McpPluginTool] 名称仍然可以编译,但已弃用;请使用 Ai* 系列特性。