Ferramentas personalizadas
Crie ferramentas, prompts, recursos e skills do Unity MCP em C# com os atributos AiTool, rode na thread principal e use em um jogo compilado.
O Unity MCP pode transformar qualquer método C# do seu projeto em uma ferramenta que o agente de IA pode chamar. É assim que você dá ao agente habilidades específicas do projeto: "gerar uma onda de inimigos", "reconstruir o nível a partir deste JSON", "validar todos os prefabs de itens". Esta página mostra a API de atributos para ferramentas, prompts, recursos e skills, como fazer chamadas à API do Unity com segurança e como expor ferramentas de um jogo em execução.
Antes de escrever uma ferramenta
O RQL já lê e define membros dos seus próprios componentes e chama seus métodos públicos, então muitas tarefas não precisam de nenhuma ferramenta personalizada. Escreva uma ferramenta quando quiser uma operação única e bem descrita, com validação, ou uma lógica que não deva ser improvisada pelo modelo a cada vez. Para fazer o RQL entender melhor os seus próprios tipos, veja adaptadores RQL personalizados.
Sua primeira ferramenta
Uma ferramenta é um método marcado com [AiTool] dentro de uma classe marcada com [AiToolType]. Ambos os atributos estão no namespace 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.";
});
}
}Coloque código exclusivo do Editor como este (ele usa UnityEditor) em uma pasta Editor ou em uma assembly de Editor. O plugin escaneia as assemblies do projeto quando inicia e após cada recompilação, então a ferramenta aparece na lista do agente sem nenhum registro. Reconecte o agente se ele não atualizar a lista de ferramentas sozinho.
Nomes e descrições
- O primeiro argumento de
[AiTool]é o id da ferramenta que o agente chama. Use kebab-case, como nas ferramentas integradas (rql-query,tests-run). Titleé um nome legível por humanos exibido nas listas de ferramentas.[Description](deSystem.ComponentModel) no método e em cada parâmetro é o que o modelo lê. Diga o que a ferramenta faz, quando chamá-la e o que cada valor significa, com um exemplo.- Parâmetros com valor padrão, ou anuláveis como
string? tag = null, são opcionais para o modelo.
Valores de retorno
Uma ferramenta pode retornar uma string, qualquer objeto serializável (o plugin o converte em JSON e descreve seu esquema ao modelo), Task<T> para trabalho assíncrono, ou um ResponseCallTool de com.IvanMurzak.McpPlugin.Common.Model quando você quiser controle explícito, por exemplo ResponseCallTool.Success("…"), ResponseCallTool.Error("…") ou ResponseCallTool.Image(bytes, "image/png"). Retorne mensagens claras de sucesso e erro: o modelo as usa para decidir o que fazer em seguida.
A thread principal
As ferramentas podem ser invocadas fora da thread principal do Unity, e a maioria das APIs do Unity lança exceção quando chamada de outra thread. Envolva as chamadas ao Unity em MainThread.Instance.Run(...) (namespace com.IvanMurzak.ReflectorNet.Utils), ou MainThread.Instance.RunAsync(...) em uma ferramenta assíncrona. Mantenha o trabalho pesado que não toca no Unity, como analisar um arquivo grande, fora do wrapper para que o Editor continue responsivo.
Opções da ferramenta
[AiTool] aceita dicas opcionais que informam aos clientes MCP como a ferramenta se comporta:
| Propriedade | Significado |
|---|---|
ReadOnlyHint | A ferramenta apenas lê dados. |
DestructiveHint | A ferramenta pode excluir ou sobrescrever dados. |
IdempotentHint | Chamá-la novamente com os mesmos argumentos não muda mais nada. |
OpenWorldHint | A ferramenta se comunica com sistemas externos, como a web. |
Visible | false oculta a ferramenta da lista do agente quando descoberta pela primeira vez; ela ainda é executada quando chamada pelo nome. |
Target | ToolTarget.Editor, ToolTarget.Runtime ou ToolTarget.Both (padrão). Também pode ser definido em [AiToolType]. |
Exemplo: [AiTool("level-validate", Title = "Level / Validate", ReadOnlyHint = true, Visible = false)].
Prompts personalizados
Um prompt é uma instrução reutilizável que o usuário pode invocar a partir do cliente de IA, muitas vezes como um comando de barra. Marque uma classe com [AiPromptType] e um método que retorna string com [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 ou Role.Assistant. Defina Enabled = false para entregar um prompt oculto por padrão; os prompts integrados fazem isso.
Recursos personalizados
Um recurso são dados somente leitura que o cliente pode buscar por URI. Marque uma classe com [AiResourceType] e um método com [AiResource], e forneça um método de listagem:
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") };
}Skills personalizadas
Skills são arquivos Markdown gerados na pasta de skills do agente (por exemplo, .claude/skills). Adicione as suas com [AiSkillType] em uma classe estática e [AiSkill] em uma propriedade ou campo estático de string que contém o Markdown:
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 ...";
}Em um método de ferramenta, [AiSkillDescription] e [AiSkillBody] adicionam um resumo curto e um Markdown longo à skill gerada para essa ferramenta. Regenere as skills com Skills > Generate na janela ou unity-mcp-cli setup-skills <agent>.
Ferramentas em um jogo em execução
Os mesmos atributos funcionam em uma build de player. Em um jogo, nada se conecta por padrão: você inicializa o plugin de runtime por conta própria e registra as ferramentas que quiser. Marque as ferramentas exclusivas do jogo com Target = ToolTarget.Runtime (namespace com.IvanMurzak.McpPlugin.Common). O Editor então as ignora, e o servidor encaminha suas chamadas ao jogo em execução mesmo enquanto o Editor está conectado.
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 representa o código do seu próprio jogo. Para autenticar em um servidor, defina c.CredentialProvider como uma função que retorna o token. Chame UnityMcpPluginRuntime.DisposeInstance() para encerrar a conexão de runtime. Os jogadores precisam de um servidor MCP alcançável, então planeje onde ele roda (veja self-hosting).
Solução de problemas com ferramentas personalizadas
- A ferramenta não aparece. Verifique o Console em busca de erros de compilação, confirme que ambos os atributos estão presentes e abra Tools > Open na janela: a ferramenta pode estar oculta. Assemblies cujos nomes começam com
Unity.,UnityEngine,UnityEditor,SystemouMicrosoftnão são escaneadas, então dê outro nome à sua assembly. - Erros de "Can only be called from the main thread". Envolva as chamadas ao Unity em
MainThread.Instance.Run. - O modelo chama a ferramenta de forma errada. Melhore os textos de
[Description]e adicione exemplos de valores válidos.
Os nomes antigos [McpPluginToolType] / [McpPluginTool] ainda compilam, mas estão obsoletos; use os atributos Ai*.
Páginas relacionadas
- Ferramentas e RQL para as ferramentas integradas.
- Adaptadores RQL personalizados.
- Configuração para mostrar ou ocultar ferramentas.