CU

chir24/unreal_mcp

Developer tools
998 stars 184 forks Quality 92 Trend 48MIT

A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine through the native C++ Automation Bridge plugin. Built with TypeScript and C++.

Overview

A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine through a native C++ Automation Bridge plugin. Built with TypeScript and C++. - — All operations route through the MCP Automation Bridge plugin - — Native HTTP/SSE (no bridge needed) or WebSocket via TypeScript bridge - — Runtime introspection for lights, debug shapes, and sequencer tracks - — Server starts even without an active Unreal connection - — Retries automation handshakes with exponential backoff - — Blocks dangerous console commands with pattern-based validation - — Optional token-based authentication for both WS and HTTP transports - — 10-second TTL for improved performance - — Per-IP rate limiting (60 req/min) on Prometheus endpoint - — Unified class aliases and type definitions - 5.0–5.8 source-compatibility target. The current complete live acceptance record covers UE 5.7.4 only.

README

EngineWire

EngineWire connects Claude, Cursor, VS Code or any other MCP client to a running Unreal Editor, and let it build levels, Blueprints, UI, materials, effects and more, through a native C++ editor plugin.

🧭 One tool, over 400 capabilities  ·  ⚙️ Native C++ editor plugin  ·  🌐 HTTP or stdio  ·  🔐 Local and token-protected by default  ·  🎮 Unreal Engine 5.0 – 5.8

🌐 enginewire.dev  ·  🛒 Fab  ·  🚀 Quick Start  ·  📖 Wiki  ·  📦 Releases  ·  💬 Discussions

📌 Which version is this? This README describes 0.6.0, the stable release: the plugin on Fab and on the Releases page, and npm unreal-engine-mcp-server (latest). The previous release, 0.5.30, exposes 23 separate tools instead of one; staying on a 0.5.30 plugin means pinning [email protected]. See Upgrading from 0.5.x.

Contents · What it does · How it works · Quick start · The unreal tool · Configuration · Security · Engine plugins · Docker · Documentation · Development · Community


What it does

Over 450 capabilities in all, behind a single MCP tool. The assistant finds them by searching in plain words, so nobody has to learn their names. The full list is the generated Action Reference.

A platformer level built with the MCP, in Unreal Editor 5.8. Bottom right: the plugin’s status, MCP :3000 (1), meaning the native server is running on port 3000 with one client connected.

How it works

The EngineWire plugin runs inside the editor and does all the work, on the editor’s game thread, and serves MCP itself. Clients reach it in one of two ways, and both get the same single tool, unreal:

🌐 Route A · Native HTTP 🧩 Route B · stdio
Path Client → the plugin’s Streamable HTTP server at http://127.0.0.1:3000/mcp Client → unreal-engine-mcp-server (Node.js, stdio) → the same /mcp
Node.js Not needed 20.19 or later
Capability token The client sends it in the X-MCP-Capability-Token header Read from the project, given UE_PROJECT_PATH
Editor restarts The client reconnects by hand The session stays: calls answer NOT_CONNECTED until the editor is back
Best for Claude Code, Cursor, VS Code, and several clients sharing one editor Claude Desktop, clients that only launch local commands, and long sessions across editor restarts

Everything listens on 127.0.0.1 and requires the project’s capability token unless you change it.

Quick start

The Quick Start page walks through this with screenshots. You need Unreal Engine 5.0 to 5.8. Installing from Fab needs nothing else; building the plugin from source needs a project with C++ code (a Blueprint-only project can install from Fab or use prebuilt binaries).

1. Add the plugin

From Fab (no compiler needed). Get EngineWire on Fab. In the Epic Games Launcher, open Unreal Engine › Library › Fab Library, find EngineWire and click Install to Engine for your engine version. Open your project, enable EngineWire in Edit › Plugins and restart the editor. The Fab build ships with the native server off: turn on Enable Native MCP Server in Edit › Project Settings › Plugins › EngineWire › Native MCP and restart once more. Fab updates follow Epic’s review, so the Fab build can trail the newest release here.

From a release. Download EngineWire-plugin-.zip from the newest release on the Releases page, and copy the EngineWire folder it contains into your project:

MyGame/Plugins/EngineWire/

Or use a clone of this repository: copy plugins/EngineWire/, or reference the folder from your .uproject with "AdditionalPluginDirectories": ["C:/Path/To/Unreal_mcp/plugins"].

Open the project and let Unreal rebuild the plugin. When it’s loaded, the status bar shows MCP :3000 (0): the plugin serves MCP on port 3000. If you see “Engine modules cannot be compiled at runtime”, build the project once in Visual Studio, Rider or Xcode. More in Installation.

https://github.com/user-attachments/assets/d8b86ebc-4364-48c9-9781-de854bf3ef7d

2. Connect your client

Route A · Native HTTP (no Node.js)

  1. Enable Native MCP Server is on by default (Edit › Project Settings › Plugins › EngineWire, port 3000); the Fab build ships with it off. A project where it is off needs it on and an editor restart.

  2. Read the capability token the plugin generated: /Saved/MCP/capability-token. Treat it like a password.

  3. Add the server to your client and send the token in the X-MCP-Capability-Token header. Claude Code:

    claude mcp add --transport http unreal-engine http://127.0.0.1:3000/mcp --header "X-MCP-Capability-Token: "
    

    Or in a project .mcp.json (Claude Code), reading the token from an environment variable:

    {
      "mcpServers": {
        "unreal-engine": {
          "type": "http",
          "url": "http://127.0.0.1:3000/mcp",
          "headers": { "X-MCP-Capability-Token": "${UNREAL_MCP_TOKEN}" }
        }
      }
    }
    

    Cursor (.cursor/mcp.json) takes the same url and headers without type. VS Code, Windsurf and others: Connecting Clients.

  4. Check it: when the client connects, the count in the status bar goes up.

Route B · stdio (Node.js 20.19+)

The package is a small stdio proxy in front of the same /mcp. A client connected straight to /mcp loses its session whenever the editor closes or crashes, and has to reconnect by hand. Through the proxy, every call answers NOT_CONNECTED while the editor is down, and the first call after it is back reaches it, with no reconnect. It keeps the editor’s last tool list, so a session that starts before the editor still gets the real tool.

Add this to your client’s MCP configuration, for example Claude Desktop’s claude_desktop_config.json:

{
  "mcpServers": {
    "unreal-engine": {
      "command": "npx",
      "args": ["-y", "unreal-engine-mcp-server"],
      "env": {
        "UE_PROJECT_PATH": "C:/Path/To/YourProject"
      }
    }
  }
}

Or let the package write it: npx unreal-engine-mcp-server init claude --project C:/Path/To/YourProject shows the entry for Claude Code’s .mcp.json (also cursor, vscode, gemini and codex), keeping the file’s other servers, and --write saves it. npx unreal-engine-mcp-server doctor checks Node, the token (never shown), the editor’s answer and that both sides run the same version.

UE_PROJECT_PATH (the project folder or its .uproject) is how the proxy finds the capability token (MCP_AUTOMATION_CAPABILITY_TOKEN overrides it). UNREAL_MCP_URL points it at another endpoint (default http://127.0.0.1:3000/mcp). Pair it with a 0.6 plugin; a 0.5.x plugin serves its 23 separate tools instead of the unreal gateway.

3. Try it

With the editor open, ask your assistant to “list the actors in the current level”, “spawn a point light 300 units above the origin”, or “take a screenshot of the viewport”. If it doesn’t connect, see Troubleshooting.

The unreal tool

Both routes expose exactly one MCP tool, unreal, with four operations. Only the contract the model is about to use gets loaded, instead of hundreds of tool schemas:

Operation What it does
search Finds capabilities from 2-4 plain words, such as spawn actor or save level. Every row carries a ready-to-send nextCall.
describe Returns one capability’s exact contract: parameters, schemas, an example, and the consent grant when one is needed
execute Runs one capability with validated parameters and returns the data plus a receipt of what changed
configure Enables or disables groups of internal tools; never touches the editor

A typical exchange:

{ "operation": "search", "query": "spawn actor" }
{ "operation": "describe", "tool": "control_actor", "action": "spawn" }
{
  "operation": "execute",
  "tool": "control_actor",
  "action": "spawn",
  "params": { "classPath": "/Script/Engine.PointLight", "actorName": "KeyLight", "location": [0, 0, 300] }
}
  • Parameters are strict. An undeclared name is refused with UNDECLARED_PARAMETER and the list of allowed names; every error carries an executable nextCall.
  • Deletes need consent. Every destructive capability, plus some risky writes, only runs with the consentGrant from describe, passed back as a top-level consent field.
  • Names. A call names the tool + action pair or a capability id, such as "capability": "control_actor.spawn".

Full reference with real replies: Using the Gateway.

Migrating from direct tool calls

The single unreal tool is permanent on both routes; there is no opt-out and no 23-tool listing to restore. A client that still calls a canonical tool name directly (tools/call with name: "manage_asset", name: "control_actor", …) receives a bounded, copy-paste-executable DIRECT_TOOL_CALL_REMOVED receipt instead of a routed call. Its nextCall drills exactly one level: { "operation": "search" } for an unknown name, { "operation": "describe", "tool": "" } when no action was given, or { "operation": "execute", "tool": "", "action": "", "params": { ... } } when the call already named an action. Run that nextCall through unreal to finish the migration. See Upgrading.

Protocol versions

Both routes negotiate the MCP protocol version at initialize: 2025-11-25 (latest), 2025-06-18 or 2025-03-26. A client asking for an older version is answered with 2025-11-25. An unsupported MCP-Protocol-Version header on the native route gets HTTP 400. Both also serve MCP 2026-07-28 per request, with no initialize or session, and answer server/discover with it. Details: docs/protocol.md.

Configuration

Most setups touch one setting: UE_PROJECT_PATH for Route B; Route A needs none. Everything is listed on the Configuration page.

Plugin settings (Project Settings › Plugins › EngineWire, saved in Config/DefaultGame.ini):

Setting Default
Enable Native MCP Server on Serve MCP over HTTP at /mcp (both routes use it)
Native MCP Port 3000 The MCP_NATIVE_PORT environment variable of the editor process overrides it
Listen Ports 8090,8091 The plugin’s WebSocket listener; neither route uses it
Require Capability Token on Both routes refuse clients without the token
Allow Non Loopback off LAN access for both listeners. See Security.

Environment variables (Route B only, in the client’s env block):

Variable Default
UE_PROJECT_PATH unset Project folder or .uproject; used to find the token
MCP_AUTOMATION_CAPABILITY_TOKEN read from the token file Token to present, when the proxy can’t read the project folder
UNREAL_MCP_URL http://127.0.0.1:3000/mcp The editor’s /mcp endpoint
LOG_LEVEL info debug, info, warn or error; logs go to stderr

Security

  • Local by default. Both routes listen on 127.0.0.1 only. The native server never binds off-loopback: it has no TLS, so reach it from another machine through an SSH tunnel (ssh -L 3000:127.0.0.1:3000) or a TLS proxy. The WebSocket bridge binds a LAN address only with Allow Non Loopback, Require Capability Token and Enable Tls all on. The proxy sends the token to another machine only with UNREAL_MCP_ALLOW_REMOTE_TOKEN=1.
  • Capability token. Generated per project at /Saved/MCP/capability-token (a value typed into Capability Token overrides it) and compared in constant time. Delete the file and restart the editor to rotate it.
  • Consent for destructive work. Deletes and some other writes need a per-call consent grant, which the plugin checks itself.
  • Guard rails. Asset paths are limited to /Game, /Engine, /Script, /Temp and /Niagara, and a content path may also sit under a mount the connected editor reports; arguments the server treats as files (such as filePath, and outputPath even where it names an asset) never use the reported mounts; console commands that chain or quit the editor are blocked; the plugin’s own settings are out of reach of automation.

Details: Security. Please report vulnerabilities privately through GitHub security advisories.

Engine plugins

The bridge declares its engine-plugin dependencies, so Unreal enables them together with it.

The plugin targets every Unreal Engine minor from 5.0 to 5.8. If it fails to build on yours, please open an issue with the build log.

Docker

The image runs the stdio proxy. It has to reach the editor’s /mcp on 127.0.0.1:3000, so use host networking (Linux), and pass the token because the container can’t read your project folder:

docker build -t unreal-mcp .
docker run -i --rm --network host -e MCP_AUTOMATION_CAPABILITY_TOKEN= unreal-mcp

Use -i without -t: a TTY corrupts the MCP stream on stdout.

Documentation

📖 Wiki Quick Start, Installation, Connecting Clients, Using the Gateway, Configuration, Security, Troubleshooting, FAQ, Upgrading
📋 Action Reference Every capability with its effect, scope and consent, generated from the capability records
🔌 Gateway client guide The gateway contract, with the source file behind each claim
📡 Protocol Transports, version negotiation, cancellation
🔐 Security and receipts Scopes, consent, path gating, idempotency, refusal codes
🧪 Testing guide Test suites and how to add cases
🧩 Extending the plugin Adding an editor action: record, handler, route and tests, plus the rules for plugin code
🗺️ Roadmap Development roadmap
📝 Changelog What changed in each release

Development

npm install
npm run build              # clean + compile TypeScript to dist/
npm run dev                # run from source with ts-node
npm run lint               # ESLint (CI fails on any warning)
npm run type-check         # tsc --noEmit, sources and tests
npm run test:unit          # Vitest unit tests (no Unreal required)
npm run test:params        # strict parameter audit
npm run registry:generate  # capability records -> generated contracts, native shards, action reference
npm run registry:check     # fail if generated artifacts drift
npm run manifest:check     # fail if the gateway manifest drifts
npm run eval:check         # search-ranking corpus
npm run version:check      # all version sources agree
npm test                   # integration suite (needs a live Unreal Editor serving /mcp)

The capability records in src/tools/catalog/capabilities/records/ are the single source of truth; every *.generated.* file, the plugin’s MCP/Generated/ shards and the action reference are generated from them. Never hand-edit generated files. How to add a capability: Development.

CI runs, in order: ESLint (npx eslint . --max-warnings=0), type-check, unit tests, registry:check, manifest:check, headers:check, the strict parameter audit (test:params) and eval:check, then a blocking runtime dependency audit (npm audit --omit=dev --audit-level=moderate) and an informational full-tree audit. A Node 20.19 / 26 matrix adds build. Plugin packaging runs only when an Unreal Engine root is configured, because CI runners don’t ship an engine. Release archives exclude Binaries/, Intermediate/ and Saved/.

Community

💬 Discussions Questions, ideas, show and tell
🐞 Issues Bug reports and feature requests
🗺️ Project board Roadmap progress and priorities

Contributing: keep pull requests small and focused, with a Conventional Commits title; include reproduction steps for bugs; follow the existing code style. See CONTRIBUTING.md.

License

MIT. See LICENSE.

View the full README on GitHub

Install

npx -y unreal-engine-mcp-server
New to MCP? Start with the mcpskill.org guide →

Configuration

{ "mcpServers": { "unreal-engine": { "type": "http", "url": "http://127.0.0.1:3000/mcp", "headers": { "X-MCP-Capability-Token": "${UNREAL_MCP_TOKEN}" } } } }