# 사용자 정의 도구 — Unity · AI Game Developer Wiki

AiTool 특성을 사용해 C#으로 나만의 Unity MCP 도구, 프롬프트, 리소스, 스킬을 작성하고, 메인 스레드에서 실행하며 빌드된 게임에서도 사용하세요.

이 페이지는 영어에서 기계 번역되었습니다. [영어 원문 읽기](https://ai-game.dev/docs/wiki/unity/custom-tools).

Unity MCP는 프로젝트의 어떤 C# 메서드든 AI 에이전트가 호출할 수 있는 도구로 바꿀 수 있습니다. 이렇게 해서 에이전트에게 프로젝트 특화 능력을 부여합니다: "적 웨이브 생성", "이 JSON으로 레벨 재구축", "모든 아이템 프리팹 검증". 이 페이지에서는 도구, 프롬프트, 리소스, 스킬을 위한 특성 API와 Unity API 호출을 안전하게 실행하는 방법, 그리고 실행 중인 게임에서 도구를 노출하는 방법을 보여줍니다.

## 도구를 작성하기 전에

RQL은 이미 자체 컴포넌트의 멤버를 읽고 설정하며 public 메서드를 호출할 수 있으므로, 많은 작업에는 사용자 정의 도구가 전혀 필요하지 않습니다. 검증이 포함된 하나의 잘 설명된 작업이 필요하거나, 모델이 매번 즉흥적으로 처리해서는 안 되는 로직이 있을 때 도구를 작성하세요. 대신 RQL이 자체 타입을 더 잘 이해하도록 하려면 [사용자 정의 RQL 어댑터](https://ai-game.dev/docs/rql/custom-adapters)를 참고하세요.

## 첫 번째 도구

도구는 `[AiToolType]`으로 표시된 클래스 안에서 `[AiTool]`으로 표시된 메서드입니다. 두 특성 모두 `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.";
        });
    }
}
```

이와 같은 Editor 전용 코드(예: `UnityEditor` 사용)는 `Editor` 폴더나 Editor 어셈블리에 넣으세요. 플러그인은 시작할 때와 재컴파일이 있을 때마다 프로젝트의 어셈블리를 스캔하므로, 별도 등록 없이 도구가 에이전트의 목록에 나타납니다. 에이전트가 스스로 도구 목록을 갱신하지 않으면 다시 연결하세요.

### 이름 지정과 설명

- `[AiTool]`의 첫 번째 인수는 에이전트가 호출하는 도구 id입니다. 내장 도구(`rql-query`, `tests-run`)처럼 kebab-case를 사용하세요.
- `Title`은 도구 목록에 표시되는 사람이 읽을 수 있는 이름입니다.
- 메서드와 각 매개변수에 붙는 `[Description]`(`System.ComponentModel`에서 가져옴)은 모델이 읽는 내용입니다. 도구가 무엇을 하는지, 언제 호출해야 하는지, 각 값이 무엇을 의미하는지 예시와 함께 설명하세요.
- 기본값이 있는 매개변수나 `string? tag = null`처럼 nullable인 매개변수는 모델에게 선택 사항입니다.

### 반환 값

도구는 `string`, 직렬화 가능한 객체(플러그인이 JSON으로 변환하고 그 스키마를 모델에 설명), 비동기 작업을 위한 `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]`를 표시하세요:

```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`은 `Role.User` 또는 `Role.Assistant`입니다. `Enabled = false`로 설정하면 기본적으로 숨겨진 프롬프트로 제공됩니다. 내장 프롬프트가 이렇게 동작합니다.

## 사용자 정의 리소스

리소스는 클라이언트가 URI로 가져올 수 있는 읽기 전용 데이터입니다. 클래스에 `[AiResourceType]`을, 메서드에 `[AiResource]`를 표시하고 목록 메서드를 제공하세요:

```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") };
}
```

## 사용자 정의 스킬

스킬은 에이전트의 스킬 폴더(예: `.claude/skills`)에 생성되는 Markdown 파일입니다. 정적 클래스에 `[AiSkillType]`을, Markdown을 담은 정적 문자열 속성이나 필드에 `[AiSkill]`을 붙여 나만의 스킬을 추가하세요:

```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 ...";
}
```

도구 메서드에서 `[AiSkillDescription]`과 `[AiSkillBody]`는 해당 도구용으로 생성된 스킬에 짧은 요약과 긴 형식의 Markdown을 추가합니다. 창에서 **Skills > Generate**를 사용하거나 `unity-mcp-cli setup-skills <agent>`로 스킬을 다시 생성하세요.

## 실행 중인 게임에서의 도구

동일한 어트리뷰트는 플레이어 빌드에서도 작동합니다. 게임에서는 기본적으로 아무것도 연결되지 않습니다. 런타임 플러그인을 직접 초기화하고 원하는 도구를 등록해야 합니다. 게임 전용 도구는 `Target = ToolTarget.Runtime`(네임스페이스 `com.IvanMurzak.McpPlugin.Common`)으로 표시하세요. 그러면 Editor는 해당 도구를 건너뛰고, 서버는 Editor가 연결되어 있는 동안에도 해당 호출을 실행 중인 게임으로 라우팅합니다.

```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`는 여러분의 게임 코드를 나타냅니다. 서버에 인증하려면 `c.CredentialProvider`를 토큰을 반환하는 함수로 설정하세요. 런타임 연결을 종료하려면 `UnityMcpPluginRuntime.DisposeInstance()`를 호출하세요. 플레이어는 접근 가능한 MCP 서버가 필요하므로 어디에서 실행할지 계획하세요([셀프 호스팅](https://ai-game.dev/ko/docs/wiki/unity/configuration#self-hosting) 참고).

## 사용자 정의 도구 문제 해결

- **도구가 나타나지 않습니다.** Console에서 컴파일 오류를 확인하고, 두 어트리뷰트가 모두 있는지 확인한 뒤 창에서 **Tools > Open**을 여세요. 도구가 숨겨져 있을 수 있습니다. 이름이 `Unity.`, `UnityEngine`, `UnityEditor`, `System` 또는 `Microsoft`로 시작하는 어셈블리는 검사되지 않으므로 어셈블리에 다른 이름을 지정하세요.
- **"Can only be called from the main thread" 오류.** Unity 호출을 `MainThread.Instance.Run`으로 감싸세요.
- **모델이 도구를 잘못 호출합니다.** `[Description]` 텍스트를 개선하고 유효한 값의 예시를 추가하세요.

이전 `[McpPluginToolType]` / `[McpPluginTool]` 이름은 여전히 컴파일되지만 사용이 권장되지 않습니다. `Ai*` 어트리뷰트를 사용하세요.

## 관련 페이지

- 기본 제공 도구에 대해서는 [도구와 RQL](https://ai-game.dev/ko/docs/wiki/unity/tools-and-rql)을 참고하세요.
- [사용자 정의 RQL 어댑터](https://ai-game.dev/docs/rql/custom-adapters).
- 도구를 표시하거나 숨기려면 [Configuration](https://ai-game.dev/ko/docs/wiki/unity/configuration#tools-prompts-resources)을 참고하세요.

---

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

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