사용자 정의 도구
AiTool 특성을 사용해 C#으로 나만의 Unity MCP 도구, 프롬프트, 리소스, 스킬을 작성하고, 메인 스레드에서 실행하며 빌드된 게임에서도 사용하세요.
Unity MCP는 프로젝트의 어떤 C# 메서드든 AI 에이전트가 호출할 수 있는 도구로 바꿀 수 있습니다. 이렇게 해서 에이전트에게 프로젝트 특화 능력을 부여합니다: "적 웨이브 생성", "이 JSON으로 레벨 재구축", "모든 아이템 프리팹 검증". 이 페이지에서는 도구, 프롬프트, 리소스, 스킬을 위한 특성 API와 Unity API 호출을 안전하게 실행하는 방법, 그리고 실행 중인 게임에서 도구를 노출하는 방법을 보여줍니다.
도구를 작성하기 전에
RQL은 이미 자체 컴포넌트의 멤버를 읽고 설정하며 public 메서드를 호출할 수 있으므로, 많은 작업에는 사용자 정의 도구가 전혀 필요하지 않습니다. 검증이 포함된 하나의 잘 설명된 작업이 필요하거나, 모델이 매번 즉흥적으로 처리해서는 안 되는 로직이 있을 때 도구를 작성하세요. 대신 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.";
});
}
}이와 같은 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]를 표시하세요:
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" 오류. Unity 호출을
MainThread.Instance.Run으로 감싸세요. - 모델이 도구를 잘못 호출합니다.
[Description]텍스트를 개선하고 유효한 값의 예시를 추가하세요.
이전 [McpPluginToolType] / [McpPluginTool] 이름은 여전히 컴파일되지만 사용이 권장되지 않습니다. Ai* 어트리뷰트를 사용하세요.
관련 페이지
- 기본 제공 도구에 대해서는 도구와 RQL을 참고하세요.
- 사용자 정의 RQL 어댑터.
- 도구를 표시하거나 숨기려면 Configuration을 참고하세요.