自定义工具
使用 AiTool 特性以 C# 编写自己的 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.";
});
}
}把这类仅限编辑器使用的代码(它用到了 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 时,首次发现该工具时它不会出现在智能体的列表中;但按名称调用时它仍会执行。 |
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”错误。 请用
MainThread.Instance.Run包裹 Unity 调用。 - 模型错误地调用了工具。 改进
[Description]文本,并添加有效值的示例。
旧的 [McpPluginToolType] / [McpPluginTool] 名称仍然可以编译,但已弃用;请使用 Ai* 系列特性。
相关页面
- 工具与 RQL:了解内置工具。
- 自定义 RQL 适配器。
- 配置:显示或隐藏工具。