# 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.

_Last updated: 2026-11-04_

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](https://ai-game.dev/docs/wiki/unreal/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:

```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)
```

What the context can add:

- **Sources** (where a query starts): `AddObjectSource`, `AddObjectListSource`, `AddAssetSource`, `AddClassDefaultsSource`.
- **Operations** (called with `call`): `AddApiClass` or `AddApiSource` make every `UFUNCTION` of a class callable. Declare them `UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))`; the doc comment above each becomes the description the agent reads.
- **Members**: `ExposeProperties` for properties reflection hides, and read-only computed members with `AddJsonMember`, `AddObjectMember`, `AddObjectListMember`.
- **Guides**: `AddGuide` adds a section to the `unreal-rql` skill.

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](https://github.com/IvanMurzak/Unreal-MCP).

## Option 2: custom tools

### Step 1: add the dependency

In your module's `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
});
```

In your `.uplugin`, add the plugin dependency so load order resolves:

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

### Step 2: implement the 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));
            });
    }
};
```

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; your `GetExtensionId()` is stamped on automatically.

### Step 3: register it as a 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();
    }
}
```

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.

1. Make your game module depend on `UnrealMcpRuntime` (not `UnrealMcpEditor`) and author tools exactly as above.
2. Enable **Project Settings, Plugins, Unreal MCP (Runtime)**. The switch is off by default and is stored in `DefaultGame.ini`.
3. Connect explicitly. The game never connects on its own.

From C++:

```cpp
#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:

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

Safety rules built into `Connect()`:

- Shipping builds refuse to connect unless you compile in the `bUnrealMcpAllowShipping` flag.
- 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](https://ai-game.dev/docs/wiki/unreal/installation#editor-only).

---

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