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:
parent
56e2ba5e79
commit
a8b81c56a0
|
|
@ -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
|
||||
|
|
@ -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. 3–10 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.
|
||||
|
|
@ -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 5–9 `/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 0–360°, sunk 10–30 cm into the ground so nothing floats.
|
||||
A path: 6–10 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.05–0.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,500–8,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 2–4 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 30–60° 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 2–6.
|
||||
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 400–600 cm for a
|
||||
prop/character subject; height 120–160 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..N−1: 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.
|
||||
|
|
@ -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 0–1 (`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) | 160–175 |
|
||||
| Door | 200–210 tall × 80–90 wide |
|
||||
| Ceiling, residential | 240–300 |
|
||||
| One building storey | 300–400 |
|
||||
| Counter/desk height | 75–110 |
|
||||
| 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,000–120,000 | −60° to −90° | 5,500–6,000 K |
|
||||
| Afternoon | 40,000–75,000 | −30° to −50° | 5,000–5,500 K |
|
||||
| Golden hour | 5,000–20,000 | −5° to −15° | 2,800–3,500 K |
|
||||
| Overcast | 5,000–20,000 (soft) | −45° ± | 6,500–7,500 K |
|
||||
| Blue hour / dusk | 10–100 | −2° to +5° | 8,000–12,000 K |
|
||||
| Full-moon night | 0.05–0.3 | −30° to −60° | 4,000–4,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 | 10–15 (≈1,850 K) |
|
||||
| 40 W incandescent equiv. | 450 (2,700 K) |
|
||||
| 60 W equiv. | 800 (2,700–3,000 K) |
|
||||
| 100 W equiv. | 1,600 (3,000 K) |
|
||||
| Bright ceiling fixture | 2,000–4,000 (3,000–4,000 K) |
|
||||
| Fluorescent tube / office | 2,500–5,000 (4,000–5,000 K) |
|
||||
| Streetlight (sodium) | 5,000–15,000 (≈2,000 K, orange) |
|
||||
| Car headlight | 1,000–1,500 each (4,300–6,000 K) |
|
||||
| Campfire | 100–300, flicker (1,700–2,000 K) |
|
||||
|
||||
Spot cone: inner 20–35°, outer 40–60° 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,700–1,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,500–10,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 | 14–16 |
|
||||
| Overcast exterior | 11–13 |
|
||||
| Golden hour | 10–12 |
|
||||
| Bright interior (day, windows) | 7–9 |
|
||||
| Dim practical-lit interior | 4–6 |
|
||||
| Street at night | 2–4 |
|
||||
| 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.005–0.015 subtle depth cue · 0.02–0.05 moody/morning · 0.05–0.2 heavy.
|
||||
Enable **Volumetric Fog** on it for light shafts through it; then dial
|
||||
per-light "Volumetric Scattering Intensity" (1–10) 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; 1–2 practicals, hard shadows | Minimal | 0.03–0.06 volumetric | 4–5 | Single motivated key, deep blacks |
|
||||
| Sci-fi corridor | Rect lights 2,000 lm 6,500–8,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 | 18–28 mm | f/5.6–8 |
|
||||
| Neutral "human eye" | 35–50 mm | f/4 |
|
||||
| Portrait / subject isolation | 85–135 mm | f/1.4–2.8 |
|
||||
| Compression (stacked background) | 100–200 mm | f/2.8–5.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 ≈ 155–170 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).
|
||||
|
|
@ -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.
|
||||
Loading…
Reference in New Issue