UEFN MCP Server
Control UEFN (Unreal Editor for Fortnite) from Claude Code via the Model Context Protocol.
Claude Code <--stdio--> MCP Server (mcp_server.py) <--HTTP 127.0.0.1:8765--> Listener (uefn_listener.py, inside UEFN)
|--TCP 127.0.0.1:1962--> UEFN VerseWorkflowServer (verse_compile / verse_status / verse_push)
|--stdio-------------> verse-lsp.exe (verse_symbols / verse_hover / ...)
'--files / Win32 ----> editor log, settings ini, processes (uefn_status / uefn_launch_project / ...)
- 110 tools: actors, assets, levels, viewport, materials, Niagara, animations, static meshes, data tables, asset validation, device
@editable wiring, Play-In-Editor control, StaticMeshActor scatter, Verse source introspection, Verse compile/push, Verse code navigation, UEFN session status / launch, and arbitrary Python execution
- Zero C++ compilation — pure Python, works across UEFN versions
- Main-thread safe — all
unreal.* calls dispatched via editor tick callback
- Autostart — the listener starts by itself when a project opens (see Auto-start)
- Crash recovery — the UEFN session tools read the editor state and relaunch UEFN after a crash; see UEFN session and crash recovery
- No input simulation — the server never sends mouse or keyboard input; tools that are hard to undo are tagged experimental
What's new in 0.5.0
- UEFN session (
uefn_session.py, 3 tools): uefn_status (read-only state from the editor log, processes and
ports), uefn_launch_project (start UEFN, open a project, wait for the listener; can stop a crash reporter left over
from a crash), uefn_set_load_on_startup. They send no mouse or keyboard input: when UEFN stops on the HUB, the user
opens the project.
- [experimental] tag: 7 tools that are hard to undo or act outside the open level ask the user to confirm before
each call. See Experimental tools.
setup.ps1: one idempotent setup step for a new machine (-DryRun).
- Safety fixes (listener protocol 0.3.3): rotations are named axes
{"pitch", "yaw", "roll"} (a documented
[pitch, yaw, roll] list used to be applied as [roll, pitch, yaw]); the staticmesh_* tools stop calling the
getters that crash UEFN 42.20. See Conventions.
Full list: CHANGELOG.md.
What's new in 0.4.0
A community fork was merged into this repo: 79 new tools (materials, Niagara, animation, static meshes, asset
management, data tables, validation, screenshots, device @editable fields, Play-In-Editor, scatter, Verse
introspection), a Verse module that compiles, pushes and navigates Verse without the editor listener, listener
autostart through ensure_mcp_hook.ps1, a system-tray icon, and a listener that survives project switches.
Full list: CHANGELOG.md.
Quick Start
0. One step: setup.ps1
git clone https://github.com/kirchuvakov/uefn-mcp-server
powershell -NoProfile -ExecutionPolicy Bypass -File .\uefn-mcp-server\setup.ps1 -DryRun # shows what it would do
powershell -NoProfile -ExecutionPolicy Bypass -File .\uefn-mcp-server\setup.ps1 -ScheduleHook -Project "<path>\<Island>.uefnproject"
The script is idempotent (run it again after moving the clone or after a Fortnite update): it finds a real Python
3.10+, installs requirements.txt, sets UEFN_MCP_PATH, installs the listener autostart hook (-ScheduleHook adds the
hourly task that re-adds it after Fortnite updates), reports the UEFN install, "Load on Startup" and whether Python is
enabled for your project, and prints what is left. Options: -WithTray (tray-icon packages into vendor/),
-WithMss, -EnableLoadLastProject (UEFN reopens the last project at startup instead of the HUB; recommended, see
below), -RegisterClaude (user-scope registration), -Python <python.exe>, -SkipPip, -DryRun.
Recommended: Load on Startup = Most Recent Project. UEFN crashes often. To let an agent recover by itself, set
Editor Preferences > Loading & Saving > Load on Startup to Most Recent Project (the HUB screen has the same
selector). Then uefn_launch_project relaunches UEFN, the project opens without the HUB, and the listener autostarts
through the hook. Without it, UEFN stops on the HUB after every relaunch and you have to click the
project. With UEFN closed you can also run setup.ps1 -EnableLoadLastProject or let the agent call
uefn_set_load_on_startup('LastProject'); details in UEFN session and crash recovery.
Steps 1-6 below are what the script automates, plus the two things only you can do: enable Python in each project
(step 3) and approve the server in Claude Code (step 5). Prefer a conversation? Ask Claude Code "Help me set up UEFN
MCP server".
1. Clone and point UEFN_MCP_PATH at the clone
git clone https://github.com/kirchuvakov/uefn-mcp-server
[Environment]::SetEnvironmentVariable('UEFN_MCP_PATH', (Resolve-Path .\uefn-mcp-server).Path, 'User')
Any folder works; the config snippets below find the server through UEFN_MCP_PATH, so no absolute path is
written into a project. Restart terminals and Claude Code after setting it.
2. Install the MCP SDK (host Python, not inside UEFN)
pip install -r requirements.txt
Use a real Python 3.10+ install. On Windows, a bare python can resolve to the Microsoft Store alias
(%LOCALAPPDATA%\Microsoft\WindowsApps\python.exe), which Claude Code cannot start: turn off the python.exe
App execution aliases in Windows settings, or put the full path of a real python.exe into command below.
3. Enable Python in UEFN
- Open your project in UEFN
- Go to Project > Project Settings
- Search for Python and check the box for Python Editor Script Plugin
UEFN keeps this per project and per user (EnablePythonLocallyPerProject); every project that should autostart the
listener needs it.
4. Start the listener inside UEFN
Autostart (recommended, one-time setup): see Auto-start. Manual start at any time:
Tools > Execute Python Script > uefn_listener.py from the clone.
A status window will appear showing:
- Listener status — green when running, red when stopped
- MCP Server status — green when Claude Code is connected (heartbeat every 10s)
- Port — editable when listener is stopped
- Metrics — uptime, request count, errors, last command, avg response time
- Controls — Stop / Start / Restart buttons
Closing this window does not stop the listener — it just hides it. A system-tray icon (optional, see
Tray icon) stays in the notification area while the editor session lives: left-click (or Show window)
re-opens the window; the menu also has Hide window, Restart listener, Start/Stop listener and
Quit (stop & remove tray).
.mcp.json in your project root (Claude Code expands ${VAR} and ${VAR:-default} in it):
{
"mcpServers": {
"uefn": {
"command": "python",
"args": ["${UEFN_MCP_PATH}/mcp_server.py"]
},
"unreal-mcp": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp"
}
}
}
unreal-mcp is Epic's own MCP server, available while UEFN runs with Toolsets enabled; it complements this one
(asset/actor search, reading device properties, sessions). A project-scope .mcp.json server must be approved once
in Claude Code (/mcp); if it stays "pending approval", register this server at user scope instead:
claude mcp add uefn -s user -- python "$env:UEFN_MCP_PATH\mcp_server.py"
6. Restart Claude Code
Claude Code picks up MCP servers on startup. Check with claude mcp list (expect uefn ... Connected) and ask
"ping UEFN".
Try it
Ask Claude Code:
- "List all actors in the level"
- "Spawn a cube at position 100, 200, 300"
- "Compile the Verse code and show me the errors"
- "Move the viewport camera to look at the origin"
Tray icon
The tray icon needs pystray + Pillow built for UEFN's embedded Python (3.11), so they go into ./vendor
(gitignored) instead of the host Python. Populate it once with UEFN's own python.exe (it ships with pip):
$uefnPython = Join-Path $env:ProgramFiles 'Epic Games\Fortnite\Engine\Binaries\ThirdParty\Python3\Win64\python.exe' # adjust if Fortnite lives elsewhere
& $uefnPython -m pip install --target "$env:UEFN_MCP_PATH\vendor" pystray Pillow
Tested with pystray 0.19.5 and Pillow 12.2.0 (cp311 wheel). If vendor/ is missing, everything still works — the
window just hides with no tray icon.
Auto-start
UEFN starts Python right after a project opens (when step 3 is done for that project), but then runs
init_unreal.py only from a fixed whitelist of engine plugin folders. Copying init_unreal.py into
Content/Python, Documents/UnrealEngine/Python or UE_PYTHONPATH does not start anything, and .py files inside a
UEFN project make session upload fail with [ContainsPythonData].
ensure_mcp_hook.ps1 appends a small marked hook to Epic's whitelisted
<Fortnite>\Engine\Plugins\Experimental\Toolsets\EditorToolset\Content\Python\init_unreal.py. The hook runs this
repo's init_unreal.py, which puts the repo on sys.path and imports uefn_listener. The Output Log then shows
[MCP] Listener started on http://127.0.0.1:8765 and [MCP] Auto-started on port 8765.
$hook = Join-Path $env:UEFN_MCP_PATH 'ensure_mcp_hook.ps1'
powershell -NoProfile -ExecutionPolicy Bypass -File $hook -DryRun # shows what it would do
powershell -NoProfile -ExecutionPolicy Bypass -File $hook # appends the hook (elevated shell if Program Files is write-protected)
Fortnite updates overwrite the Epic file and remove the hook (symptom: no [MCP] Auto-started after a project
opens). The script is idempotent, so let Windows re-run it every hour:
schtasks /Create /F /SC HOURLY /TN "UEFN-MCP-hook" /TR "powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$hook`""
The hook takes effect at the next project open; there is no remote way to start the listener in a project that is
already open (use Tools > Execute Python Script). Options: -FortniteDir (or UEFN_FORTNITE_DIR; default from the
Epic launcher manifest, else %ProgramFiles%\Epic Games\Fortnite), -Target (or UEFN_MCP_HOOK_TARGET; default: the
per-user shim Documents\UnrealEngine\Python\init_unreal.py if an older install created it, else this repo's
init_unreal.py), -DryRun. The script backs up the Epic file before writing and logs to ensure_mcp_hook.log.
Tools marked experimental ask the user to confirm before each call: execute_python,
shutdown, delete_asset, asset_batch_rename, verse_push, uefn_launch_project, uefn_set_load_on_startup.
The execute_python tool is the most powerful — it runs arbitrary Python code inside the editor with full access to the unreal module:
# DESC: list actor labels <- optional: shown as the task label in the status window
# Pre-populated variables: unreal, actor_sub, asset_sub, level_sub, tk, get_tk_root
# Assign to `result` to return a value
actors = actor_sub.get_all_level_actors()
result = [a.get_actor_label() for a in actors]
Tkinter note: When creating UI windows via execute_python, use get_tk_root() + tk.Toplevel(root). Never call tk.Tk() — multiple instances crash the editor.
Timeouts: a listener call returns after 30 s, but the code keeps running in the editor. Split long jobs into
smaller calls and check the editor state afterwards instead of re-running a timed-out call.
Seven tools do something that is hard to undo or reaches outside the open level: run arbitrary code, delete or rename
assets, push to a live session, stop the listener, start UEFN or edit its settings. They carry the [experimental]
tag, and an agent should ask the user to confirm before each call:
The tag shows in three places: the MCP title starts with [experimental], the annotations set destructiveHint=true,
and the description starts with "[experimental] Ask the user to confirm before each call." The server instructions
repeat the list, so the agent sees the tag before it calls the tool.
UEFN session and crash recovery
The uefn_* tools run in the MCP server process (Windows, no listener needed) and read the editor log, UEFN's settings
ini, the Epic launcher manifests, processes, windows and ports. They send no mouse or keyboard input.
uefn_status: read-only state (not_running, starting, hub, opening, project_open, other_project_open,
open_failed, crash_dialog), the open project, whether the editor windows respond, the crash reporter, Python /
listener / Toolsets markers, ports, "Load on Startup", the autostart hook, and next-step hints.
uefn_launch_project: starts UEFN if needed (Epic launcher URI or the editor exe), waits for the project, then for
the listener. If UEFN stops on the HUB (project browser) it returns status hub: the user opens the project, then the
agent calls again with launch=False. close_crash_reporter=true (default false) stops a crash reporter left over
from a crash, only when no editor process runs. It never closes or kills an editor.
uefn_set_load_on_startup: HomeScreen (the HUB) or LastProject (Most Recent Project). UEFN must be closed
(it rewrites the file on exit); a backup is kept; dry_run shows the change only.
Load on Startup = Most Recent Project is what makes unattended recovery work. Set it in Editor Preferences >
Loading & Saving > Load on Startup (or with the selector on the HUB screen). The ini key is
ValkyrieLoadAtStartupMostRecentProject=LastProject in section [/Script/ValkyrieEditor.ValkyrieEditorConfig] of
%LOCALAPPDATA%\UnrealEditorFortnite\Saved\Config\WindowsEditor\EditorPerProjectUserSettings.ini; with UEFN closed,
uefn_set_load_on_startup('LastProject') or setup.ps1 -EnableLoadLastProject writes it. With this setting a crash
costs one call: uefn_launch_project relaunches UEFN (pointing the last project at the requested one first), the
project opens without the HUB, and the listener autostarts through the hook.
Architecture
The system uses two independently running Python processes, plus two direct channels for Verse:
Why two processes?
- All
unreal.* calls must happen on the editor's main thread (tick callback)
- The MCP SDK needs pip-installable packages that can't be added to UEFN's embedded Python
- Each component can restart independently
See docs/architecture.md for details.
Configuration
Custom port example:
{
"mcpServers": {
"uefn": {
"command": "python",
"args": ["${UEFN_MCP_PATH}/mcp_server.py"],
"env": { "UEFN_MCP_PORT": "8766" }
}
}
}
Known limitations
UEFN exposes a large subset of the Unreal Python API, but a few editor actions have no scripting surface:
- Push Changes / Verse build — no Python API (
FortniteEditorLibrary, FortEditorUtilityLibrary, Fort*/Creative* libraries and console commands such as UEFN.PushChanges do not expose them). verse_compile and verse_push use the Verse workflow socket instead, the channel of the VS Code extension. verse_push works only while a session runs; new textures and other new assets still need a session relaunch. Launch Session and the other toolbar actions have no scripting surface either: the user clicks them.
- Verse
@editable fields — ScriptDevice bindings block get_editor_property on Verse-declared fields, and plain-name writes fail ("Failed to find property"). device_list_editables / device_set_editable cover native device properties; for Verse fields use execute_python on the device's inner object (mangled name __verse_0x<HASH>_<Field>), or read them with the official unreal-mcp DeviceToolset. verse_check_editable_coverage audits the sources instead of the live level.
get_editor_log picks the newest .log in the project log folder, which can be the revision-control log (upstream PR #3).
- Static-mesh metadata in UEFN 42.20 — the
StaticMeshEditorSubsystem getters (has_vertex_colors, get_lod_count, get_number_verts, ...), StaticMesh.get_num_triangles / get_num_sections and BodySetup.agg_geom.export_text() crashed the editor. staticmesh_get_info reads asset-registry tags, slots, bounds and Nanite settings instead and reports the rest as "not available safely in UEFN 42.20" (details).
Tests
Scripts that run inside the UEFN editor to introspect the Python API.
Run via Tools > Execute Python Script in the UEFN menu bar.
Documentation
Requirements
- UEFN editor with Python enabled for the project (Project Settings)
- Python 3.10+ on the host system,
pip install -r requirements.txt
- Claude Code CLI
- Optional: the
epicgames.verse VS Code extension (Verse navigation tools), mss (screenshot_desktop), pystray + Pillow in vendor/ (tray icon)
- UEFN session tools: Windows; nothing to install
License
MIT