# 自訂工具 — Unreal Engine · AI Game Developer Wiki

從任何 C++ 外掛將你自己的 AI 工具、RQL 模組、提示與資源加入 Unreal MCP，並在執行中的遊戲裡公開遊戲玩法工具。

本頁面由英文機器翻譯而成。[閱讀英文原文](https://ai-game.dev/docs/wiki/unreal/custom-tools)。

Unreal MCP 讓任何 Unreal 外掛（包括你自己遊戲專案的外掛）為 AI 代理新增能力。你不需要 fork Unreal MCP：只要實作一個小型的公開介面，將它註冊為 Unreal **modular feature**，外掛就會在你的模組載入時接手。

本頁面是給 C++ 開發者看的。如果你只是想要更多引擎涵蓋範圍，請先查看現成的 [extensions](https://ai-game.dev/zh-hant/docs/wiki/unreal/extensions)。

## 選擇正確的貢獻類型

| 你希望代理… | 貢獻類型 | 介面 |
| --- | --- | --- |
| 讀取或變更某個引擎功能或你自己型別的編輯器資料 | **RQL 模組**（編輯器工作推薦） | `IUnrealMcpRqlModule` |
| 執行非資料存取的动作，或在執行中的遊戲內操作 | **工具** | `IUnrealMcpToolProvider` |
| 取得可重複使用的提示範本 | **提示** | `IUnrealMcpPromptProvider` |
| 依 URI 讀取固定內容 | **資源** | `IUnrealMcpResourceProvider` |

所有第一方擴充功能都是 RQL 模組，因此代理會透過與編輯器其餘部分相同的 `rql-query` 與 `rql-mutate` 工具來存取它們。

## 選項 1：RQL 模組

在你的編輯器模組 `Build.cs` 中相依於 `UnrealMcpRql` 模組，並在 `.uplugin` 的 `Plugins` 陣列中宣告 `UnrealMCP` 外掛。接著實作該模組：

```cpp
#include "IUnrealMcpRqlModule.h"

class FMyRqlModule : public IUnrealMcpRqlModule
{
public:
    virtual FString GetRqlModuleName() const override { return TEXT("Niagara"); }
    virtual FString GetExtensionId() const override { return TEXT("com.example.niagara-ai"); }
    virtual FString GetExtensionVersion() const override { return UnrealMcpRql::PluginVersion(TEXT("MyNiagaraAI")); }
    virtual void ConfigureRql(FUnrealMcpRqlModuleContext& Context) override
    {
        Context.AddAssetSource(TEXT("niagaraSystems"), TEXT("Niagara system assets"), UNiagaraSystem::StaticClass());
        Context.AddApiSource(TEXT("niagara"), TEXT("Niagara operations"), UMyNiagaraOperations::StaticClass(),
            TEXT("NiagaraOperations"));
        Context.AddGuide(TEXT("Niagara"), TEXT("... markdown with example queries ..."));
    }
};

IMPLEMENT_MODULE(TUnrealMcpRqlEditorModule<FMyRqlModule>, MyNiagaraAI)
```

context 可以加入的內容：

- **來源**（查詢的起點）：`AddObjectSource`、`AddObjectListSource`、`AddAssetSource`、`AddClassDefaultsSource`。
- **操作**（以 `call` 呼叫）：`AddApiClass` 或 `AddApiSource` 會讓某個類別的每個 `UFUNCTION` 都可被呼叫。將它們宣告為 `UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))`；每個函式上方的 doc 註解就會成為代理所讀取的說明。
- **成員**：`ExposeProperties` 用於反射所隱藏的屬性，以及透過 `AddJsonMember`、`AddObjectMember`、`AddObjectListMember` 建立的唯讀計算成員。
- **指南**：`AddGuide` 會在 `unreal-rql` 技能中加入一個章節。

一次 mutation 會在單一編輯器交易中執行，因此請以 `RF_Transactional` 建立物件，並對你變更的物件呼叫 `Modify()`。編輯器的 Extensions 區段會列出你的模組，並可將它停用。可建置的參考範例是 [Unreal-MCP repository](https://github.com/IvanMurzak/Unreal-MCP) 中的 `samples/UnrealAITemplate` 外掛。

## 選項 2：自訂工具

### 步驟 1：加入相依性

在你模組的 `Build.cs` 中：

```csharp
PublicDependencyModuleNames.AddRange(new string[] { "Core" });

PrivateDependencyModuleNames.AddRange(new string[]
{
    "CoreUObject", "Engine", "Projects",
    "Json",            // the registry header includes Dom/JsonObject.h
    "UnrealMcpEditor", // the extension contract and tool registry
});
```

在你的 `.uplugin` 中，加入此外掛相依性，讓載入順序得以解析：

```json
{
  "Plugins": [ { "Name": "UnrealMCP", "Enabled": true } ]
}
```

### 步驟 2：實作 provider

```cpp
#include "IUnrealMcpToolProvider.h"
#include "UnrealMcpToolRegistry.h"

class FMyExtensionProvider : public IUnrealMcpToolProvider
{
public:
    virtual FString GetExtensionId() const override      { return TEXT("com.foo.my-extension"); }
    virtual FText   GetDisplayName() const override      { return NSLOCTEXT("Foo", "Name", "My Extension"); }
    virtual FString GetExtensionVersion() const override { return TEXT("1.0.0"); }

    virtual void RegisterTools(FUnrealMcpToolRegistry& Registry) override
    {
        Registry.Tool(TEXT("hello-extension"))
            .Title(TEXT("Hello Extension"))
            .Description(TEXT("Returns a friendly greeting."))
            .ParamString(TEXT("name"), TEXT("Who to greet. Defaults to 'world'."))
            .ReadOnlyHint(true).DestructiveHint(false).IdempotentHint(true).OpenWorldHint(false)
            .Handle([](const FUnrealMcpToolCall& Call) -> FUnrealMcpToolResult
            {
                const FString Name = Call.Has(TEXT("name")) ? Call.GetString(TEXT("name")) : TEXT("world");
                return FUnrealMcpToolResult::Success(FString::Printf(TEXT("Hello, %s!"), *Name));
            });
    }
};
```

builder 提供 `.Title`、`.Description`、參數輔助方法 `.ParamString`、`.ParamInt`、`.ParamNumber`、`.ParamBool` 與 `.ParamVector`（必填參數請傳入 `EUnrealMcpParamRequirement::Required`）、四個 MCP hints，以及 `.Handle`。處理常式會在遊戲執行緒上執行，因此你可以直接呼叫編輯器與 `UObject` API。

重要規則：

- 工具 id 採 kebab-case：小寫字母與數字，並以單一連字號分隔。
- 每個工具都要有標題與全部四個 hints。任何會刪除或覆寫的工具都要標示為 destructive。
- 不要自行呼叫 `.ExtensionId(...)`；你的 `GetExtensionId()` 會自動被標記上去。

### 步驟 3：註冊為 modular feature

```cpp
virtual void StartupModule() override
{
    Provider = MakeUnique<FMyExtensionProvider>();
    IModularFeatures::Get().RegisterModularFeature(
        IUnrealMcpToolProvider::GetModularFeatureName(), Provider.Get());
}

virtual void ShutdownModule() override
{
    if (Provider.IsValid())
    {
        IModularFeatures::Get().UnregisterModularFeature(
            IUnrealMcpToolProvider::GetModularFeatureName(), Provider.Get());
        Provider.Reset();
    }
}
```

在註冊期間保持 provider 存活，並務必在關閉時取消註冊。

## 註冊的行為

- **探索與熱重載。** Provider 會在編輯器啟動時被找到。之後載入或卸載你的模組會重建註冊表，AI 用戶端會自動看到變更。
- **確定性順序。** Provider 依 extension-id 遞增順序合併；同一個 provider 內的工具則維持其宣告順序。
- **隔離。** 無效的工具（id 錯誤、schema 損壞、沒有處理常式）或重複的工具名稱會被丟棄，並記錄在你的擴充功能項目上。你的其他工具與其他擴充功能不受影響。處理常式內部的當機仍會造成編輯器當機，因此請驗證輸入並回傳 `FUnrealMcpToolResult::Error(...)`。
- **可見性與開關。** 你的工具在 MCP Tools 視窗中會取得與內建工具相同的 **Visible** 核取方塊。被停用的擴充功能不會貢獻任何內容。

## 提示與資源

提示（prompt）與資源（resource）採用相同的模式：`IUnrealMcpPromptProvider` 搭配 `FUnrealMcpPromptRegistry`，以及 `IUnrealMcpResourceProvider` 搭配 `FUnrealMcpResourceRegistry`。提示會用同一組 `Param*` 輔助函式宣告參數，並回傳帶有角色標記的訊息。資源以其 URI 識別，回傳文字或附帶 MIME 型別的 base64 blob。目前僅支援固定 URI，不支援樣板化 URI。內建的 `level-design-brief` 提示與 `unreal://project/levels` 資源是可運作的參考範例。

## 使用 agent 產生工具

在以外掛原始碼安裝的情況下，`unreal-skill-create` 系統工具會在外掛的編輯器模組中寫入一個新的 C++ 工具檔案。必須重新建置編輯器（Live Coding 或重新建置專案）之後，這個工具才會存在。

## 遊戲內（執行階段）工具

Unreal MCP 也能連線到執行中的遊戲（Play In Editor、Standalone 或打包後的 Development 建置），讓 AI agent 直接操作。執行階段連線不提供任何引擎工具：只有你的遊戲所註冊的工具。

1. 讓你的遊戲模組相依於 `UnrealMcpRuntime`（而非 `UnrealMcpEditor`），並完全依照上述方式撰寫工具。
2. 啟用 **Project Settings, Plugins, Unreal MCP (Runtime)**。此開關預設為關閉，設定會存放在 `DefaultGame.ini` 中。
3. 明確呼叫連線。遊戲本身絕不會自動連線。

從 C++：

```cpp
#include "UnrealMcpRuntimeSubsystem.h"

if (UUnrealMcpRuntimeSubsystem* Mcp = UUnrealMcpRuntimeSubsystem::Get(this))
{
    Mcp->RegisterToolProvider(MyToolProvider);
    Mcp->Connect(TEXT("http://localhost:8080"), TEXT("my-token"));
}
```

在 Blueprint 中，使用 **Get Unreal MCP Runtime Subsystem**，再呼叫 **Connect**、**Disconnect** 與 **Is Connected**。從主控台：

```text
UnrealMcp.Connect <host> [token]
UnrealMcp.Disconnect
```

`Connect()` 內建的安全規則：

- 除非你在編譯時開啟 `bUnrealMcpAllowShipping` 旗標，否則 Shipping 建置會拒絕連線。
- 除非傳入 `bAllowRemoteHost = true`，否則只允許 loopback 主機。
- 執行階段模式僅支援桌面平台（Windows、macOS、Linux）。

儲存庫中的 `samples/UnrealAIRuntimeSample` 外掛新增了一個 `game-time-dilation` 工具，可讀取並設定執行中遊戲世界的時間縮放（time dilation）。如果你不需要執行階段工具，請依照 [Installation](https://ai-game.dev/zh-hant/docs/wiki/unreal/installation#editor-only) 的說明，在打包建置時排除此外掛。

---

Source (HTML): https://ai-game.dev/zh-hant/docs/wiki/unreal/custom-tools

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