# Openclaw Unreal Skill

> OpenClaw Unreal Plugin

- Skill: `tomleelive/openclaw-unreal-skill` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add tomleelive/openclaw-unreal-skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomleelive/openclaw-unreal-skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: TomLeeLive (https://skillmd.com/u/tomleelive)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomleelive/openclaw-unreal-skill

---


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

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

1. The operator starts the gateway with the opt-in environment variable. It is
   read from the gateway process, so the model cannot set it:

   ```bash
   OPENCLAW_EDITOR_ALLOW_DESTRUCTIVE=1 openclaw gateway restart
   # or, scoped to this skill only:
   OPENCLAW_UNREAL_ALLOW_DESTRUCTIVE=1 openclaw gateway restart
   ```

2. The caller passes `confirm: true` on 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.

1. When the gateway loads this extension it generates a 32-byte token and writes
   it to `~/.openclaw/unreal-bridge.token` with mode `0600`
   (`$OPENCLAW_CONFIG_DIR`/`$OPENCLAW_HOME` override the directory). The token is
   never logged — only its path is.
2. The Unreal plugin reads that file (or `OPENCLAW_BRIDGE_TOKEN`, or `"secret"`
   in its `openclaw.json`) and sends it as `X-OpenClaw-Bridge-Token` on
   `POST /unreal/register`. Without it: **401**, and no session exists.
3. `register` answers with a per-session token. `poll`, `heartbeat` and `result`
   must carry it as `X-OpenClaw-Session`, and it must belong to the `sessionId`
   in the request. Otherwise: **401**.
4. Every queued command carries a nonce. `POST /unreal/result` is 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.
5. Requests from a non-loopback peer are refused with **403**, as is any request
   carrying an `Origin` or `Referer` header. No CORS headers are sent (before
   1.2.0 the bridge replied `Access-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:

```bash
# 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 name
- `level.list` — all levels in project
- `level.open` — open level by name
- `level.save` — save current level

### Actor
- `actor.find` — find by name/class
- `actor.getAll` — list all actors
- `actor.create` — create actors: StaticMeshActor (Cube, Sphere, Cylinder, Cone), PointLight, Camera
- `actor.delete` — delete by name
- `actor.getData` — detailed actor info
- `actor.setProperty` — set properties via UE reflection system

### Transform
- `transform.getPosition` / `transform.setPosition`
- `transform.getRotation` / `transform.setRotation`
- `transform.getScale` / `transform.setScale`

> Transform tools require a valid RootComponent (works on StaticMeshActor, PointLight, etc. — not on bare Actor).

### Component
- `component.get` — get component data
- `component.add` — add component (not yet implemented)
- `component.remove` — remove component (not yet implemented)

### Editor
- `editor.play` — start PIE (uses RequestPlaySession)
- `editor.stop` — stop PIE
- `editor.pause` / `editor.resume` — pause/resume PIE
- `editor.getState` — current editor state

### Debug
- `debug.hierarchy` — actor hierarchy tree
- `debug.screenshot` — capture editor viewport
- `debug.log` — write to output log

### Input
- `input.simulateKey` — simulate key press
- `input.simulateMouse` — simulate mouse
- `input.simulateAxis` — simulate axis

### Asset
- `asset.list` — list assets at path
- `asset.import` — import asset (not yet implemented)

### Console
- `console.execute` — run console command
- `console.getLogs` — read project log file; params: `count` (number of lines), `filter` (text filter)

### Blueprint
- `blueprint.list` — list blueprints
- `blueprint.open` — open blueprint (not yet implemented)

## Troubleshooting

### Stale binaries / plugin not loading

Clear the build cache and restart the editor:

```bash
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.token` with mode 0600 and the bundled MCP bridge reads it from there (`OPENCLAW_BRIDGE_TOKEN` overrides). 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=1` restores 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_tools` can 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 with `OPENCLAW_EDITOR_ALLOW_DESTRUCTIVE=1` **and** the call carries `confirm: true`. Confirm the change with the user before setting it — see [Safety and permissions](#safety-and-permissions). None of these should run implicitly from a vague request.
- **Data visibility**: `debug.screenshot` and `console.getLogs` can 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: true` is 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, and `dryRun: true` previews any call without sending it. Keep the project under source control before automation sessions. The Korean `SKILL_KO.md` states the same recommendation; if the two ever disagree, this file governs.

## License

Apache-2.0 — See LICENSE.md

