Custom Tools (Plugin Developers)
Expose your own UE5 plugin’s C++ methods as Nwiro tools. Once registered, they appear in Nwiro’s tool list alongside the built-ins and can be called by the AI in both Nwiro Pro and Nwiro Integration Kit.
You do this with zero Nwiro includes and zero dependency on Nwiro. You never
copy a Nwiro header, never add a Nwiro module to your Build.cs, and never link
anything Nwiro-specific. You tag a normal UFUNCTION with one meta string, and
Nwiro discovers it through Unreal’s reflection system at session start.
Because there is no dependency:
- Your plugin compiles and runs identically whether or not Nwiro is installed.
- If Nwiro is absent, the tag is just unread metadata. Nothing happens. Nothing breaks.
The one thing you write
Tag a UFUNCTION with meta=(NwiroTool="<leaf_name>"):
// MyRoadTools.h - your own plugin, NO Nwiro headers
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "MyRoadTools.generated.h"
UCLASS()
class UMyRoadTools : public UObject
{
GENERATED_BODY()
public:
/**
* Generate a road spline between two points.
* @param Start Start point (cm, world space)
* @param End End point
* @param Width Road width (m). Defaults to 6.
*/
UFUNCTION(meta=(NwiroTool="build_road", NwiroRisk="mutating"))
static FString BuildRoad(FVector Start, FVector End, float Width = 6.0f);
};// MyRoadTools.cpp
#include "MyRoadTools.h"
FString UMyRoadTools::BuildRoad(FVector Start, FVector End, float Width)
{
// Do the work (you run on the game thread), then return a JSON string.
return TEXT("{\"success\":true,\"actor\":\"/Game/Roads/Road_0\"}");
}That is the whole integration. The tool shows up as plugin.<your-id>.build_road.
Your Build.cs only needs what your own code uses (a UFUNCTION needs
CoreUObject and Engine). There is no Nwiro entry:
PublicDependencyModuleNames.AddRange(new[] { "Core", "CoreUObject", "Engine" });Meta keys
All optional except NwiroTool:
| Meta | Effect |
|---|---|
NwiroTool="leaf" | Required. Marks the function and sets its leaf name. Lowercase [a-z0-9_]. |
NwiroProvider="id" | Authority segment of the namespace. If omitted, derived from your plugin/package name. |
NwiroDesc="..." | Description the AI reads to decide when to call the tool. Falls back to the function’s doc comment (ToolTip). |
NwiroRisk="read_only|mutating|destructive" | Risk class. Defaults to mutating. |
NwiroSchema="<json>" | Explicit JSON-Schema for the arguments. Use for complex or nested inputs (see below). |
Defining the arguments
Three ways, all header-free. Pick whichever fits.
1. Typed parameters (simplest). The schema is derived from the function’s parameters. A parameter with a default value becomes optional.
UFUNCTION(meta=(NwiroTool="build_road"))
static FString BuildRoad(FVector Start, FVector End, float Width = 6.0f);2. Explicit schema (full control). Give a single const FString& ArgsJson
parameter and supply the schema yourself. Nwiro passes the raw arguments JSON in,
and you parse it.
UFUNCTION(meta=(NwiroTool="bulk_roads", NwiroRisk="mutating",
NwiroSchema="{\"type\":\"object\",\"properties\":{\"segments\":{\"type\":\"array\"}},\"required\":[\"segments\"]}"))
static FString BulkRoads(const FString& ArgsJson);3. Raw arguments, no schema. A lone const FString& ArgsJson parameter with
no NwiroSchema is treated as a free-form object argument. The whole arguments
blob is handed to your string.
In all cases your function returns a FString JSON result. For the raw and
explicit-schema forms, return your own envelope (for example
{"success":true,...}).
Risk classes
The risk you declare drives Nwiro’s approval prompt:
| Risk | Use for | Behavior |
|---|---|---|
read_only | queries, inspection, no side effects | lightest approval |
mutating | changes project/editor state, generally reversible | standard approval |
destructive | deletes or otherwise irreversible actions | strongest approval |
If you omit NwiroRisk, Nwiro treats the tool as the strictest class. Declare it
accurately so users get the right prompt.
How your tool runs
- Tagged functions run on the game thread, so you can touch UObjects and editor APIs directly.
- The function is invoked on the class default object, so write it as
static(or otherwise stateless). Do not rely on per-instance state. - Always return a JSON string. On failure return
{"success":false,"error":"..."}rather than throwing. Nwiro wraps each call in a timeout and surfaces a tool error; a misbehaving tool never crashes the editor.
Namespacing and reserved names
Nwiro assigns the authoritative prefix, so third parties cannot squat reserved or each other’s names:
plugin.<your-id>.<tool> your tools
nwiro.<tool> Nwiro built-ins
ue.<toolset>.<tool> imported UE 5.8 native tools- You control only the leaf name and propose the provider id (
NwiroProvider); the head is verified against your plugin identity. - Reserved heads
nwiro,ue,system,internal,debug,mcpare rejected. - Keep ids short and stable, for example
acme.roads. Lowercase, dots allowed.
Where your tools appear
Registered tools show up in Settings -> Tool Sources, grouped under Plugins by provider. End users can enable or disable any source by namespace.
The set is resolved once per session and frozen, which keeps the tool list stable for prompt caching. Define your tagged functions at load time and avoid changing the set mid-session.
Also exposing to Unreal’s native MCP (UE 5.8)
UE 5.8 ships its own MCP server backed by the engine Toolset Registry. Nwiro can
import those native tools too, namespaced ue.<toolset>.<tool>. You can target
Nwiro, Unreal’s native MCP, or both:
| You want | What to write | Works on |
|---|---|---|
| Nwiro only | UFUNCTION(meta=(NwiroTool=...)) on any UCLASS | 5.5 - 5.8 |
| UE native MCP only | UToolsetDefinition subclass + static UFUNCTION(meta=(AICallable)) | 5.8 |
| Both, one declaration | UToolsetDefinition + AICallable | 5.8 (UE native exposes it; Nwiro imports it from the Toolset Registry as ue.*) |
| Both, widest reach | UToolsetDefinition + both AICallable and NwiroTool | UE native on 5.8; Nwiro as plugin.* on 5.5 - 5.8 |
The two Nwiro paths key off different things and do not interfere: NwiroTool is
found by reflection (plugin.*, every version), while native tools are imported
from the engine Toolset Registry (ue.*, 5.8 only).
With the dual-tag “widest reach” option on 5.8, the same tool can resolve twice
inside Nwiro: once as plugin.<id>.<tool> (from NwiroTool) and once as
ue.<toolset>.<tool> (from the import). This is not a problem in practice: the
Unreal Engine source is off by default in Settings -> Tool Sources, so only
the plugin.* copy shows unless the user turns it on. Unreal’s own native MCP
server exposes the tool independently regardless.
Requirements and caveats
- Tools are discovered in the editor (
WITH_EDITOR). Adding a new taggedUFUNCTIONrequires a full editor restart to be picked up. Changing only the body of an existing one works with Live Coding. - The function must be a reflected
UFUNCTIONon aUCLASS(so it appears in Unreal’s reflection registry). It does not need to beBlueprintCallable. - This is all you ship. There is no SDK to vendor and no Nwiro header to include.