Since Unreal MCP became available, I have tried wiring it up with several Python scripts that I previously asked Codex to run, and I found that the process is not that simple.
I will explain how a custom Python function can be used by AI agents through Unreal MCP, how an agent’s tool-use request is routed back to that specific function, and how one registered toolset can expose multiple tools.
I tested this with Unreal Engine 5.8 using Codex.
Before the Editor Starts: Required Files and Definitions
The runtime registration sequence starts when Unreal Editor starts. Before that happens, the required plugins and developer-authored source files must already be in place.
Unreal Engine provides:
PythonScriptPlugin, which discovers Python startup locations and executes startup files.Toolset Registry, which converts reflected toolset classes into runtime handlers and callable tools.Unreal MCP, which exposes registered tools to MCP clients and forwards identified calls.
Before starting the editor, we prepare:
- A recognized project or enabled-plugin
Content/Pythondirectory. - A toolset definition and its custom Python functions.
init_unreal.py, which creates or imports the toolset class and explicitly registers it.
Prepare the Tool Function Code
The custom class derives from unreal.ToolsetDefinition. A method exposed as a tool is declared as a static method and decorated with @toolset_registry.tool_call.
**Template file:** Content/Python/my_project_toolset.py
import unreal
import toolset_registry
@unreal.uclass()
class MyProjectToolset(unreal.ToolsetDefinition):
@toolset_registry.tool_call
@staticmethod
def run_project_action(message: str) -> str:
return message
The decorator supplies the reflection information needed to create an Unreal UFunction marked with AICallable metadata. Function parameters and return annotations provide the information used to build the tool’s input and output schema.
Prepare init_unreal.py
The recommended two-file layout keeps startup registration separate from the tool implementation.
**Template file:** Content/Python/init_unreal.py
from my_project_toolset import MyProjectToolset
from toolset_registry.registration import Registration
_registration = Registration([MyProjectToolset])
_registration.register()
The separate toolset module is recommended, not required. A small toolset can instead be defined directly inside init_unreal.py, above the registration call.
### When the Editor Starts: Runtime Registration Sequence
The runtime sequence is:
- Unreal mounts the project and enabled-plugin content roots.
PythonScriptPluginadds their existingContent/Pythondirectories to Python’s system path.PythonScriptPlugindiscovers and executesinit_unreal.py.- Executing the startup file imports or defines the reflected toolset class and tool methods.
- The startup file explicitly registers the class, and Toolset Registry creates its runtime handler and callable wrappers.
- Unreal MCP exposes the registered tool schemas to connected MCP clients.
Each stage has a separate role.
1. Mount Content Roots and Register Python Paths
Unreal’s content system first mounts the project content root and the content roots of enabled plugins.
FPythonScriptPlugin::RegisterModulePaths() receives those mounted roots and derives a Python directory from each one. If the directory exists, PythonScriptPlugin adds it to Python’s system path.
This makes both of these locations available:
<Project>/Content/Python/
<Project>/Plugins/<PluginName>/Content/Python/
PythonScriptPlugin can also add configured additional paths and paths supplied through UE_PYTHONPATH.
Directory mounting and Python startup-file discovery are separate operations. Mounting establishes the content roots. PythonScriptPlugin then converts their Content/Python directories into searchable Python paths.
2. Discover and Execute init_unreal.py
After the Python interpreter is initialized, FPythonScriptPlugin::RunStartupScripts() checks the root of each registered Python path for the exact filename init_unreal.py.
The search is path-level, not recursive. Unreal does not inspect every .py file and automatically turn decorated functions into tools.
When PythonScriptPlugin finds init_unreal.py, it executes the file as a startup script. This execution begins the developer-authored part of the registration process.
In this workflow, startup scripts run once during editor initialization. Changes to the startup file or reflected tool definitions therefore normally require an editor restart.
3. Import or Define the Reflected Toolset
With the recommended two-file layout, the first important line in init_unreal.py imports the toolset class.
**File:** Content/Python/init_unreal.py
**Scope:** toolset-module import
from my_project_toolset import MyProjectToolset
Importing the module executes its top-level code. The @unreal.uclass() declaration creates the reflected toolset class, while @toolset_registry.tool_call creates a reflected, AICallable UFunction for each decorated method.
A separate module is only an organizational choice. If the class is defined directly in init_unreal.py, Python creates the same reflected definitions while executing the startup file. In both layouts, the class must exist before it can be registered.
At the end of this stage, the reflected definitions exist in the running editor, but they are not yet entries in Toolset Registry.
4. Call the Explicit Registration
The next lines in init_unreal.py perform the explicit registration.
**File:** Content/Python/init_unreal.py
**Scope:** toolset registration
_registration = Registration([MyProjectToolset])
_registration.register()
Registration.register() iterates over the supplied toolset classes and calls the Toolset Registry Python interface for each one. That interface ultimately calls unreal.ToolsetRegistry.register_toolset_class(...).
Importing or defining a decorated class is therefore not enough. The class only enters the runtime registry when this explicit registration call is made.
5. Build the Runtime Handler and Callable Wrappers
On the C++ side, UToolsetRegistry::RegisterToolsetClass() creates one FFunctionLibraryToolset handler for each registered toolset class.
The handler scans the class for valid AICallable functions. For every valid function, FFunctionLibraryToolset::GenerateToolCallObjects() creates one FObjectFunctionToolCall wrapper and adds it to the handler’s Tools map.
A Python module does not map one-to-one to a toolset. A module can define or import multiple toolset classes, and Registration can receive multiple classes. The runtime relationship is one registered toolset class to one FFunctionLibraryToolset handler.
Toolset Registry owns the resulting structure:
UToolsetRegistrySubsystem
-> FToolsetRegistry
-> ToolsetHandlers map
-> one FFunctionLibraryToolset per registered toolset class
-> Tools map
-> one FObjectFunctionToolCall per decorated tool method
The handler is stored in FToolsetRegistry::ToolsetHandlers under the toolset’s qualified name. Each callable wrapper is stored inside that handler under its function name.
The registration belongs to the running Unreal Editor. It is runtime state, not a persistent tool cache. The startup discovery, definition, and registration sequence runs again after the editor restarts.
6. Expose the Registered Schemas Through Unreal MCP
Once registration succeeds, Toolset Registry can describe the toolset name, tool names, descriptions, parameters, and return structures.
Unreal MCP makes that information available to connected MCP clients. A coding agent can inspect the schemas and decide whether to call one of the tools.
The tool is registered with the running Unreal Editor, not with Codex, Claude Code, or another client. The client discovers and calls the tool through MCP, but it does not own the registration.
How an Agent Tool-Use Request Reaches the Python Function
Tool selection happens on the agent side. The agent selects a published tool and supplies its identifiers and arguments.
Agent supplies toolset_name, tool_name, and arguments
-> Unreal MCP receives the identified tool call
-> the MCP adapter forwards it to Toolset Registry
-> ToolsetHandlers finds one handler by toolset_name
-> the handler's Tools map finds one wrapper by tool_name
-> the wrapper converts JSON into Unreal function parameters
-> the wrapper invokes its reflected UFunction
-> the result is converted to JSON and returned through MCP
The source-level responsibilities are:
FToolsetRegistryToolAdapterManager::DispatchToolCall()bridges the MCP request into Toolset Registry.FToolsetRegistry::ExecuteTool()selects a registered handler fromToolsetHandlers.FFunctionLibraryToolset::ExecuteToolInternal()selects a callable wrapper from the handler’sToolsmap.FObjectFunctionToolCall::Execute()converts the input, invokes the reflected function, and prepares the returned value.
Relevant Unreal Engine 5.8 source files:
`Engine/Plugins/Experimental/ModelContextProtocol/Source/ModelContextProtocolEditor/Private/ModelContextProtocolToolsetRegistryAdapter.cpp`
`Engine/Plugins/Experimental/ToolsetRegistry/Source/ToolsetRegistry/Private/ToolsetRegistry/ToolsetRegistry.cpp`
`Engine/Plugins/Experimental/ToolsetRegistry/Source/ToolsetRegistry/Private/ToolsetRegistry/FunctionLibraryToolset.cpp`
`Engine/Plugins/Experimental/ToolsetRegistry/Source/ToolsetRegistry/Private/ToolsetRegistry/ObjectFunctionToolCall.cpp`
Nothing in this route performs semantic tool selection after the agent sends the call. The runtime uses the supplied toolset name and tool name for deterministic lookup.
Multiple Tools in a Single Toolset
A registered toolset is not limited to one tool.
MyProjectToolset
-> one FFunctionLibraryToolset handler
-> first decorated UFunction
-> one FObjectFunctionToolCall wrapper
-> second decorated UFunction
-> one FObjectFunctionToolCall wrapper
-> third decorated UFunction
-> one FObjectFunctionToolCall wrapper
The relationships are:
- Toolset class to toolset handler: one-to-one
- Decorated tool method to callable wrapper: one-to-one
- Toolset handler to callable wrappers: one-to-many
The Registration helper can also receive multiple toolset classes. In that case, Toolset Registry creates one handler for each registered class, and each handler contains the wrappers for that class’s tool methods.
Current Boundaries
Like any other tool call, an agent’s decision to use a tool is not always consistent. Tool names, descriptions, and boundaries become more important as the registry grows.
Registration changes currently depend on an editor restart unless a separate reload and re-registration process is implemented.
Reference Implementation and Feedback
The reference repository applies these mechanisms to an existing Niagara Python exporter. The Niagara code is an implementation example rather than the subject of this post.
The repository contains the portable plugin, developer-authored Python files, Unreal source references, structured call and output, screenshots, optional MCP setup, limitations, and troubleshooting:
Happy to share these. Thanks for reading.