Esta página se tradujo automáticamente del inglés. Lee el original en inglés.

Herramientas personalizadas

Escribe herramientas, prompts, recursos y skills de Unity MCP en C# con atributos AiTool, ejecútalas en el hilo principal y úsalas en el juego final.

Actualizado el 4 de noviembre de 2026 Ver como Markdown

Unity MCP puede convertir cualquier método C# de tu proyecto en una herramienta que el agente de IA puede llamar. Así es como le das al agente habilidades específicas del proyecto: «generar una oleada de enemigos», «reconstruir el nivel a partir de este JSON», «validar todos los prefabs de objetos». Esta página muestra la API de atributos para herramientas, prompts, recursos y skills, cómo realizar llamadas seguras a la API de Unity y cómo exponer herramientas desde un juego en ejecución.

Antes de escribir una herramienta

RQL ya lee y establece miembros de tus propios componentes y llama a sus métodos públicos, así que muchas tareas no necesitan ninguna herramienta personalizada. Escribe una herramienta cuando quieras una operación única y bien descrita con validación, o lógica que el modelo no deba improvisar cada vez. Para que RQL entienda mejor tus propios tipos, consulta adaptadores RQL personalizados.

Tu primera herramienta

Una herramienta es un método marcado con [AiTool] dentro de una clase marcada con [AiToolType]. Ambos atributos están en el espacio de nombres 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.";
        });
    }
}

Coloca código exclusivo del Editor como este (usa UnityEditor) en una carpeta Editor o en un ensamblado de Editor. El plugin escanea los ensamblados del proyecto al iniciarse y después de cada recompilación, por lo que la herramienta aparece en la lista del agente sin necesidad de registrarla. Vuelve a conectar el agente si no actualiza su lista de herramientas por sí solo.

Nombres y descripciones

  • El primer argumento de [AiTool] es el id de la herramienta que llama el agente. Usa kebab-case, como en las herramientas integradas (rql-query, tests-run).
  • Title es un nombre legible por humanos que se muestra en las listas de herramientas.
  • [Description] (de System.ComponentModel) en el método y en cada parámetro es lo que lee el modelo. Indica qué hace la herramienta, cuándo llamarla y qué significa cada valor, con un ejemplo.
  • Los parámetros con un valor predeterminado, o los que admiten null como string? tag = null, son opcionales para el modelo.

Valores de retorno

Una herramienta puede devolver un string, cualquier objeto serializable (el plugin lo convierte a JSON y describe su esquema al modelo), Task<T> para trabajo asíncrono, o un ResponseCallTool de com.IvanMurzak.McpPlugin.Common.Model cuando quieras un control explícito, por ejemplo ResponseCallTool.Success("…"), ResponseCallTool.Error("…") o ResponseCallTool.Image(bytes, "image/png"). Devuelve mensajes claros de éxito y error: el modelo los usa para decidir qué hacer a continuación.

El hilo principal

Es posible que las herramientas se invoquen fuera del hilo principal de Unity, y la mayoría de las API de Unity lanzan una excepción si se llaman desde otro hilo. Envuelve las llamadas a Unity en MainThread.Instance.Run(...) (espacio de nombres com.IvanMurzak.ReflectorNet.Utils), o en MainThread.Instance.RunAsync(...) desde una herramienta asíncrona. Mantén fuera del envoltorio el trabajo pesado que no toca Unity, como analizar un archivo grande, para que el Editor siga respondiendo.

Opciones de la herramienta

[AiTool] acepta indicaciones opcionales que informan a los clientes MCP sobre cómo se comporta la herramienta:

PropiedadSignificado
ReadOnlyHintLa herramienta solo lee datos.
DestructiveHintLa herramienta puede eliminar o sobrescribir datos.
IdempotentHintLlamarla de nuevo con los mismos argumentos no cambia nada más.
OpenWorldHintLa herramienta se comunica con sistemas externos, como la web.
Visiblefalse oculta la herramienta de la lista del agente al descubrirla por primera vez; aun así se ejecuta cuando se llama por su nombre.
TargetToolTarget.Editor, ToolTarget.Runtime o ToolTarget.Both (predeterminado). También se puede establecer en [AiToolType].

Ejemplo: [AiTool("level-validate", Title = "Level / Validate", ReadOnlyHint = true, Visible = false)].

Prompts personalizados

Un prompt es una instrucción reutilizable que el usuario puede invocar desde el cliente de IA, a menudo como un comando de barra. Marca una clase con [AiPromptType] y un método que devuelva una cadena con [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 es Role.User o Role.Assistant. Establece Enabled = false para incluir un prompt oculto de forma predeterminada; los prompts integrados hacen esto.

Recursos personalizados

Un recurso son datos de solo lectura que el cliente puede obtener por URI. Marca una clase con [AiResourceType] y un método con [AiResource], y proporciona un método de listado:

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

Las skills son archivos Markdown que se generan en la carpeta de skills del agente (por ejemplo, .claude/skills). Añade las tuyas con [AiSkillType] en una clase estática y [AiSkill] en una propiedad o campo estático de tipo string que contenga el 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 ...";
}

En un método de herramienta, [AiSkillDescription] y [AiSkillBody] añaden un resumen breve y Markdown extenso a la skill generada para esa herramienta. Regenera las skills con Skills > Generate en la ventana o unity-mcp-cli setup-skills <agent>.

Herramientas en un juego en ejecución

Los mismos atributos funcionan en una compilación de player. En un juego no se conecta nada de forma predeterminada: inicializas tú mismo el plugin de runtime y registras las herramientas que quieras. Marca las herramientas exclusivas del juego con Target = ToolTarget.Runtime (espacio de nombres com.IvanMurzak.McpPlugin.Common). Así el Editor las omite, y el servidor dirige sus llamadas al juego en ejecución incluso mientras el 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 tu propio código de juego. Para autenticarte contra un servidor, asigna a c.CredentialProvider una función que devuelva el token. Llama a UnityMcpPluginRuntime.DisposeInstance() para cerrar la conexión de runtime. Los jugadores necesitan un servidor MCP accesible, así que planifica dónde se ejecuta (consulta autoalojamiento).

Solución de problemas de herramientas personalizadas

  • La herramienta no aparece. Revisa la Consola en busca de errores de compilación, confirma que ambos atributos están presentes y abre Tools > Open en la ventana: puede que la herramienta esté oculta. Los ensamblados cuyos nombres empiezan por Unity., UnityEngine, UnityEditor, System o Microsoft no se escanean, así que dale otro nombre a tu ensamblado.
  • Errores de «Can only be called from the main thread». Envuelve las llamadas a Unity en MainThread.Instance.Run.
  • El modelo llama mal a la herramienta. Mejora los textos de [Description] y añade ejemplos de valores válidos.

Los nombres antiguos [McpPluginToolType] / [McpPluginTool] todavía compilan, pero están obsoletos; usa los atributos Ai*.