loading…
Search for a command to run...
loading…
A two-process MCP bridge for Unreal Editor for Fortnite that lets Claude Code, Cursor, and other MCP clients drive a live UEFN editor safely.
A two-process MCP bridge for Unreal Editor for Fortnite that lets Claude Code, Cursor, and other MCP clients drive a live UEFN editor safely.
Python 3.10+ UEFN MCP
A two-process MCP bridge for Unreal Editor for Fortnite that lets Claude Code, Cursor, and other MCP clients drive a live UEFN editor safely.
MCP client (Claude Code / Cursor / custom host)
│
│ stdio
▼
server/main.py ── FastMCP host process
│
│ HTTP 127.0.0.1:8765-8770
▼
Content/Python/uefn_tools/tools/mcp_bridge.py
│
│ queue + Slate tick callback
▼
UEFN editor main thread -> unreal.* -> 354 registered uefn_tools actions
Quick setup
git clone https://github.com/quangdang46/uefn-verse-mcp.git
cd uefn-verse-mcp
python -m pip install -r requirements.txt
python deploy.py --project "MyIsland"
There is no packaged one-line installer yet. The supported path today is source checkout plus local deploy into a UEFN project.
UEFN's Python layer is powerful, but it is a bad place to host a modern MCP server directly:
unreal.* calls must run on the editor main thread.uefn-mcp splits the system in two:
server/main.py runs externally as a FastMCP stdio server.Content/Python/uefn_tools/tools/mcp_bridge.py runs inside UEFN as a local HTTP listener.uefn-mcp?| Capability | What it gives you |
|---|---|
| 31 direct MCP tools | A curated surface for actors, assets, project info, viewport, logs, and status |
354 run_tool entries |
An escape hatch into the full uefn_tools registry without re-wrapping every action as first-class MCP |
| Main-thread-safe dispatch | unreal.* work is queued and executed on editor ticks instead of from the HTTP thread |
| Auto port discovery | The host scans ports 8765-8770 and reconnects automatically if the listener moved |
| Generic UEFN loader | init_unreal.py auto-loads any package under Content/Python/ that exposes register() |
| Deployment helper | deploy.py copies the package and keeps .urcignore aligned for editor-only Python files |
The live inventory is tracked in FEATURES.md, and deeper engine constraints are documented in docs/UEFN_QUIRKS.md.
This repo sits on top of two upstream lines:
uefn_tools registry and tooling: UEFN-TOOLBELT (Ocean Bennett).This fork keeps both ideas, but reshapes them into an MCP-first repo:
server/Content/Python/uefn_tools/tools/mcp_bridge.pyLicensing is documented in LICENSE. In short:
Combined distribution: This repository is offered under GNU AGPL v3.0 with UEFN Toolbelt’s additional visible-attribution term (copyleft; network use triggers source-sharing obligations — see the license file and the AGPL-3.0 text).
Upstream MIT components: Portions derived from KirChuvakov’s work were originally under the MIT License; their copyright and permission notice must stay intact in copies of this project (also reproduced in LICENSE).
Forks and derivatives: If you ship something built on this codebase or its architecture, include the visible Toolbelt credit required in LICENSE (for example in README, docs, store page, or an in-editor credits panel):
Built on UEFN Toolbelt by Ocean Bennett
(https://github.com/undergroundrap/UEFN-TOOLBELT)
…or an equivalent that names Ocean Bennett and links to that repository.
This is not legal advice; read LICENSE for full terms.
The fastest end-to-end workflow is:
git clone https://github.com/quangdang46/uefn-verse-mcp.git
cd uefn-verse-mcp
python -m pip install -r requirements.txt
python deploy.py --project "MyIsland"
# In the UEFN Python console
import uefn_tools as ut; ut.register(); ut.run("mcp_start")
// In your MCP client config (.mcp.json)
{
"mcpServers": {
"uefn": {
"command": "python",
"args": ["C:\\path\\to\\uefn-verse-mcp\\server\\main.py"]
}
}
}
Once the client reconnects, the typical first calls look like this:
ping()
get_project_info()
get_all_actors()
spawn_actor(class_path="/Game/Blueprints/BP_Cube.BP_Cube_C", location=[0, 0, 200])
set_viewport_camera(location=[-800, 0, 400], rotation=[-15, 0, 0])
list_tools(category="Materials")
describe_tool("material_gradient_painter")
run_tool("arena_generate", kwargs={"size": "large", "teams": True})
run_tool("smoke_test")
Examples above use conceptual MCP call syntax. The tool names and parameters match the real FastMCP surface exported by server/main.py.
The host process uses ordinary Python and mcp>=1.2.0. UEFN only needs the local listener and the tool registry.
unreal.* from the wrong threadThe HTTP server runs on a daemon thread, but every editor action is queued and drained on the Slate post-tick callback. That is the safety boundary that keeps automation from fighting the editor event loop.
The direct MCP surface covers the most common actor, asset, level, and viewport workflows. run_tool, list_tools, and describe_tool expose the deeper registry when you need specialized behavior.
The intended setup is:
uefn_tools/ and init_unreal.py into the target project.server/main.py.No editor-side package installation and no IDE-specific glue should be required beyond that.
| Approach | MCP-native | Full registry access | Main-thread safety model | Best for |
|---|---|---|---|---|
| Ad hoc UEFN Python scripts | No | No | You own every threading mistake | One-off local editor tasks |
| Minimal custom HTTP bridge | Partial | Usually narrow | Depends on your implementation | Small, fixed editor automations |
uefn-mcp |
Yes | Yes, via run_tool |
Queue + Slate tick dispatch | AI-agent-driven UEFN editing and experimentation |
server/main.py, deploy.py, and the MCP dependencyContent/Python/ files intodeploy.py (recommended)deploy.py is the clean MCP-first path.
python -m pip install -r requirements.txt
python deploy.py --project "MyIsland"
You can also deploy by full path:
python deploy.py --path "D:\\Projects\\MyIsland"
If you omit both flags, deploy.py will prompt you interactively.
For fast local iteration you can link uefn_tools/ instead of copying it.
Edits in the repo checkout are then immediately visible to UEFN without redeploying.
python deploy.py --project "MyIsland" --link-kind junction
python deploy.py --path "D:\\Projects\\MyIsland" --link-kind symlink
--link-kind |
Behavior | Privileges |
|---|---|---|
junction |
Windows directory junction (recommended on Windows) | No special privileges required |
symlink |
OS-native symbolic link | May need Developer Mode or admin on Windows |
init_unreal.py is always copied as a real file regardless of link mode.
Note: Link mode is a local development convenience. Do not use it for shared or team projects — use the default copy mode instead.
What it installs:
Content/Python/uefn_tools/Content/Python/init_unreal.py.urcignore entry for Content/Python/*deploy.bat (Windows interactive helper)If you prefer a Windows batch workflow:
deploy.bat
This is still present as a helper, but the repo's documented MCP-first path is deploy.py.
If you need full control:
Content/Python/uefn_tools/ into your project at Content/Python/uefn_tools/.init_unreal.py into your project at Content/Python/init_unreal.py.python -m pip install -r requirements.txt
.urcignore includes:Content/Python/*
install.pyinstall.py still contains older Toolbelt/UI-era behavior. Use deploy.py for the current MCP-first workflow.
git clone https://github.com/quangdang46/uefn-verse-mcp.git
cd uefn-verse-mcp
python -m pip install -r requirements.txt
python deploy.py --project "MyIsland"
import uefn_tools as ut
ut.register()
ut.run("mcp_start")
{
"mcpServers": {
"uefn": {
"command": "python",
"args": ["C:\\path\\to\\uefn-verse-mcp\\server\\main.py"]
}
}
}
ping()
get_status()
run_tool("smoke_test")
get_all_actors()
search_assets(class_name="StaticMesh", directory="/Game", recursive=True)
spawn_actor(class_path="/Game/Blueprints/BP_Cube.BP_Cube_C", location=[0, 0, 100])
| Tool | What it does | Example |
|---|---|---|
ping |
Confirms the host can reach the in-editor bridge | ping() |
get_status |
Returns listener status and bridge metadata | get_status() |
execute_python |
Executes arbitrary Python inside the UEFN editor runtime | execute_python(code="result = {'status': 'ok'}") |
get_log |
Returns recent bridge log lines | get_log(lines=100) |
shutdown |
Stops the in-editor listener | shutdown() |
| Tool | What it does | Example |
|---|---|---|
get_all_actors |
Lists actors in the current level | get_all_actors() |
get_selected_actors |
Reads the current editor selection | get_selected_actors() |
spawn_actor |
Spawns an actor from a class or object path | spawn_actor(class_path="/Game/Blueprints/BP_Cube.BP_Cube_C", location=[0, 0, 100]) |
delete_actors |
Deletes actors by label | delete_actors(actor_labels=["Cube_1", "Cube_2"]) |
set_actor_transform |
Updates actor location, rotation, and/or scale | set_actor_transform(actor_label="Cube_1", location=[200, 0, 100]) |
get_actor_properties |
Reads properties for a named actor | get_actor_properties(actor_label="Cube_1") |
set_actor_properties |
Writes multiple actor properties | set_actor_properties(actor_label="Cube_1", properties={"mobility": "Static"}) |
select_actors |
Selects actors in the editor | select_actors(actor_labels=["Cube_1"]) |
focus_selected |
Moves the viewport to the active selection | focus_selected() |
| Tool | What it does | Example |
|---|---|---|
list_assets |
Lists assets in a directory | list_assets(directory="/Game", recursive=True) |
get_asset_info |
Reads details for a specific asset path | get_asset_info(asset_path="/Game/MyFolder/MyAsset") |
get_selected_assets |
Returns selected Content Browser assets | get_selected_assets() |
rename_asset |
Renames or moves an asset | rename_asset(old_path="/Game/Old", new_path="/Game/New") |
delete_asset |
Deletes an asset | delete_asset(asset_path="/Game/Trash/TempAsset") |
duplicate_asset |
Copies an asset to a new path | duplicate_asset(source_path="/Game/A", dest_path="/Game/B") |
does_asset_exist |
Checks if an asset exists | does_asset_exist(asset_path="/Game/Props/SM_Crate") |
save_asset |
Saves a dirty asset | save_asset(asset_path="/Game/Props/SM_Crate") |
search_assets |
Searches the Asset Registry with directory and class filters | search_assets(class_name="Material", directory="/Game", recursive=True) |
| Tool | What it does | Example |
|---|---|---|
get_project_info |
Returns project metadata | get_project_info() |
save_current_level |
Saves the current level | save_current_level() |
get_level_info |
Returns summary info about the active level | get_level_info() |
| Tool | What it does | Example |
|---|---|---|
get_viewport_camera |
Reads the editor camera position and rotation | get_viewport_camera() |
set_viewport_camera |
Moves the editor camera | set_viewport_camera(location=[-800, 0, 400], rotation=[-15, 0, 0]) |
| Tool | What it does | Example |
|---|---|---|
run_tool |
Executes any registered uefn_tools action by name |
run_tool("arena_generate", kwargs={"size": "large"}) |
list_tools |
Lists all registered tools, optionally by category | list_tools(category="Materials") |
describe_tool |
Returns description and parameters for one tool | describe_tool("material_gradient_painter") |
The direct MCP surface is intentionally small. The deeper workflows are meant to be discovered dynamically:
list_tools()
list_tools(category="Verse Helpers")
describe_tool("device_call_method")
run_tool("device_call_method", kwargs={"class_filter": "TimerObjective", "method": "blueprint_pause"})
The full generated inventory lives in FEATURES.md.
.mcp.jsonUse the same Python interpreter here that you used for python -m pip install -r requirements.txt.
{
"mcpServers": {
"uefn": {
"command": "python",
"args": ["C:\\dev\\uefn-verse-mcp\\server\\main.py"]
}
}
}
| Variable | Default | Purpose |
|---|---|---|
UEFN_MCP_REQUEST_TIMEOUT |
30 |
Default timeout in seconds for bridge requests |
UEFN_MCP_LONG_TOOL_TIMEOUT |
900 |
Timeout for long-running run_tool calls that opt into the extended budget |
8765-8770.http://127.0.0.1:<port>/.init_unreal.py is intentionally generic:
Content/Python/ to sys.path__init__.pyregister() when availableThat means uefn_tools is only one possible package. The loader is reusable for other editor-side Python packages in the same project.
┌──────────────────────────────────────────────────────────────────────┐
│ MCP client │
│ Claude Code / Cursor / custom FastMCP host │
└──────────────────────────────────────────────────────────────────────┘
│
│ stdio
▼
┌──────────────────────────────────────────────────────────────────────┐
│ server/main.py │
│ - FastMCP server │
│ - 31 direct tools │
│ - run_tool / list_tools / describe_tool │
└──────────────────────────────────────────────────────────────────────┘
│
│ HTTP JSON POST
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Content/Python/uefn_tools/tools/mcp_bridge.py │
│ - Local HTTP listener inside the UEFN process │
│ - Queue-based handoff from HTTP thread to editor main thread │
│ - Bridge commands: system, actors, assets, viewport, escape hatch │
└──────────────────────────────────────────────────────────────────────┘
│
│ Slate post-tick callback
▼
┌──────────────────────────────────────────────────────────────────────┐
│ UEFN editor main thread │
│ - Executes unreal.* calls safely │
│ - Reads and writes level / assets / viewport │
│ - Invokes 354 registered uefn_tools actions │
└──────────────────────────────────────────────────────────────────────┘
Important files:
| File | Responsibility |
|---|---|
server/main.py |
External FastMCP entrypoint |
server/bridge.py |
Host-side HTTP client for the in-editor listener |
server/port_discovery.py |
Port probing across 8765-8770 |
Content/Python/uefn_tools/tools/mcp_bridge.py |
In-editor HTTP server and queue dispatcher |
Content/Python/uefn_tools/__init__.py |
Package root, registry setup, plugin loading |
deploy.py |
Project deployment and .urcignore maintenance |
init_unreal.py |
Generic UEFN Python package loader |
For a shorter architecture note, see ARCHITECTURE.md.
| Symptom | Likely cause | Fix |
|---|---|---|
No module named 'mcp' |
Your MCP client is running a different Python than the one you installed into | Install requirements.txt into the exact interpreter used by the MCP command |
UEFN listener not found on ports 8765-8770 |
The in-editor bridge is not running | In the UEFN console, run import uefn_tools as ut; ut.register(); ut.run("mcp_start") |
| Commands fail right after deploy | The package was copied into the project, but the editor has not loaded it yet | Re-run ut.register() manually, or restart UEFN if you changed init_unreal.py |
| The editor freezes during custom automation | Blocking code is running on the main thread | Avoid time.sleep() and long synchronous loops in editor-side code; keep work queued and tick-driven |
Asset or project paths resolve strangely in init_unreal.py |
UEFN path helpers do not always point where you expect during startup | Use the file-system path of init_unreal.py itself rather than unreal.Paths.project_content_dir() |
run_tool works locally but returns unusable data to the client |
A tool is returning None, a primitive, or a live Unreal object instead of a JSON-friendly result |
Make the tool return a structured dict, preferably with a "status" field |
run_tool.winget, or standalone installer release flow documented here today.No for the MCP server itself. The mcp dependency belongs in the external host Python that runs server/main.py.
Because the editor runtime is the wrong place for a long-lived stdio server and blocking automation. The two-process split is the safety boundary.
ping(), then get_status(), then either get_project_info() or run_tool("smoke_test").
Use list_tools() to discover categories, describe_tool() to inspect parameters, and run_tool() to execute the chosen action.
Yes. Any MCP client that can launch a stdio server can point at server/main.py.
No. Most package changes can be reloaded with ut.register(). Changes to startup loader behavior in init_unreal.py are the ones most likely to require a restart.
Start with docs/UEFN_QUIRKS.md. That file captures the non-obvious engine behavior this repo is already compensating for.
Выполни в терминале:
claude mcp add uefn-mcp -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.