OpenClaw Unreal Plugin
MCP skill for controlling Unreal Engine Editor via OpenClaw.
Safety and permissions
What this skill can change: anything the Unreal Editor can — actors and their
components and transforms, the open level, imported assets, PIE (Play In Editor),
and whatever a console command reaches (console.execute).
What runs without asking: read-only tools only — level.getCurrent,
level.list, actor.find, actor.getAll, actor.getData, transform.get*,
component.get, editor.getState, asset.list, blueprint.list,
debug.hierarchy, debug.screenshot, console.getLogs, debug.log. Their
behaviour is unchanged.
What is gated at runtime: every project-changing tool — actor.create,
actor.delete, actor.setProperty, transform.set*, component.add/remove,
level.open, level.save, asset.import, console.execute, input.simulate*,
editor.play/stop/pause/resume, blueprint.open — and any batch that
carries one of them. The gateway extension refuses these by default.
Unrecognised names are gated too (1.2.0): a tool name outside the catalogue
above is a custom tool or a typo, and its verb proves nothing either way, so it
is classified project-changing. Before 1.2.0 such a name passed ungated whenever
it started with get/list/find. To exempt one you have read:
# exact names, comma separated, on the gateway process
OPENCLAW_UNREAL_READONLY_CUSTOM_TOOLS="mygame.getScore" openclaw gateway restart
console.execute, execute_code, execute_custom_tool, manage_tools and any
name containing a destructive verb can never be declared read-only.
How to enable them — both steps are required:
The operator starts the gateway with the opt-in environment variable. It is read from the gateway process, so the model cannot set it:
OPENCLAW_EDITOR_ALLOW_DESTRUCTIVE=1 openclaw gateway restart # or, scoped to this skill only: OPENCLAW_UNREAL_ALLOW_DESTRUCTIVE=1 openclaw gateway restartThe caller passes
confirm: trueon each project-changing call, after the user has approved that specific change:unreal_execute: actor.delete {name: "Cube_01"}, confirm: true
Missing either one is a refusal with an explanation of what was blocked and how to allow it. Nothing reaches the Editor in the meantime.
Preview first: dryRun: true reports how a call is classified and what would
be sent, and sends nothing:
unreal_execute: level.save, dryRun: true
→ {risk: "project-changing", requiresConfirmation: true, wouldRun: false, executed: false}
Check the current state: openclaw unreal status prints whether
project-changing tools are enabled, and GET /unreal/status reports the same as
destructiveOperations: "enabled" | "blocked".
Bridge authentication (1.2.0): the /unreal/* endpoints this extension serves
are the Editor plugin's half of the bridge. They now require a per-launch secret,
and they answer loopback peers only.
- When the gateway loads this extension it generates a 32-byte token and writes
it to
~/.openclaw/unreal-bridge.tokenwith mode0600($OPENCLAW_CONFIG_DIR/$OPENCLAW_HOMEoverride the directory). The token is never logged — only its path is. - The Unreal plugin reads that file (or
OPENCLAW_BRIDGE_TOKEN, or"secret"in itsopenclaw.json) and sends it asX-OpenClaw-Bridge-TokenonPOST /unreal/register. Without it: 401, and no session exists. registeranswers with a per-session token.poll,heartbeatandresultmust carry it asX-OpenClaw-Session, and it must belong to thesessionIdin the request. Otherwise: 401.- Every queued command carries a nonce.
POST /unreal/resultis accepted only for a tool call that is actually in flight for that session and only with that call's nonce; anything else is dropped with 409 and never reaches the model. That is what stops a third local process from answering in the Editor's place — the "spoofed tool results" hole. - Requests from a non-loopback peer are refused with 403, as is any request
carrying an
OriginorRefererheader. No CORS headers are sent (before 1.2.0 the bridge repliedAccess-Control-Allow-Origin: *).
Legacy behaviour is off by default. OPENCLAW_UNREAL_ALLOW_LEGACY_UNAUTHENTICATED=1
(or OPENCLAW_EDITOR_ALLOW_LEGACY_UNAUTHENTICATED=1) restores the pre-1.2.0
unauthenticated bridge for an old plugin and logs a warning saying so; openclaw unreal status and GET /unreal/status report auth: "legacy-unauthenticated"
while it is on. Update the plugin instead.
What the token does and does not buy: the file is 0600, so another user on
the machine cannot read it. A process running as you can, and that is the
same boundary that protects your SSH keys and the gateway's own config.
Session isolation (1.2.0): each connected Editor gets its own session id,
session token, command queue and in-flight table. One session cannot poll
another's queue or answer another's tool call. When more than one Editor is
connected, unreal_execute refuses to guess and asks for an explicit sessionId.
Session tokens are never reported by unreal_sessions or GET /unreal/status.
Scope of the gate: it lives in the gateway extension that ships with this
skill (extension/index.ts), so it covers every call routed through the OpenClaw
gateway — Telegram, Discord and the other channels. The Unreal Editor plugin's
embedded MCP Direct server (port 27184, Mode B below) is a separate install from
the plugin repository; from plugin 1.4.0 it requires its own per-launch token
(~/.openclaw/unreal-mcp-bridge.token, mode 0600) instead of the optional token
it had in 1.3.1, but it is not covered by this confirmation gate: an MCP
client that holds that token talks to the Editor directly.
Connection Modes
Mode A: OpenClaw Gateway (Remote)
The plugin connects to OpenClaw Gateway via HTTP polling. Works automatically when Gateway is running.
Mode B: MCP Direct (Claude Code / Cursor)
The plugin runs an embedded HTTP server on port 27184. Use the included MCP bridge:
# Claude Code
claude mcp add unreal -- node /path/to/Plugins/OpenClaw/MCP~/index.js
# Cursor — add to .cursor/mcp.json
{"mcpServers":{"unreal":{"command":"node","args":["/path/to/Plugins/OpenClaw/MCP~/index.js"]}}}
Both modes run simultaneously.
Editor Panel
Window → OpenClaw Unreal Plugin — opens a dockable tab with:
- Connection status indicator
- MCP server info (address, protocol)
- Connect / Disconnect buttons
- Live log of tool calls and messages
Tools
Level
level.getCurrent— current level namelevel.list— all levels in projectlevel.open— open level by namelevel.save— save current level
Actor
actor.find— find by name/classactor.getAll— list all actorsactor.create— create actors: StaticMeshActor (Cube, Sphere, Cylinder, Cone), PointLight, Cameraactor.delete— delete by nameactor.getData— detailed actor infoactor.setProperty— set properties via UE reflection system
Transform
transform.getPosition/transform.setPositiontransform.getRotation/transform.setRotationtransform.getScale/transform.setScale
Transform tools require a valid RootComponent (works on StaticMeshActor, PointLight, etc. — not on bare Actor).
Component
component.get— get component datacomponent.add— add component (not yet implemented)component.remove— remove component (not yet implemented)
Editor
editor.play— start PIE (uses RequestPlaySession)editor.stop— stop PIEeditor.pause/editor.resume— pause/resume PIEeditor.getState— current editor state
Debug
debug.hierarchy— actor hierarchy treedebug.screenshot— capture editor viewportdebug.log— write to output log
Input
input.simulateKey— simulate key pressinput.simulateMouse— simulate mouseinput.simulateAxis— simulate axis
Asset
asset.list— list assets at pathasset.import— import asset (not yet implemented)
Console
console.execute— run console commandconsole.getLogs— read project log file; params:count(number of lines),filter(text filter)
Blueprint
blueprint.list— list blueprintsblueprint.open— open blueprint (not yet implemented)
Troubleshooting
Stale binaries / plugin not loading
Clear the build cache and restart the editor:
rm -rf YourProject/Plugins/OpenClaw/Binaries YourProject/Plugins/OpenClaw/Intermediate
Connection issues
- Ensure OpenClaw Gateway is running:
openclaw gateway status - Check the Editor Panel log for errors
- Verify the MCP port is not blocked by firewall
Security & Privacy Disclosure
This skill drives a live Unreal Editor. Full disclosure of capabilities and current limitations:
- The gateway bridge is authenticated (v1.2.0+):
/unreal/*needs a per-launch token from~/.openclaw/unreal-bridge.token(mode 0600) to register, a per-session token on every later request, and the issued nonce on every result — a third local process can no longer open a session or forge a result. Non-loopback peers and browser-originated requests get 403. A process running as your user can read the token file; that is the boundary. - Local HTTP server hardening (plugin v1.4.0+): the embedded MCP Direct server (port 27184) requires a per-launch token by default — the plugin writes it to
~/.openclaw/unreal-mcp-bridge.tokenwith mode 0600 and the bundled MCP bridge reads it from there (OPENCLAW_BRIDGE_TOKENoverrides). It also rejects browser-originated requests (Origin/Referer→ 403) and non-loopback peers where the engine reports them. In plugin 1.3.1 that token was optional, so an unconfigured install accepted any local process; that is fixed.OPENCLAW_UNREAL_ALLOW_LEGACY_UNAUTHENTICATED=1restores the old behaviour with a loud warning. - Unrecognised and custom tool names are gated (v1.2.0+): a name outside the documented catalogue is classified project-changing whatever its verb reads like, and
console.execute/execute_code/execute_custom_tool/manage_toolscan never be exempted. - Destructive operations are gated at runtime (v1.1.0+): deleting actors, saving levels, importing assets, running console commands (
console.execute) and simulating keyboard/mouse input are refused by the gateway extension unless it was started withOPENCLAW_EDITOR_ALLOW_DESTRUCTIVE=1and the call carriesconfirm: true. Confirm the change with the user before setting it — see Safety and permissions. None of these should run implicitly from a vague request. - Data visibility:
debug.screenshotandconsole.getLogscan capture whatever is on screen or in project logs — including credentials, tokens, or source paths if present. Review before sharing captures outside the machine. - Trigger scope: routine-sounding requests ("clean up the level", "save everything", "just run it") map to state-changing Editor operations — confirm once before the first state-changing call in a session.
- Safety defaults:
disableModelInvocation: trueis set — the model cannot auto-invoke this skill; it runs only on explicit user request. Project-changing tools default to refused, unrecognised names count as project-changing, the bridge requires a token, anddryRun: truepreviews any call without sending it. Keep the project under source control before automation sessions. The KoreanSKILL_KO.mdstates the same recommendation; if the two ever disagree, this file governs.
License
Apache-2.0 — See LICENSE.md