feat(optional-skills): add unreal-mcp companion skill for the unreal-engine MCP catalog entry

Companion to optional-mcps/unreal-engine (Epic's official editor-embedded
MCP server, UE 5.8 experimental). Mirrors the blender-mcp catalog-entry +
companion-skill pattern, sized up for Unreal's discovery-based surface:

- SKILL.md: tool-search discovery contract (list_toolsets/describe_toolset/
  call_tool), serial game-thread call discipline (explicitly overrides the
  parallel-batching default), plain-English->scene translation workflow,
  save/verify hygiene, art-direction loop
- references/tool-surface.md: architecture (Unreal MCP / Toolset Registry /
  AllToolsets), confirmed shipped toolsets, call_tool dispatch semantics,
  project Agent Skills (AgentSkillToolset), capture paths, custom Python/C++
  toolset authoring, config/CVar/console reference, cooked-build notes
- references/scene-craft.md: physically-based lighting values (lux/lumens/
  Kelvin/EV100), mood recipes, Lumen Movable-mobility rule, scale tables,
  content-path conventions, CineCamera framing, editor Python entry points
- references/recipes.md: four end-to-end builds in INTENT/DISCOVER/VALUES/
  VERIFY grammar (exterior, night interior, golden-hour cinematic still,
  import+populate) that stay honest about the project-dependent surface
- references/pitfalls.md: 25+ failure modes with fixes: start order, modal
  deadlocks, Hermes-timeout-vs-editor-completion, _C class suffix, PascalCase
  silent no-op writes, referenced-asset delete crash, async shader compiles,
  editor sprite icons in screenshots, PIE interference

Grounded in Epic's UE 5.8 docs and Epic's agent-facing skill pack for this
server; no fabricated tool names — live describe_toolset schemas are the
contract throughout.
This commit is contained in:
SHL0MS 2026-07-16 17:14:14 -04:00 committed by Teknium
parent 56e2ba5e79
commit a8b81c56a0
5 changed files with 1249 additions and 0 deletions

View File

@ -0,0 +1,238 @@
---
name: unreal-mcp
description: "Use when the user wants to do anything in Unreal Engine through Epic's official editor-embedded MCP server (catalog entry: unreal-engine) — build/light/populate scenes, place and transform actors, create material instances, frame cameras, take screenshots, render, import assets, run automation tests, or automate the editor end-to-end from plain-English prompts with no Unreal knowledge required. Covers the tool-search discovery walk (list_toolsets/describe_toolset/call_tool), serial game-thread call discipline, scene-craft numbers (physical light units, exposure, scale conventions), complete build recipes, save/undo hygiene, and extending the tool surface with custom Python toolsets."
version: 1.0.0
requires: Unreal Editor 5.8+ with the Unreal MCP plugin enabled and its server running
author: Hermes Agent
license: MIT
tags: [unreal, unreal-engine, ue5, 3d, mcp, scenes, cinematics, lighting, gamedev]
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [unreal, unreal-engine, ue5, 3d, mcp, scenes, cinematics, lighting, gamedev]
related_skills: [blender-mcp]
---
# Unreal Engine MCP Skill
Companion skill for the `unreal-engine` entry in the Hermes MCP catalog. The
MCP server (Epic's official, experimental "Unreal MCP" plugin, internal id
`ModelContextProtocol`) runs INSIDE the Unreal Editor process and exposes
editor functionality as typed tools. This skill teaches how to drive it well:
discovering the live tool surface, sequencing calls safely, translating
plain-English asks into scenes that actually look good, and verifying work
visually. The user should never need to touch the editor beyond launching it.
## When to Use
Use when the user wants anything done in Unreal Engine: build or dress a
level, spawn/move/delete actors, set up lighting and atmosphere, create or
tune material instances, frame a camera shot, capture screenshots or renders,
import assets, inspect the scene or UI, run automation tests, or script the
editor. Works for single actions ("make the sun golden hour") and for
complete multi-step projects ("build me a moody forest clearing with a
campfire and render a shot of it").
Don't use for: DCC-style mesh modeling/sculpting (use `blender-mcp` and
import the result), or for editing Unreal C++ project source (that's normal
code work — use the terminal; this skill is about the live editor).
## Prerequisites
Two halves, in this order: the editor side must be up before Hermes connects.
### One-time, editor side
1. Unreal Editor **5.8+** with a project open.
2. **Edit > Plugins** — enable **Unreal MCP** (its Toolset Registry
dependency auto-enables). Restart the editor when prompted.
3. The typed toolsets ship separately from the server: also enable the
**AllToolsets** plugin in the same Plugins browser. Unreal MCP ships NO
tools itself — AllToolsets provides the shipped toolsets (SceneTools,
ActorTools, MaterialInstanceTools, ObjectTools, …); skip it and the
server connects but the agent has nothing to call.
4. **Edit > Editor Preferences > General > Model Context Protocol** — enable
**Auto Start Server**. Default bind is `http://127.0.0.1:8000/mcp`
(port/path configurable in the same panel; server name is `unreal-mcp`).
To start manually instead, run `ModelContextProtocol.StartServer` in the
editor console (backtick key).
### One-time, Hermes side
hermes mcp install unreal-engine
This writes the `mcp_servers.unreal-engine` HTTP entry pointing at
`http://127.0.0.1:8000/mcp` and probes the live server for its tools. Run it
while the editor + server are up so the probe sees the real surface. If the
user changed port/path in Editor Preferences, edit the `url` in
`~/.hermes/config.yaml` under `mcp_servers.unreal-engine` to match.
Do NOT use `ModelContextProtocol.GenerateClientConfig` for Hermes — that
writes `.mcp.json`-style files for Claude Code/Cursor/etc. Hermes connects
from `config.yaml` via the catalog entry.
### Every session
1. Launch Unreal Editor, wait for the project to finish loading; confirm the
server started (Output Log shows the bind address, or run
`ModelContextProtocol.StartServer` manually).
2. Start the Hermes session. Tools register as `mcp_unreal_engine_*`. If
they're missing: editor wasn't up first — start it, then open a new
Hermes session.
3. Sanity check: call `mcp_unreal_engine_list_toolsets` and confirm toolsets
come back.
## The Tool Surface: Discovery, Not a Fixed List
By default the plugin runs in **tool-search mode**: `tools/list` returns only
three meta-tools, and every real tool is reached through them. Through Hermes
they appear as:
| Hermes tool | Purpose |
|---|---|
| `mcp_unreal_engine_list_toolsets` | Names + descriptions of every registered toolset |
| `mcp_unreal_engine_describe_toolset` | Full JSON schemas for one named toolset's tools |
| `mcp_unreal_engine_call_tool` | Invoke a named tool with arguments, get the result |
The discovery walk, always in this order:
1. `list_toolsets` → see what capability groups this project actually has
(the surface is project-dependent: enabled plugins, Game Feature Plugins,
and any custom toolsets all contribute).
2. `describe_toolset` on the group you need → read the real parameter
schemas. Never guess parameter names — schemas are the contract.
3. `call_tool` with exact toolset/tool name and arguments.
Cache what you learn for the session; re-list only after the editor side
changes (new plugin enabled, toolset authored, `RefreshTools` run).
The alternative eager mode (`Enable Tool Search` off in Editor Preferences)
advertises every tool as its own `mcp_unreal_engine_<tool>` entry. Discovery
then happens at `hermes mcp install`/`configure` time instead. Tool-search
mode is the default and what this skill assumes; it also keeps schema tokens
out of every API call, so prefer it.
See `references/tool-surface.md` for the shipped toolset catalog, authoring
custom toolsets, and the full plugin configuration/console-command reference.
## Operating Loop
Every Unreal task follows the same loop:
1. **Inspect first.** List toolsets, then query the scene/level state before
touching anything. Never assume an empty or default level. In an
unfamiliar project, also check for project-registered Agent Skills
(`call_tool` → `AgentSkillToolset.ListSkills`): a matching project skill's
instructions override this skill's generic defaults.
2. **Act in small, single-purpose calls.** One logical step per `call_tool`.
The server executes tools **serially on the game thread** — a big
monolithic operation freezes the editor UI until it finishes and risks
client timeouts.
3. **NEVER issue overlapping calls.** Do not batch multiple
`mcp_unreal_engine_*` calls in one turn — Hermes runs batched calls
concurrently, and parallel calls against the game thread deadlock or
fail. Strictly one call, await result, next call. This overrides the
general parallel-tool-calls guidance.
4. **Read every result.** Many tools (Blueprint compiles, material edits,
widget creation) report success/failure in the response body with no
protocol-level exception. Anything that isn't an explicit success is a
stop-and-diagnose, not a shrug. After property writes, read the value
back — several write paths silently no-op (see pitfalls).
5. **Verify visually and structurally.** After each milestone, confirm state
by querying the actors/properties you changed, and capture a viewport
screenshot when composition matters (see `references/tool-surface.md` for
the capture options; `vision_analyze` the image — you are the art
director, judge it).
6. **Save often.** Editor edits are in-memory until packages/levels are
saved; an editor crash loses everything since the last save, and MCP
edits are not reliably undoable. Save before AND after any bulk change,
and after every milestone.
7. **Report concretely.** Actor labels, asset paths (`/Game/...`), file
locations of captures/renders.
Rules of the world while you work:
- Units are **centimeters**; axes are **Z-up**, X-forward; rotations are
degrees (Rotator: Roll around X, Pitch around Y, Yaw around Z). Human eye
height ≈ 165 cm; a door ≈ 210×90 cm. Full tables in
`references/scene-craft.md`.
- Content paths use long package names: `/Game/Folder/Asset.Asset` for
project content, `/Engine/BasicShapes/Cube.Cube` for engine primitives.
- Actor **labels** (what you see in the Outliner, settable, non-unique) are
not actor **names** (internal, unique). Prefer resolving actors by
label/class queries, then hold on to whatever handle the tool returns.
- Prefer physically-plausible lighting values (lux/candela/Kelvin) over
arbitrary brightness numbers — `references/scene-craft.md` has the
numeric recipes (sun intensities, exposure, fog densities, moods).
## From Plain English to a Scene
The user gives intent, not specs. Translate before you build:
1. **Extract the brief.** Subject, mood, time of day, interior/exterior,
style, deliverable (screenshot? render? playable level?). Ask at most one
round of clarifying questions, then commit — you are the technical
director; don't bounce Unreal jargon back at the user.
2. **Plan the build order.** The order that works: level/environment shell →
blocking (major geometry/meshes in place) → lighting + atmosphere →
materials → set dressing/detail → camera → capture/render. Post the plan
as a todo list for multi-step builds.
3. **Build with the loop above**, one milestone at a time, screenshot at
each milestone.
4. **Art-direct yourself.** Compare each screenshot against the brief:
readable silhouette? believable light direction/intensity? horizon not
dead-center? scale correct against a human-height reference? Fix before
moving on.
5. **Deliver.** Screenshots/renders as files (`MEDIA:` path), plus a short
summary of what exists in the level and where it was saved.
`references/recipes.md` has complete worked builds (exterior daylight scene,
moody interior, golden-hour cinematic + render, asset import & placement)
with the exact call sequences and values.
## Reference Files
Load on demand; keep SKILL.md-level rules in mind throughout.
| Reference | Contents |
|---|---|
| `references/tool-surface.md` | Shipped toolsets catalog, discovery protocol detail, plugin console commands/CVars/flags, screenshot & capture paths, MCP Inspector debugging, extending with custom Python/C++ toolsets |
| `references/scene-craft.md` | Numeric cheat sheet: physical light intensities, color temperatures, exposure/EV100, fog densities, mood recipes (noon/golden hour/overcast/night/interior), scale tables, content path conventions |
| `references/recipes.md` | End-to-end worked builds with exact call sequences |
| `references/pitfalls.md` | Setup, runtime, and workflow pitfalls with fixes — read before your first session and whenever something misbehaves |
## Pitfalls (top of mind — full list in references/pitfalls.md)
- **Start order matters.** Editor + server up first, then the Hermes
session. Missing `mcp_unreal_engine_*` tools = wrong order.
- **One call at a time.** Serial game thread; no batching, no overlap.
- **The editor UI freezes during each call.** That's by design (game-thread
execution). Warn the user during long operations; keep calls small.
- **Modal dialogs block everything.** A tool call that opens (or collides
with) a modal editor dialog stalls until a human dismisses it. If a call
hangs indefinitely, tell the user to check the editor for a dialog.
- **Timeouts on long operations.** Hermes' per-call default is 120 s; asset
imports, big level saves, and renders can exceed it. Raise
`mcp_servers.unreal-engine.timeout` in `~/.hermes/config.yaml` for
render/import-heavy sessions.
- **Stale tool schemas.** After authoring/hot-reloading toolsets or enabling
a plugin, run `ModelContextProtocol.RefreshTools` in the editor console
and re-`list_toolsets`. New C++ `UFUNCTION`s need a full editor restart —
Live Coding won't surface them.
- **Experimental plugin.** APIs and tool shapes can change between engine
versions; trust `describe_toolset` over memory, including this skill's
examples. When docs and the live schema disagree, the live schema wins.
- **Don't expose the server beyond localhost.** Loopback-only, no auth, by
design. Never suggest binding it wider.
## Verification Checklist
- [ ] `list_toolsets` returns toolsets at session start (connection healthy)
- [ ] Scene state queried before first edit (never assumed empty)
- [ ] After each milestone: changed actors/properties re-queried and a
screenshot reviewed against the brief
- [ ] Level/dirty packages saved after each milestone and at the end
- [ ] Deliverables exist on disk (screenshot/render paths confirmed) and are
reported to the user with absolute paths
- [ ] Editor left in a clean state: no pending modal, no unsaved surprise,
user told exactly what was created/changed and where

View File

@ -0,0 +1,244 @@
# Unreal MCP — Pitfalls & Lessons
Read before your first session; return whenever something misbehaves. Ordered
by when they bite: setup → calling discipline → editor state → content →
delivery.
## Setup & Connection
### 1. Start order: editor first, Hermes second
Hermes probes MCP servers at session start. If the editor (and its server)
isn't up yet, no `mcp_unreal_engine_*` tools exist in the session. Fix:
launch the editor, confirm the server bound (Output Log shows
`LogModelContextProtocol` with the address), then open a NEW Hermes session.
Tools don't hot-appear mid-session.
### 2. Server enabled but no tools advertised
The Unreal MCP plugin ships the SERVER, not the tools. If `list_toolsets`
returns nothing/near-nothing, the toolset provider plugin (AllToolsets) or
Toolset Registry isn't enabled in this project. Fix in Edit > Plugins,
restart the editor, restart the Hermes session.
### 3. Port 8000 conflicts
Common collisions: local dev servers, Jupyter, other MCP hosts. Symptom: the
server fails to bind (Output Log) or Hermes' probe times out. Fix: change
Server Port Number in Editor Preferences > Model Context Protocol AND the
`url` in `~/.hermes/config.yaml` (`mcp_servers.unreal-engine`), then restart
both sides. Verify: `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/mcp`
(non-000 means something is listening; whether it's Unreal is a different
question — check the Output Log).
### 4. "Connection refused" mid-session
The editor was closed, crashed, or the server was stopped
(`ModelContextProtocol.StopServer`). Don't retry the tool in a loop — tell
the user, have them relaunch/restart the server, then reconnect (new session
if tools were lost).
### 5. GenerateClientConfig is not for Hermes
`ModelContextProtocol.GenerateClientConfig` writes config files for Claude
Code/Cursor/VSCode/Gemini/Codex into the project root. Hermes' connection
lives in `~/.hermes/config.yaml` via `hermes mcp install unreal-engine`.
Running GenerateClientConfig neither helps nor harms Hermes — just don't
mistake it for the Hermes setup step.
## Calling Discipline
### 6. One call at a time — never batch MCP calls
The server executes tool calls serially on the game thread and Epic
explicitly warns against overlapping calls. Hermes executes same-turn tool
calls concurrently — so batching two `mcp_unreal_engine_*` calls in one turn
IS issuing overlapping calls. Strictly sequential: call, await, then next.
This deliberately overrides the general "batch independent calls" guidance.
### 7. The editor freezes during every call — keep calls small
Game-thread execution means the editor UI hitches for the duration of each
tool call. A 30-second operation is 30 seconds of frozen editor. Split big
asks (e.g. "spawn 200 trees") into chunks so the user's editor stays
responsive and any failure loses only one chunk.
### 8. Modal dialogs deadlock the loop
Anything that pops a modal (some deletes, import options, save prompts,
experimental-plugin warnings) blocks the game thread — and your call —
until a human clicks. If a call hangs far beyond its normal duration, tell
the user to look at the editor for a dialog. Prefer tool paths/parameters
that avoid interactive prompts; save proactively so "unsaved changes"
prompts don't appear at bad times.
### 9. Timeouts: Hermes gives up before Unreal does
Hermes' default per-call timeout is 120 s. Asset imports, first-shader
compiles, big saves, and renders can exceed it — the call "fails" while the
editor happily finishes the work. Symptoms: timeout error, then the next
scene query shows the operation actually completed. Fixes: raise
`mcp_servers.unreal-engine.timeout` in config for heavy sessions; after any
timeout, RE-QUERY state before retrying, or you'll do the work twice
(duplicate actors are the classic case).
### 10. Stale schemas after editor-side changes
Toolsets are cached: after enabling a plugin, authoring a toolset, or Live
Coding, run `ModelContextProtocol.RefreshTools` in the editor console, then
re-run `list_toolsets`/`describe_toolset`. New C++ `UFUNCTION`s need a full
editor restart regardless. If a call fails with "unknown tool" that
`describe_toolset` just showed, refresh + reconnect.
### 11. Experimental means drift
Tool names, parameters, and result shapes may change across engine versions.
The live schema from `describe_toolset` is the only contract. If this
skill's examples and the live schema disagree, the schema wins — and patch
this skill afterward.
## Editor & Scene State
### 12. Never assume a fresh level
Query the scene before the first edit. The user's level may have existing
actors, a non-default sun, post-process volumes with exposure overrides —
your lighting changes can look wrong because of a pre-existing volume, not
your values.
### 13. In-memory edits are lost on crash — save per milestone
Everything you do lives in unsaved packages until a save happens. The editor
is an application that can crash, especially mid-experimental-feature. Save
the level + dirty packages after every milestone. Also: some operations
(level streaming, some asset moves) behave differently on unsaved assets.
### 14. Label ≠ Name ≠ path — use the full path as the stable identifier
Outliner shows actor LABELS (settable, duplicable, human-friendly). Internal
NAMES are unique per level but auto-generated (`StaticMeshActor_3`). The only
identifier that survives renames and disambiguates duplicates is the full
object path: `/Game/Maps/Level.Level:PersistentLevel.BP_Character_C_0`.
Tools may accept label, name, or path — read the schema. When you create an
actor, immediately set a meaningful label, and record whatever handle the
tool returns for later operations.
### 14b. Asset path forms mean different things
| Form | Example | Loads |
|---|---|---|
| Package | `/Game/Foo/Bar` | The package (asset-registry queries) |
| Package.Asset | `/Game/Foo/Bar.Bar` | The primary asset (most load/assign args) |
| Package.Asset_C | `/Game/Foo/Bar.Bar_C` | A Blueprint's **generated class** |
"Class not found: /Game/Path/BP_Foo" almost always means the missing `_C`
suffix — spawning by Blueprint class needs the generated-class form.
### 14c. Property writes can silently no-op — round-trip verify
UPROPERTY names are PascalCase at the reflection layer; snake_case lookups
through some write paths silently change nothing and return no error. After
any property write that matters, READ THE VALUE BACK and compare (allowing
formatting normalization like `1` vs `1.000000`). If it didn't take, retry
with the exact PascalCase name shown by the property dump/schema.
### 15. Play In Editor changes the world (literally)
If the user hits Play, queries/edits may target the transient PIE world, and
edits to it evaporate when play stops. If results look inexplicably
transient or actor lists suddenly differ, ask whether PIE is running; do
edit work outside PIE.
### 16. Undo exists, but don't lean on it
Editor transactions power Ctrl+Z; tool-driven changes may or may not create
clean transaction boundaries depending on the tool's implementation. Treat
undo as the user's manual escape hatch, not your rollback mechanism — your
rollback is: query state, compute the inverse edit, apply it.
## Content & Assets
### 17. Long package names, not file paths
Assets are addressed as `/Game/Folder/Asset.Asset` (project content),
`/Engine/...` (engine content), `/Script/Module.Class` (native classes).
Windows-style or absolute filesystem paths are wrong everywhere except
import/export file arguments and screenshot output paths.
### 18. Filesystem results land on the EDITOR host
Screenshots, renders, and exports write to the machine running Unreal (e.g.
`<Project>/Saved/Screenshots/...`). If Hermes runs elsewhere (SSH backend,
container), `read_file` on that path reads the wrong filesystem. Same-machine
setups (the default here) can read captures directly.
### 19. Referenced ≠ loaded
Engine basics (`/Engine/BasicShapes/...`) are always available, but project
assets may need loading before use, and a typo'd asset path often fails
soft (empty mesh, default material) rather than loud. After assigning
meshes/materials, re-query the actor to confirm the reference stuck.
### 20. Material edits: instances, not parents
Editing a parent Material recompiles shaders (slow, global blast radius).
Create a Material Instance (Dynamic or Constant per the tool surface), set
scalar/vector/texture parameters on it, assign to the mesh. Parameter names
must match the parent's exposed parameters exactly — query/describe before
setting; a misnamed parameter usually no-ops silently.
### 20b. Shader/asset compilation is async — don't judge or proceed early
Material creation/edits kick off shader compilation that can run seconds to
minutes; Niagara compiles, DDC builds, and package saves are async too. A
screenshot taken mid-compile shows the old (or default-checkerboard) state.
After material work, wait for compilation before judging visuals (poll a
compile/errors predicate if the tool surface has one; otherwise screenshot
after a delay and re-check if it looks wrong). Same discipline after saves:
don't chain a disk-read straight after a write.
### 20c. Emissive needs intensity > 1 to bloom
Emissive at ≤1.0 looks self-lit but never blooms. 310 gives visible glow —
and the Post Process Volume must have Bloom enabled (default on).
### 20d. Crash patterns to avoid outright
Engine-level, any server: (a) deleting or transforming an ASSET while level
actors still reference it → `RegisteredElementType` assertion, editor down,
unsaved work gone — walk references first, swap actors to a replacement,
then delete; (b) spawn→delete→spawn the same actor in rapid succession can
corrupt the actor registry — don't tight-loop create/destroy cycles;
(c) Niagara/MetaSound assertion during PIE reverts to last on-disk save —
save BEFORE entering PIE when those subsystems are involved.
## Delivery
### 21. Screenshot judgment is part of the job
Don't declare a lighting/composition milestone done from numbers alone —
capture the viewport, `vision_analyze` it, and art-direct (silhouette,
exposure, horizon placement, scale against human height). The user is
non-technical; you are the one with eyes on both the brief and the frame.
### 21b. Editor screenshots show sprite icons that look like content
Viewport captures in the editor include per-component sprite icons (light
bulbs, speaker/Niagara icons). They are editor overlay, NOT your scene —
particles especially: an editor screenshot is not proof a Niagara effect is
emitting. Verify effects via the actor's active state or a PIE capture.
### 22. Report package paths + file paths
The user needs: what actors/assets now exist (labels + `/Game/...` paths),
where the level was saved, and absolute filesystem paths of any
captures/renders (delivered as `MEDIA:` where appropriate).
## Sources
Grounded in Epic's UE 5.8 Unreal MCP documentation, Epic's own agent-facing
skill pack for this server (unreal-engine-skills-for-claude-code), and
engine-level field reports from the UE-via-MCP community (ue5-mcp field
manual). Engine behaviors (crash patterns, Lumen mobility, async compiles,
reflection casing) are server-agnostic; tool names and schemas remain
whatever the live `describe_toolset` says.

View File

@ -0,0 +1,222 @@
# Unreal MCP — Worked Recipes
Complete build sequences from a plain-English brief to a delivered capture.
Written against the discovery contract, because the live tool surface is
project-dependent: each step names the **capability to locate** (via
`list_toolsets` / `describe_toolset`) and the **exact values** to feed it —
not hardcoded tool names, which drift while the plugin is experimental.
## Recipe grammar
Every step = four parts:
INTENT what this step achieves
DISCOVER which toolset/tool capability to use (locate via describe_toolset)
VALUES the exact arguments/numbers (from scene-craft.md)
VERIFY the query or screenshot that proves it worked
Dispatch shape: `call_tool` with `toolset_name`, `tool_name`, and an
`arguments` object matching the described schema — result returns on the
same turn.
LIGHTING RULE for every recipe: set **Mobility = Movable** on every light
you spawn (Lumen GI ignores Static/Stationary lights — the #1 "why is GI
dead" cause).
Session preamble for every recipe (do once):
1. `list_toolsets` → note actor/scene/material/level capability groups.
2. `describe_toolset` on each group you'll touch → cache schemas.
3. Query current level state (all actors + classes) → never assume empty.
4. Locate the screenshot path for this project:
`<Project>/Saved/Screenshots/<Platform>/`.
Save the level + dirty packages after every phase marked 💾. One tool call
at a time throughout — no batching, ever.
---
## Recipe A — Daylight exterior clearing (blocking-first exterior)
Brief: "a sunny clearing with some rocks and a path"
**Phase 1 — environment shell**
- INTENT sky + sun + atmosphere exist and track each other.
DISCOVER actor-spawn capability (spawn by class).
VALUES spawn `SkyAtmosphere`, `SkyLight` (real-time capture),
`DirectionalLight` at rotation (0, 55, 40) [roll, pitch, yaw],
intensity 90,000 lux, temperature 5,800 K, "atmosphere sun light" on;
`ExponentialHeightFog` density 0.008.
VERIFY actor list shows all four; screenshot reads as daytime sky, not
black (if black: exposure — see Phase 3).
**Phase 2 — ground & blocking**
- INTENT walkable ground plane.
DISCOVER spawn-from-asset capability.
VALUES `/Engine/BasicShapes/Plane.Plane` at (0,0,0), scale (100,100,1)
→ 100×100 m ground. Label `Ground`.
- INTENT rock cluster + path silhouette from primitives (placeholder for
real assets if the project has none).
VALUES 59 `/Engine/BasicShapes/Cube.Cube` at scattered locations within
±2,000 cm of origin, non-uniform scales between (1.5,1.5,1) and (4,3,2),
yaws randomized 0360°, sunk 1030 cm into the ground so nothing floats.
A path: 610 flattened cubes scale ≈(1.2,0.8,0.05) snaking through.
VERIFY screenshot at eye height (camera z≈165) along the path axis: rocks
read as varied, nothing floats, scale sane against the 180 cm yardstick
(place one 180 cm-tall cylinder temporarily as a human stand-in, delete
after checking). 💾
**Phase 3 — exposure & mood**
- INTENT deterministic exposure.
DISCOVER PostProcessVolume spawn + property-set capability.
VALUES PPV unbound=true, metering Manual, EV100 = 14.5.
VERIFY screenshot: bright but not blown; shadows readable.
**Phase 4 — deliver**
- INTENT hero still.
VALUES `HighResShot 3840x2160` from a framed viewpoint (see Recipe C
Phase 2 for framing rules).
VERIFY file exists in Saved/Screenshots; `vision_analyze` against brief;
iterate lighting yaw/fog once if flat. 💾 Report actor labels + paths.
---
## Recipe B — Moody practical-lit interior
Brief: "a dim cozy room at night, warm lamp, blue moonlight through window"
**Phase 1 — room shell from primitives**
- VALUES floor: Cube at (0,0,10) scale (6,6,0.2) → 6×6 m room. Four walls:
cubes scale (6,0.2,3) / (0.2,6,3) positioned at ±300 on the respective
axis, z=140 (walls 280 cm tall, sitting on the floor plane; keep tops at
z≈290). Ceiling: cube scale (6,6,0.2) at z≈290 — spawn it LAST so you can
screenshot the interior while open-topped. One window: leave a gap in a
wall by using two shorter wall segments with a 120×120 cm opening at
sill height 90 cm.
VERIFY top-down + interior screenshots; door/window heights sane.
**Phase 2 — lighting (the point of this recipe)**
- INTENT kill the sun; interior reads as night.
VALUES if the template level has a DirectionalLight: intensity → 0.05 lux
temperature 4,300 K, pitch 20°, yawed to rake through the window (this
is the "moon"). SkyLight intensity scale down to ≈0.050.1.
- INTENT warm practical.
VALUES PointLight at lamp position (e.g. corner table, z≈120):
800 lumens (or ≈64 candela), temperature 2,700 K, attenuation radius
600 cm, source radius 10 cm (softer shadows).
- INTENT cool window rim.
VALUES SpotLight outside the window aimed through it: 2,000 lumens,
6,5008,000 K if faking without moon; skip if the directional moon
already rakes through visibly. Inner/outer cone 25°/50°.
- INTENT exposure for dim interior.
VALUES PPV unbound, Manual, EV100 = 4.5; fog: ExponentialHeightFog
density 0.015 + volumetric fog on; practical's volumetric scattering
intensity 24 so the lamp glows.
VERIFY screenshot from a corner at z≈160: warm pool around lamp, cool
slash from window, deep-but-readable shadows. The warm/cool split IS the
deliverable — iterate intensities (never move both at once) until it
reads. 💾
**Phase 3 — dress & deliver**
- VALUES if Starter Content exists, swap primitives: `/Game/StarterContent/
Props/SM_TableRound`, `SM_Chair`, `SM_Lamp_Ceiling`, materials
`M_Wood_Pine` on floor, `M_Basic_Wall` on walls. Otherwise assign
MaterialInstances with warm-neutral base colors to primitives.
VERIFY final `HighResShot 3840x2160`, vision-check, 💾, report.
---
## Recipe C — Golden-hour cinematic still (camera craft)
Brief: "make it golden hour and give me a cinematic shot of <subject>"
**Phase 1 — relight for golden hour**
- VALUES DirectionalLight: intensity 12,000 lux, temperature 3,200 K,
pitch 8°, yaw set so the sun is 3060° OFF the camera axis behind the
subject (rim + long shadows — never light flat from the camera).
Fog density 0.02 + volumetric fog, sun volumetric scattering 26.
PPV EV100 = 11.
VERIFY screenshot: long shadows, warm rim on subject edges.
**Phase 2 — the camera**
- INTENT a framed CineCamera, not a viewport eyeball.
DISCOVER CineCameraActor spawn + property-set; viewport-pilot or
camera-view capability if advertised.
VALUES position: subject-distance by lens — 85 mm at 400600 cm for a
prop/character subject; height 120160 cm; aim so subject sits on a
thirds intersection, horizon in upper or lower third (not center).
Focal 85 mm, aperture f/2.0, manual focus distance = measured
camera→subject distance (compute from the two locations).
VERIFY screenshot THROUGH this camera (pilot it / set viewport to its
view first — confirm by a cheap `HighResShot 1` before the big one).
**Phase 3 — deliver**
- VALUES `HighResShot 3840x2160` (or user's target res) through the
piloted camera. For a sequence/turntable instead of a still: this needs a
Level Sequence with a Camera Cut track + Movie Render Queue — treat as
its own task; warn about first-render shader-compile stall.
VERIFY read file, `vision_analyze`: rim light present? focus falloff on
the right plane? horizon off-center? Iterate at most twice, then deliver
MEDIA: path + what was changed. 💾
---
## Recipe D — Import an asset and populate the scene
Brief: "here's model.fbx / a Fab asset — put a ring of them around the fountain"
**Phase 1 — import**
- INTENT asset lands in `/Game/Imported` with no dialog stall.
DISCOVER import capability (if none advertised: custom toolset wrapping
`unreal.AssetImportTask` with `automated=True` — see tool-surface.md;
the `automated` flag is what prevents a modal import dialog from
freezing the whole MCP loop).
VALUES destination `/Game/Imported`, save=true.
VERIFY asset-exists query on the resulting long package name; spawn one
instance at origin, screenshot, check scale against 180 cm yardstick —
DCC exports are routinely 100× off (meters vs centimeters). Fix by
actor scale or reimport with unit conversion.
**Phase 2 — populate**
- INTENT ring of N instances around a center C.
VALUES for i in 0..N1: angle θ=360·i/N, position = C + (r·cosθ,
r·sinθ, 0) with r = fountain radius + clearance (e.g. 350 cm), yaw =
θ+90° so each faces the center (or +270° to face outward — check one
instance first and LOOK). Spawn one, verify facing, then loop the rest
one call at a time.
VERIFY count query matches N; screenshot from above (camera z≈1,500
looking down) for spacing; eye-level screenshot for scale. 💾
**Phase 3 — deliver**
- Report: asset path, N instances with label prefix, level saved,
overview + eye-level captures as MEDIA paths.
---
## When a recipe's capability is missing
If discovery shows no shipped tool for a step (no import tool, no
console-exec for HighResShot, etc.):
1. Say so plainly; don't fake the step.
2. Offer the custom-toolset path (tool-surface.md) — a 20-line Python
toolset usually covers the gap; it needs `RefreshTools` + session
restart to appear.
3. Or hand the user the one-liner to run in the editor's Python/console
themselves, with exact text.
Never claim a phase done without its VERIFY evidence. The user can't check
the editor for you — the screenshots are the ground truth they see.

View File

@ -0,0 +1,271 @@
# Unreal MCP — Scene-Craft Cheat Sheet
The numbers and conventions that make a scene read as *good* instead of
merely present. Sources: physical/photographic standards (stable), UE
conventions (stable), and practical ranges from production use (marked ≈).
UE-version-specific defaults drift; when a live schema or editor value
disagrees with this sheet, trust the editor and patch the sheet.
## Units & Conventions (bedrock — memorize)
| Thing | Convention |
|---|---|
| Distance | 1 Unreal Unit = **1 cm** |
| Axes | **Z-up**, X-forward, Y-right (left-handed) |
| Rotation | Rotator in **degrees**: Roll (around X), Pitch (around Y), Yaw (around Z) |
| Color | Linear RGBA, each channel 01 (`FLinearColor`) |
| Light color | Prefer `use_temperature` + Kelvin over tinting RGB |
| Scale | Multiplier per axis (1,1,1 = authored size) |
Directional-light aiming: the ROTATION points the light. Pitch 90° = sun
straight overhead (noon); pitch 5° to 15° = sun grazing the horizon
(golden hour); yaw picks the compass direction the light travels toward.
### Human-scale reference (sanity-check every layout against these)
| Reference | Size (cm) |
|---|---|
| Eye height (standing) | 160175 |
| Door | 200210 tall × 8090 wide |
| Ceiling, residential | 240300 |
| One building storey | 300400 |
| Counter/desk height | 75110 |
| Chair seat | 45 |
| Stair riser / tread | ≈18 / ≈28 |
| UE default mannequin | ≈180 tall |
| Car | ≈450 long × 180 wide × 145 tall |
If a "house" door comes out 400 cm tall, the scene reads as toy/giant.
Always place one human-scale object early as a yardstick.
## Content Paths
| Root | Meaning |
|---|---|
| `/Game/...` | Project content (Content/ folder) |
| `/Engine/...` | Engine-shipped content, present in every project |
| `/Script/Module.Class` | Native classes (e.g. `/Script/Engine.PointLight`) |
Long package name form: `/Game/Props/SM_Chair.SM_Chair` (package.object).
Always-available engine primitives (great for blocking before real assets):
/Engine/BasicShapes/Cube.Cube (100×100×100 cm at scale 1)
/Engine/BasicShapes/Sphere.Sphere (100 cm diameter)
/Engine/BasicShapes/Cylinder.Cylinder (100 cm ⌀ × 100 cm)
/Engine/BasicShapes/Cone.Cone
/Engine/BasicShapes/Plane.Plane (100×100 cm, single-sided)
Their default material is plain grey; assign a MaterialInstance for anything
presentational. If the project has Starter Content, useful packs live under
`/Game/StarterContent/` (Props, Materials like `M_Basic_Wall`, `M_Wood_Pine`,
`M_Metal_Steel`, Particles). Query before assuming Starter Content exists.
Common actor classes for spawning: `StaticMeshActor`, `PointLight`,
`SpotLight`, `RectLight`, `DirectionalLight`, `SkyLight`,
`ExponentialHeightFog`, `SkyAtmosphere`, `VolumetricCloud`,
`PostProcessVolume`, `CameraActor`, `CineCameraActor`, `PlayerStart`.
## Lighting — physically based values
UE5 lights default to physical units (directional in lux, point/spot in
candela or lumens, exposure in EV100). Use real-world values; they
compose correctly with exposure instead of fighting it.
### Sun (DirectionalLight, lux)
| Condition | Intensity (lux) | Pitch | Temperature |
|---|---|---|---|
| Noon, clear | 75,000120,000 | 60° to 90° | 5,5006,000 K |
| Afternoon | 40,00075,000 | 30° to 50° | 5,0005,500 K |
| Golden hour | 5,00020,000 | 5° to 15° | 2,8003,500 K |
| Overcast | 5,00020,000 (soft) | 45° ± | 6,5007,500 K |
| Blue hour / dusk | 10100 | 2° to +5° | 8,00012,000 K |
| Full-moon night | 0.050.3 | 30° to 60° | 4,0004,500 K (cool-blue read comes from exposure + grade) |
Overcast: also drop directional shadow contrast (soften via larger source
angle) and let the sky light dominate.
### Sky light
One SkyLight per level, Real-Time Capture (SLS Captured Scene) when using
SkyAtmosphere — it then tracks the sun automatically. Don't stack multiple
sky lights; don't leave a stale static capture after big lighting changes
(recapture if not real-time).
### Local lights (point/spot/rect)
Rules of thumb in lumens (candela ≈ lumens/(4π) for a point light):
| Source | Lumens |
|---|---|
| Candle flame | 1015 (≈1,850 K) |
| 40 W incandescent equiv. | 450 (2,700 K) |
| 60 W equiv. | 800 (2,7003,000 K) |
| 100 W equiv. | 1,600 (3,000 K) |
| Bright ceiling fixture | 2,0004,000 (3,0004,000 K) |
| Fluorescent tube / office | 2,5005,000 (4,0005,000 K) |
| Streetlight (sodium) | 5,00015,000 (≈2,000 K, orange) |
| Car headlight | 1,0001,500 each (4,3006,000 K) |
| Campfire | 100300, flicker (1,7002,000 K) |
Spot cone: inner 2035°, outer 4060° for a natural falloff. Attenuation
radius: keep tight (a few hundred cm for practicals) — giant radii cost
performance and flatten the scene. Cast-shadow off for pure fill lights.
### Color temperature vocabulary (Kelvin)
1,7001,900 match/candle · 2,700 warm bulb · 3,200 tungsten studio ·
3,500 golden hour · 4,300 moonlight-read · 5,600 daylight/flash ·
6,500 overcast · 7,50010,000 shade/blue hour.
Warm subject + cool ambient (or inverse) is the cheapest way to make a
shot read "lit" instead of "flat".
### Exposure (PostProcessVolume — the #1 "why is it black/white" knob)
Auto-exposure fights deterministic lighting reads. For agent-driven work,
prefer **manual exposure** in a PPV:
1. Spawn/locate a PostProcessVolume, set **Infinite Extent (Unbound) = true**.
2. Metering Mode = Manual, then set Exposure Compensation ≈ 0 and EV100 to
the scene value:
| Scene | EV100 |
|---|---|
| Bright sun exterior | 1416 |
| Overcast exterior | 1113 |
| Golden hour | 1012 |
| Bright interior (day, windows) | 79 |
| Dim practical-lit interior | 46 |
| Street at night | 24 |
| Moonlit exterior | 2 to 0 |
If you keep auto-exposure instead (Metering: Auto Histogram), clamp it:
Min/Max EV100 within ±2 of the target so it can't swim. Symptom table:
scene renders black with lights present → EV100 too high for the light
levels; blown white → EV100 too low.
### Global illumination & reflections
UE5 defaults: **Lumen** GI + Lumen reflections, no lightmass bake needed —
lighting is live; just keep "Allow Static Lighting" defaults alone.
**Critical: Lumen GI only considers lights with Movable mobility.** Spawned
lights can default to Stationary/Static and then contribute nothing to GI —
set Mobility = Movable explicitly on every light you place, and check
mobility first when "GI isn't working". Metal/mirror surfaces read
correctly only with something to reflect: give the scene a sky and
surroundings before judging materials.
### Fog & atmosphere
- **SkyAtmosphere** for a physically-plausible sky (sun disk, horizon
gradient); pairs with directional light "Atmosphere Sun Light = true".
- **ExponentialHeightFog**: density default 0.02. ≈ Practical ranges:
0.0050.015 subtle depth cue · 0.020.05 moody/morning · 0.050.2 heavy.
Enable **Volumetric Fog** on it for light shafts through it; then dial
per-light "Volumetric Scattering Intensity" (110) on the key lights.
- **VolumetricCloud** for real sky clouds (exterior only; costs GPU).
- Night sky: drop fog density, add faint cool fill (skylight at low
intensity) so shadows aren't pure black.
## Mood recipes (compact)
| Mood | Sun/Key | Sky | Fog | EV100 | Grade notes |
|---|---|---|---|---|---|
| Crisp noon | 100k lux, pitch 70°, 5,800 K | Real-time capture | 0.005 | 15 | Neutral |
| Golden hour | 10k lux, pitch 8°, 3,200 K | Real-time capture | 0.02 + volumetric | 11 | Warm key, long shadows: rotate yaw for rim/side light |
| Overcast | 10k lux soft, 7,000 K | Dominant | 0.01 | 12 | Low contrast, saturation carries color |
| Night, moonlit | 0.25 lux, 4,300 K + practicals ≈800 lm 2,700 K | Very low | 0.015 | 1 to 1 | Cool ambient vs warm practicals |
| Horror interior | No sun; 12 practicals, hard shadows | Minimal | 0.030.06 volumetric | 45 | Single motivated key, deep blacks |
| Sci-fi corridor | Rect lights 2,000 lm 6,5008,000 K + colored accents | None | 0.02 volumetric | 6 | Complementary accent pair (cyan/orange) |
## Camera & framing (CineCameraActor)
Use CineCameraActor (not plain Camera) for anything presentational — it has
real filmback/lens/DoF controls.
| Intent | Focal length | Aperture |
|---|---|---|
| Establishing / interior wide | 1828 mm | f/5.68 |
| Neutral "human eye" | 3550 mm | f/4 |
| Portrait / subject isolation | 85135 mm | f/1.42.8 |
| Compression (stacked background) | 100200 mm | f/2.85.6 |
- Filmback: default 16:9 digital film (≈23.76 × 13.365 mm) is fine; leave it.
- Focus: Manual focus distance = distance camera→subject in cm; shallow DoF
needs long lens + wide aperture + subject far from background.
- Placement: eye-level ≈ 155170 cm for neutral shots; below ≈ 100 cm =
heroic/imposing; high angle = diminishing. Keep the horizon off
dead-center; put subjects on thirds. Slight camera pitch (2° to 8°)
usually beats perfectly level for interiors.
- Aspect/eye candy: enable camera's "Constrain Aspect Ratio" for clean
letterboxed stills.
## Capture & render
- **Viewport screenshot**: console `HighResShot 1` (viewport res),
`HighResShot 2` (2×), or `HighResShot 3840x2160`. Output:
`<Project>/Saved/Screenshots/<Platform>/`. Filenames auto-increment
(`HighresScreenshot00000.png`).
- To frame from a camera: pilot/possess the CineCamera (or set viewport to
its view) before HighResShot; verify by screenshotting first at 1×.
- **Movie Render Queue** (MRQ) is the quality path for finals/sequences:
needs a Level Sequence with the camera bound (Camera Cut track); renders
PNG/EXR sequences or stills at arbitrary resolution with anti-aliasing
temporal sample counts. First render after opening a project stalls on
shader compilation — warn the user, don't declare it hung.
- Judge results by looking: read the file back and `vision_analyze` every
capture against the brief.
## Editor Python quick reference
Custom toolsets and any shipped Python-execution tool speak the `unreal`
module. Canonical entry points (verify names against the live editor —
Epic migrates libraries to subsystems over time):
```python
import unreal
# Actors (EditorActorSubsystem supersedes EditorLevelLibrary for these)
eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actors = eas.get_all_level_actors()
actor = eas.spawn_actor_from_class(unreal.PointLight, unreal.Vector(0, 0, 200))
mesh_a = eas.spawn_actor_from_object(
unreal.EditorAssetLibrary.load_asset("/Engine/BasicShapes/Cube.Cube"),
unreal.Vector(0, 0, 50))
actor.set_actor_label("Key Light")
actor.set_actor_location(unreal.Vector(100, 0, 250), False, True)
actor.set_actor_rotation(unreal.Rotator(0, -30, 45), True) # roll, pitch, yaw
eas.destroy_actor(actor)
# Assets
unreal.EditorAssetLibrary.does_asset_exist("/Game/Props/SM_Chair")
unreal.EditorAssetLibrary.list_assets("/Game/Props", recursive=True)
unreal.EditorAssetLibrary.save_directory("/Game", only_if_is_dirty=True)
# Level save
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
les.save_current_level()
# Undo-friendly mutation
with unreal.ScopedEditorTransaction("Agent: dress set") as trans:
... # property edits inside are one undo step
# Import (FBX/textures)
task = unreal.AssetImportTask()
task.filename = "/abs/path/model.fbx"
task.destination_path = "/Game/Imported"
task.automated = True # suppresses the import dialog — critical for MCP
task.save = True
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Editor property access works on anything reflected
light_comp = actor.get_component_by_class(unreal.PointLightComponent)
light_comp.set_editor_property("intensity", 800.0)
light_comp.set_editor_property("use_temperature", True)
light_comp.set_editor_property("temperature", 2700.0)
```
`set_editor_property`/`get_editor_property` with snake_case names is the
universal fallback when a dedicated setter doesn't exist — property names
match what the Details panel shows (spaces removed).

View File

@ -0,0 +1,274 @@
# Unreal MCP — Tool Surface Reference
How Epic's editor-embedded MCP server organizes, advertises, and executes
tools, and how to extend the surface when the shipped tools run out.
Everything here is against UE 5.8's experimental plugin (id
`ModelContextProtocol`); expect drift between engine versions — the live
`describe_toolset` schema always outranks this file.
## Architecture in one paragraph
The **Unreal MCP** plugin hosts the HTTP server inside the editor process
(default `http://127.0.0.1:8000/mcp`, loopback-only, no auth, HTTP + SSE
only — no stdio/WebSocket). It implements the protocol but ships no tools of
its own. Tools come from **Toolsets** — classes deriving from
`UToolsetDefinition` (C++) or `unreal.ToolsetDefinition` (Python) — collected
at startup by the **Toolset Registry** subsystem (sibling plugin,
auto-enabled). Epic's shipped toolsets are delivered by a separate toolset
provider plugin (**AllToolsets**); project plugins and Game Feature Plugins
can contribute more. Unreal MCP wraps every registered tool call as an MCP
Tool. Execution is **serialized onto the game thread** — one tool call at a
time, editor UI blocked while each runs.
## Tool-search mode (the default contract)
With `Enable Tool Search` on (default), `tools/list` advertises exactly three
meta-tools:
| Meta-tool | Args | Returns |
|---|---|---|
| `list_toolsets` | — | Registered toolset names + descriptions |
| `describe_toolset` | toolset name | JSON Schemas for every tool in that toolset |
| `call_tool` | toolset/tool name + arguments object | The tool's result, same turn |
Discipline:
- `list_toolsets` once per session; re-run only after `RefreshTools`, plugin
changes, or reconnect.
- `describe_toolset` before first use of any toolset. Parameter names, types,
and required fields come from the schema — never from memory or this file.
- Results: primitive results arrive wrapped as `{"result": ...}` (CVar
`ModelContextProtocol.WrapPODToolResultsInObject`, default true).
Structured results serialize with field-level schema.
- Errors come back as tool-call errors with the engine-side message — read
them; they usually name the offending parameter or missing asset.
Eager mode (`Enable Tool Search` off) advertises every tool individually.
Under Hermes that means each tool becomes `mcp_unreal_engine_<tool_name>` at
session start, and `hermes mcp configure unreal-engine` can prune the list.
Schema payload grows with every registered toolset, and tool authors are told
NOT to rely on eager advertising — stay in tool-search mode unless a very
small fixed surface is wanted.
## call_tool dispatch semantics
Confirmed against Epic's own agent-facing skill for this server:
- `call_tool` takes `toolset_name`, `tool_name`, and an `arguments` object
matching the schema from `describe_toolset`. The result returns on the
same turn.
- Tool identities are effectively dotted: `BlueprintTools.create`,
`SequencerTools.create_level_sequence`,
`LiveCodingToolset.CompileLiveCoding` — they are dispatched server-side
and never appear as native MCP tools while tool search is on.
- Top-level dispatch (omitting `toolset_name`) is reserved for tools
registered directly on the MCP server — and is rejected for `call_tool`
itself.
## Shipped toolsets
The registry is project-dependent; treat this as orientation, not contract —
`describe_toolset` on the live server is the only source of truth for tool
names and schemas. Epic's plugin pack describes the shipped surface as
"hundreds of tools across 30+ toolsets": actors, blueprints, materials,
Niagara, Control Rigs, Sequencer, State Trees, widgets, Gameplay Ability
System, automation testing, Live Coding.
Toolset names confirmed in Epic's docs and Epic's own Claude Code skill pack:
| Toolset | Scope |
|---|---|
| `SceneTools` | Scene-level queries and edits |
| `ActorTools` | Inspect/modify actors: transforms, labels, parent-child relationships, components |
| `MaterialInstanceTools` | Create/configure material instances |
| `MaterialTools` | Material assets |
| `ObjectTools` | Generic UObject property inspection/editing |
| `BlueprintTools` | Blueprint creation/editing (e.g. `BlueprintTools.create`) |
| `StaticMeshTools` | Static mesh asset operations |
| `LevelTools` | Level operations |
| `SequencerTools` | Level Sequences (e.g. `create_level_sequence`) |
| `LiveCodingToolset` | C++ Live Coding recompile (`CompileLiveCoding` blocks until the compile finishes and surfaces compiler diagnostics) |
| `AgentSkillToolset` | Project-registered Agent Skills (see below) |
| `GASToolsets` (plugin, C++) | Gameplay Ability System attributes — ships disabled; enabling prompts an experimental-feature warning |
Known gap: the shipped toolsets contain **no mesh-modelling tools** — you
can spawn/place/instance existing meshes but not author new geometry. The
supported route to parametric geometry is a custom Python toolset wrapping
**Geometry Script** (`UDynamicMesh`: append box/cylinder/sphere, booleans,
then `Create New Static Mesh Asset from Mesh` to bake an `SM_` asset). For
organic/sculpted meshes, model in Blender (`blender-mcp` skill) and import.
First-session move: `list_toolsets`, then `describe_toolset` each group you
plan to use, and keep those schemas in working memory for the session.
## Project Agent Skills (AgentSkillToolset)
Projects and plugins can register **Agent Skills** — named instruction
bundles for project-specific conventions and workflows (naming schemes,
folder layout, canonical multi-step sequences). They are NOT listed by
`list_toolsets`; reach them through `call_tool`:
1. `AgentSkillToolset.ListSkills` → names + descriptions of registered
skills.
2. If one matches the task, `AgentSkillToolset.GetSkills` on it → full
instructions, then FOLLOW THEM — a project skill exists precisely
because the project's way differs from the obvious way, and it takes
precedence over this skill's generic defaults.
Check at the start of unfamiliar work in any project, not just once ever.
## Seeing your work: screenshots and captures
An agent that can't see the viewport is flying blind. In order of preference:
1. **A shipped screenshot/viewport tool, if the registry advertises one**
check `list_toolsets`/`describe_toolset` output for viewport, screenshot,
or thumbnail capture tools and use those (they return the image through
MCP directly).
2. **Console command via any shipped console/exec tool**: `HighResShot 1`
writes the current viewport to
`<Project>/Saved/Screenshots/<Platform>/` on the EDITOR host's
filesystem. `HighResShot 3840x2160` for fixed resolution,
`HighResShot 2` for 2× viewport. Read the file back with `read_file`/
`vision_analyze` (same machine) — remember paths resolve on the editor
host.
3. **Custom toolset escape hatch** (below) exposing
`unreal.AutomationLibrary.take_high_res_screenshot()` or viewport
capture, when nothing shipped covers it.
Always `vision_analyze` the capture and art-direct against the brief before
declaring a milestone done.
## Plugin configuration reference
Editor Preferences > General > Model Context Protocol:
| Property | Default | Notes |
|---|---|---|
| Auto Start Server | `false` | Turn on for frictionless sessions |
| Server Port Number | `8000` | Change on conflict; mirror in Hermes config url |
| Server URL Path | `/mcp` | Same |
| Enable Tool Search | `true` | Keep on (see above) |
Console commands (editor console, backtick):
| Command | Effect |
|---|---|
| `ModelContextProtocol.StartServer [port]` | Start server (optional port override) |
| `ModelContextProtocol.StopServer` | Stop server, close all sessions |
| `ModelContextProtocol.RefreshTools` | Re-poll toolset providers — run after authoring/hot-reload/Game-Feature activation |
| `ModelContextProtocol.GenerateClientConfig <Client\|All>` | Write client config files (ClaudeCode/Cursor/VSCode/Gemini/Codex) — NOT used for Hermes |
Command-line flags for launching the editor pre-configured:
`-ModelContextProtocolStartServer` (force start regardless of preference),
`-ModelContextProtocolPort=N`.
Console variables:
| CVar | Default | Notes |
|---|---|---|
| `ModelContextProtocol.WrapPODToolResultsInObject` | `true` | Primitive results wrapped as `{"result": ...}` |
| `ModelContextProtocol.AudioResultOggFormat` | `false` | OGG instead of WAV for audio results |
| `ModelContextProtocol.ProgressIntervalSeconds` | `1.0` | Min interval between progress notifications |
| `ModelContextProtocol.PaginationPageSize` | `0` | 0 = no pagination of list results |
| `ModelContextProtocol.EnableAnalytics` | `true` | Epic telemetry gate |
## Debugging the connection
- **Output Log** at editor startup logs bind address/port/path — first stop
when the server seems absent. Port-in-use and missing-dependency failures
surface here.
- **Log verbosity:** `Log LogModelContextProtocol Verbose` in the editor
console.
- **MCP Inspector** (`npx @modelcontextprotocol/inspector`, point at
`http://127.0.0.1:8000/mcp`, transport "Streamable HTTP") lists every
advertised tool with schemas and offers form-based invocation — isolates
"server broken" from "agent calling it wrong".
- **After Live Coding / authoring:** connected clients can hold stale
schemas. `ModelContextProtocol.RefreshTools`, then reconnect (new Hermes
session) if schemas still look stale.
## Extending the surface: custom toolsets
When shipped tools don't cover an operation, the supported path is authoring
a project toolset — NOT trying to smuggle arbitrary code through unrelated
tools. Python toolsets are first-class and hot-loadable, so prefer them.
### Python toolset (recommended)
Any enabled plugin's `Content/Python/` directory (or the project's) can hold
toolset modules; the registry discovers them at startup. Shape (mirrors
Epic's shipped `ActorTools`):
```python
import unreal
import toolset_registry
@unreal.uclass()
class MySceneTools(unreal.ToolsetDefinition):
"""One-line toolset description — surfaces to the agent in list_toolsets."""
@toolset_registry.tool_call
@staticmethod
def take_viewport_screenshot(filename: str, width: int, height: int) -> str:
"""Capture the active viewport to Saved/Screenshots.
Args:
filename: Base filename without extension.
width: Output width in pixels.
height: Output height in pixels.
Returns:
Absolute path the screenshot will be written to.
"""
...
```
Conventions that matter (they generate the schema the agent sees):
- `@unreal.uclass()` on the class; inherit `unreal.ToolsetDefinition`.
- Class docstring = toolset description; write it for an agent audience.
- Each advertised function: `@toolset_registry.tool_call` + `@staticmethod`.
Functions without the decorator stay private.
- Type hints (`str`, `bool`, `list[str]`, `unreal.Actor`, dataclasses) drive
the JSON Schema; Google-style docstrings (`Args:`/`Returns:`) become the
parameter descriptions. Write them with API-surface care.
- Small, single-responsibility tools with structured return types beat
mega-tools returning prose. Data leaves the tool via its RETURN VALUE —
`print()`/stdout go to the UE log, not back over MCP.
After authoring: `ModelContextProtocol.RefreshTools` in the editor console,
then re-`list_toolsets` from Hermes. Users on Claude Code can scaffold with
the `create-toolset` skill from Epic's `unreal-mcp` plugin pack; the
conventions above still apply.
### C++ toolset
Derive from `UToolsetDefinition`, mark the class `UCLASS(BlueprintType,
Hidden)`, expose static `UFUNCTION(meta = (AICallable))` methods; doc
comments reflect into schemas. Use only when Python can't reach the API,
when reflected `USTRUCT` signatures are needed, or when the Python boundary
cost matters. Exclude a function with `meta = (AIIgnore)`. Live Coding
propagates edited function bodies, but NEW `UFUNCTION`s require a full
editor restart. There is also a direct-registration path
(`IModelContextProtocolTool` + `IModelContextProtocolModule::AddTool()`) for
runtime-shaped tools; caller owns deregistration.
## Runtime and cooked builds
The server is editor-hosted by default but not editor-only: runtime modules
can host it in cooked builds via `IModelContextProtocolModule::StartServer()`.
The Toolset Registry adapter (and the three tool-search meta-tools) are
editor-only, though — cooked-build tools must be registered explicitly
through `AddTool()` and are advertised eagerly. MCP Resources and Prompts are
not advertised by any shipping toolset.
## Known limitations (5.8, experimental)
- HTTP + SSE transports only; loopback-only listener; non-loopback `Origin`
headers rejected; no auth layer. Not safe beyond the local machine.
- Serial game-thread execution: overlapping calls unsupported; editor UI
blocks during each call.
- Feature-incomplete by Epic's own labeling; APIs and data formats subject
to change without notice.
- Live Coding does not propagate new `UFUNCTION` declarations.