自定义工具
通过任意 C++ 插件向 Unreal MCP 添加你自己的 AI 工具、RQL 模块、提示词和资源,并在运行中的游戏里暴露玩法工具。
Unreal MCP 允许任何 Unreal 插件(包括你自己游戏项目中的插件)为 AI 代理添加能力。你不需要 fork Unreal MCP:只需实现一个小的公开接口,将其注册为 Unreal modular feature,插件会在你的模块加载时自动发现它。
本页面向 C++ 开发者。如果你只是想扩大引擎覆盖范围,请先查看现成的扩展。
选择正确的贡献方式
| 你希望代理能够…… | 贡献类型 | 接口 |
|---|---|---|
| 读取或修改某个引擎功能或你自己类型在编辑器中的数据 | RQL 模块(推荐用于编辑器相关工作) | IUnrealMcpRqlModule |
| 执行非数据访问的操作,或在运行中的游戏内执行操作 | 工具 | IUnrealMcpToolProvider |
| 获取可复用的提示词模板 | 提示词 | IUnrealMcpPromptProvider |
| 按 URI 读取固定内容 | 资源 | IUnrealMcpResourceProvider |
所有官方扩展都是 RQL 模块,因此代理通过与其他编辑器功能相同的 rql-query 和 rql-mutate 工具访问它们。
方案 1:RQL 模块
在编辑器模块的 Build.cs 中依赖 UnrealMcpRql 模块,并在 .uplugin 的 Plugins 数组中声明 UnrealMCP 插件。然后实现该模块:
#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)上下文可以添加的内容:
- 源(查询的起点):
AddObjectSource、AddObjectListSource、AddAssetSource、AddClassDefaultsSource。 - 操作(通过
call调用):AddApiClass或AddApiSource会让某个类的所有UFUNCTION都可调用。请将它们声明为UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"));每个函数上方的文档注释会成为代理读取的描述。 - 成员:
ExposeProperties用于反射隐藏的属性,以及通过AddJsonMember、AddObjectMember、AddObjectListMember添加的只读计算成员。 - 指南:
AddGuide会向unreal-rql技能中添加一节内容。
变更操作在一个编辑器事务中运行,因此请使用 RF_Transactional 创建对象,并对你修改的内容调用 Modify()。编辑器的 Extensions 部分会列出你的模块,并可以将其禁用。可构建的参考示例是 Unreal-MCP 仓库中的 samples/UnrealAITemplate 插件。
方案 2:自定义工具
步骤 1:添加依赖
在你的模块的 Build.cs 中:
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 中添加插件依赖,以便解析加载顺序:
{
"Plugins": [ { "Name": "UnrealMCP", "Enabled": true } ]
}步骤 2:实现 provider
#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。处理函数在游戏线程上运行,因此你可以直接调用编辑器 API 和 UObject API。
重要规则:
- 工具 id 使用 kebab-case:小写字母和数字,用单个连字符分隔。
- 为每个工具提供标题和全部四个 hints。将任何删除或覆盖内容的工具标记为 destructive。
- 不要自行调用
.ExtensionId(...);你的GetExtensionId()会自动被写入。
步骤 3:将其注册为 modular feature
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 按扩展 id 升序合并;同一个 provider 内的工具保持其声明顺序。
- 隔离性。 无效的工具(id 不合法、schema 损坏、缺少处理函数)或重名的工具会被丢弃,并记录到你的扩展条目上。你的其他工具和其他扩展不受影响。处理函数内部崩溃仍然会导致编辑器崩溃,因此请校验输入并返回
FUnrealMcpToolResult::Error(...)。 - 可见性与开关。 你的工具在 MCP Tools 窗口中与内置工具一样拥有 Visible 复选框。被禁用的扩展不会贡献任何内容。
提示词与资源
Prompt 和 resource 使用相同的模式,即 IUnrealMcpPromptProvider 加 FUnrealMcpPromptRegistry,以及 IUnrealMcpResourceProvider 加 FUnrealMcpResourceRegistry。Prompt 用相同的 Param* 辅助函数声明参数,并返回带角色标记的消息。Resource 由其 URI 标识,返回文本或带 MIME 类型的 base64 blob。目前只支持固定 URI,不支持模板化 URI。内置的 level-design-brief prompt 和 unreal://project/levels resource 是可用的参考示例。
让 agent 生成工具
在插件以源码方式安装的情况下,unreal-skill-create 系统工具会把新的 C++ 工具文件写入插件的 editor 模块。只有在重新构建编辑器之后(Live Coding 或重新构建项目),该工具才会存在。
游戏内(运行时)工具
Unreal MCP 还可以连接正在运行的游戏(Play In Editor、Standalone 或打包的 Development 构建),让 AI agent 可以驱动它。运行时连接不提供任何引擎工具:只有你的游戏注册的工具。
- 让你的游戏模块依赖
UnrealMcpRuntime(而不是UnrealMcpEditor),并完全按照上面的方式编写工具。 - 启用 Project Settings, Plugins, Unreal MCP (Runtime)。该开关默认关闭,其值保存在
DefaultGame.ini中。 - 显式连接。游戏绝不会自行连接。
在 C++ 中:
#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。在控制台中:
UnrealMcp.Connect <host> [token]
UnrealMcp.DisconnectConnect() 内置的安全规则:
- 除非编译时加入
bUnrealMcpAllowShipping标志,否则 Shipping 构建会拒绝连接。 - 除非传入
bAllowRemoteHost = true,否则只允许 loopback 主机。 - 运行时模式仅适用于桌面平台(Windows、macOS、Linux)。
仓库中的 samples/UnrealAIRuntimeSample 插件新增了一个 game-time-dilation 工具,可在运行中的游戏里读取并设置世界的时间膨胀。如果你不需要运行时工具,请按照 Installation 中所述,将插件从打包构建中排除。