Custom tools
Add your own AI tools, RQL modules, prompts and resources to Unreal MCP from any C++ plugin, and expose gameplay tools in a running game.
Unreal MCP lets any Unreal plugin, including your own game project's plugins, add capabilities for AI agents. You do not fork Unreal MCP: you implement a small public interface, register it as an Unreal modular feature, and the plugin picks it up when your module loads.
This page is for C++ developers. If you only want more engine coverage, check the ready-made extensions first.
Pick the right kind of contribution
| You want the agent to… | Contribute | Interface |
|---|---|---|
| Read or change editor data for an engine feature or your own types | An RQL module (recommended for editor work) | IUnrealMcpRqlModule |
| Run an action that is not data access, or act inside a running game | Tools | IUnrealMcpToolProvider |
| Fetch a reusable prompt template | Prompts | IUnrealMcpPromptProvider |
| Read fixed content by URI | Resources | IUnrealMcpResourceProvider |
All first-party extensions are RQL modules, so the agent reaches them through the same rql-query and rql-mutate tools as the rest of the editor.
Option 1: an RQL module
Depend on the UnrealMcpRql module in your editor module's Build.cs, and declare the UnrealMCP plugin in your .uplugin Plugins array. Then implement the module:
#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)What the context can add:
- Sources (where a query starts):
AddObjectSource,AddObjectListSource,AddAssetSource,AddClassDefaultsSource. - Operations (called with
call):AddApiClassorAddApiSourcemake everyUFUNCTIONof a class callable. Declare themUFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")); the doc comment above each becomes the description the agent reads. - Members:
ExposePropertiesfor properties reflection hides, and read-only computed members withAddJsonMember,AddObjectMember,AddObjectListMember. - Guides:
AddGuideadds a section to theunreal-rqlskill.
A mutation runs in one editor transaction, so create objects with RF_Transactional and call Modify() on what you change. The editor's Extensions section lists your module and can disable it. The buildable reference is the samples/UnrealAITemplate plugin in the Unreal-MCP repository.
Option 2: custom tools
Step 1: add the dependency
In your module's 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
});In your .uplugin, add the plugin dependency so load order resolves:
{
"Plugins": [ { "Name": "UnrealMCP", "Enabled": true } ]
}Step 2: implement the 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));
});
}
};The builder offers .Title, .Description, the parameter helpers .ParamString, .ParamInt, .ParamNumber, .ParamBool and .ParamVector (pass EUnrealMcpParamRequirement::Required for a required one), the four MCP hints, and .Handle. The handler runs on the game thread, so you can call editor and UObject APIs directly.
Rules that matter:
- Tool ids are kebab-case: lowercase letters and digits separated by single hyphens.
- Give every tool a title and all four hints. Mark anything that deletes or overwrites as destructive.
- Do not call
.ExtensionId(...)yourself; yourGetExtensionId()is stamped on automatically.
Step 3: register it as a 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();
}
}Keep the provider alive while it is registered and always unregister it on shutdown.
How registration behaves
- Discovery and hot reload. Providers are found at editor start. Loading or unloading your module later rebuilds the registry and the AI clients see the change automatically.
- Deterministic order. Providers merge in ascending extension-id order; tools inside one provider keep their declaration order.
- Isolation. An invalid tool (bad id, broken schema, no handler) or a duplicate tool name is dropped and recorded on your extension's entry. Your other tools and other extensions are unaffected. A crash inside a handler is still an editor crash, so validate inputs and return
FUnrealMcpToolResult::Error(...). - Visibility and toggles. Your tools get the same Visible checkbox in the MCP Tools window as built-in ones. A disabled extension contributes nothing.
Prompts and resources
Prompts and resources use the same pattern with IUnrealMcpPromptProvider plus FUnrealMcpPromptRegistry, and IUnrealMcpResourceProvider plus FUnrealMcpResourceRegistry. A prompt declares parameters with the same Param* helpers and returns role-tagged messages. A resource is identified by its URI and returns either text or a base64 blob with a MIME type. Only fixed URIs are supported today, not templated ones. The built-in level-design-brief prompt and the unreal://project/levels resource are working references.
Generate a tool with the agent
With a source install of the plugin, the unreal-skill-create system tool writes a new C++ tool file into the plugin's editor module. The tool exists only after you rebuild the editor (Live Coding or a project rebuild).
In-game (runtime) tools
Unreal MCP can also connect a running game (Play In Editor, Standalone or a packaged Development build) so an AI agent can drive it. A runtime connection offers no engine tools: only the tools your game registers.
- Make your game module depend on
UnrealMcpRuntime(notUnrealMcpEditor) and author tools exactly as above. - Enable Project Settings, Plugins, Unreal MCP (Runtime). The switch is off by default and is stored in
DefaultGame.ini. - Connect explicitly. The game never connects on its own.
From C++:
#include "UnrealMcpRuntimeSubsystem.h"
if (UUnrealMcpRuntimeSubsystem* Mcp = UUnrealMcpRuntimeSubsystem::Get(this))
{
Mcp->RegisterToolProvider(MyToolProvider);
Mcp->Connect(TEXT("http://localhost:8080"), TEXT("my-token"));
}From Blueprint, use Get Unreal MCP Runtime Subsystem, then Connect, Disconnect and Is Connected. From the console:
UnrealMcp.Connect <host> [token]
UnrealMcp.DisconnectSafety rules built into Connect():
- Shipping builds refuse to connect unless you compile in the
bUnrealMcpAllowShippingflag. - Only loopback hosts are allowed unless you pass
bAllowRemoteHost = true. - Runtime mode works on desktop platforms only (Windows, macOS, Linux).
The samples/UnrealAIRuntimeSample plugin in the repository adds a game-time-dilation tool that reads and sets the world's time dilation in a running game. If you do not need runtime tools, exclude the plugin from packaged builds as described on Installation.