Skip to Content
ReferenceCustom Tools (Plugin Developers)

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:

MetaEffect
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:

RiskUse forBehavior
read_onlyqueries, inspection, no side effectslightest approval
mutatingchanges project/editor state, generally reversiblestandard approval
destructivedeletes or otherwise irreversible actionsstrongest 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, mcp are 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 wantWhat to writeWorks on
Nwiro onlyUFUNCTION(meta=(NwiroTool=...)) on any UCLASS5.5 - 5.8
UE native MCP onlyUToolsetDefinition subclass + static UFUNCTION(meta=(AICallable))5.8
Both, one declarationUToolsetDefinition + AICallable5.8 (UE native exposes it; Nwiro imports it from the Toolset Registry as ue.*)
Both, widest reachUToolsetDefinition + both AICallable and NwiroToolUE 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 tagged UFUNCTION requires 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 UFUNCTION on a UCLASS (so it appears in Unreal’s reflection registry). It does not need to be BlueprintCallable.
  • This is all you ship. There is no SDK to vendor and no Nwiro header to include.
Last updated on