# Herramientas personalizadas — Unity · AI Game Developer Wiki

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.

Esta página se tradujo automáticamente del inglés. [Lee el original en inglés](https://ai-game.dev/docs/wiki/unity/custom-tools).

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](https://ai-game.dev/docs/rql/custom-adapters).

## 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`.

```csharp
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:

| Propiedad | Significado |
| --- | --- |
| `ReadOnlyHint` | La herramienta solo lee datos. |
| `DestructiveHint` | La herramienta puede eliminar o sobrescribir datos. |
| `IdempotentHint` | Llamarla de nuevo con los mismos argumentos no cambia nada más. |
| `OpenWorldHint` | La herramienta se comunica con sistemas externos, como la web. |
| `Visible` | `false` oculta la herramienta de la lista del agente al descubrirla por primera vez; aun así se ejecuta cuando se llama por su nombre. |
| `Target` | `ToolTarget.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]`:

```csharp
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:

```csharp
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:

```csharp
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.

```csharp
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](https://ai-game.dev/es/docs/wiki/unity/configuration#self-hosting)).

## 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*`.

## Páginas relacionadas

- [Herramientas y RQL](https://ai-game.dev/es/docs/wiki/unity/tools-and-rql) para las herramientas integradas.
- [Adaptadores RQL personalizados](https://ai-game.dev/docs/rql/custom-adapters).
- [Configuración](https://ai-game.dev/es/docs/wiki/unity/configuration#tools-prompts-resources) para mostrar u ocultar herramientas.

---

Source (HTML): https://ai-game.dev/es/docs/wiki/unity/custom-tools

Machine-translated (es) from the English page: https://ai-game.dev/docs/wiki/unity/custom-tools
