Skip to main content
Glama

bunny — always-on MCP proxy for Unreal Engine 5.8

A small proxy that sits between your MCP harness (Junie, Cursor, Claude Code, VS Code, MCP Inspector, …) and the ModelContextProtocol plugin embedded in Unreal Engine 5.8. Two jobs:

  1. Fix the protocol-version deadlock. UE 5.8 whitelists 2025-11-25 / 2025-06-18 / 2024-11-05 and replies with 2025-11-25 to anything else. Kotlin-based SDKs (e.g. Junie's) offer 2025-03-26, see 2025-11-25 back, throw Server's protocol version is not supported: 2025-11-25 and mark the server Failed. The proxy negotiates with each side in its own language: it echoes back whatever protocol version the client asked for (including 2025-03-26) and talks 2025-06-18 upstream to UE.

  2. Never be offline. The proxy is its own MCP server. UE's tool catalog is cached to data/tools_cache.json on first connect and served even when the editor is closed, so harnesses keep the same stable MCP surface through code → compile → restart-engine loops. Calls to ue_* tools route to UE when it is running and return an actionable error when it is not (never a crash/timeout), and local proxy__* tools let the agent run the engine, build the project, and refresh the catalog itself.

harness ──http://127.0.0.1:8765/mcp──► bunny ──http://127.0.0.1:8000/mcp──► UE 5.8
   (any protocol version)             (shim + cache + launcher)          (2025-06-18)

Quick start

:: 1. install (once)
uv venv --python 3.14 .venv
uv pip install --python .venv\Scripts\python.exe -r requirements.txt

:: 2. point it at your project + engine
copy config.example.json config.json
notepad config.json

:: 3. run the proxy
bunny.bat
::   -> http://127.0.0.1:8765/health
::   -> MCP endpoint: http://127.0.0.1:8765/mcp

Initial tool cache: start UE once, run ModelContextProtocol.StartServer in its console (or set the plugin's Auto Start Server), then:

.venv\Scripts\python.exe -m bunny.server --refresh

After that the tools stay cached. UE can come and go freely.

Point your harness at it

CLI-only (there is deliberately no MCP tool for this — it's setup, not a runtime capability):

:: print the entry for a harness to stdout (default --agent claude),
:: plus its default global config location on stderr
.venv\Scripts\python.exe -m bunny.server --emit-client-config

:: write/merge the entry into a file (existing servers in it are preserved)
.venv\Scripts\python.exe -m bunny.server --emit-client-config "C:\path\mcp.json" --agent junie

--agent picks the harness syntax and default location:

--agent

Syntax emitted

Default global location (no --path)

claude (default)

Claude Desktop mcpServers stdio entry bridging via npx -y mcp-remote <url> (Desktop has no native url transport; needs Node)

%APPDATA%\Claude\claude_desktop_config.json

codex

TOML [mcp_servers.unreal-via-bunny] with url = … (Streamable HTTP native)

%USERPROFILE%\.codex\config.toml

opencode

opencode.json "mcp": {"unreal-via-bunny": {"type": "remote", "url": …}}

%USERPROFILE%\.config\opencode\opencode.json

hermes

YAML mcp_servers.unreal-via-bunny.url

%HERMES_HOME%\config.yaml (~/.hermes/config.yaml)

copilot

VS Code user mcp.json "servers": {"unreal-via-bunny": {"type": "http", "url": …}}

%APPDATA%\Code\User\mcp.json

junie

mcpServers with "type": "http", "url": …

%USERPROFILE%\.junie\mcp\mcp.json

Writes merge: JSON files keep their other servers, TOML/YAML replace only the bunny section. Restart the harness afterwards. Scope notes: Junie project scope .junie/mcp/mcp.json; Copilot CLI is ~/.copilot/mcp-config.json (mcpServers + type, write via explicit path); opencode also reads a project-root opencode.json. Junie example of the emitted entry:

{
  "mcpServers": {
    "unreal-via-bunny": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
  }
}

No special client settings needed: the proxy accepts 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28 clients.

Related MCP server: proxy-mcp

The tool surface

Always available (proxy-local, prefixed proxy__):

Tool

Purpose

proxy__ue_status

Is UE's MCP reachable? Editor process running? Launch/build possible?

proxy__ue_launch

Start the editor (-ExecCmds=ModelContextProtocol.StartServer) and block until MCP is reachable; re-caches tools. Always the GUI UnrealEditor.exe (see launch rules).

proxy__ue_wait_ready

Block until MCP reachable (use after you start UE manually / after live-coding).

proxy__ue_build

Build.bat <Target> Win64 Development -project=… — compiles with UE closed; auto-relaunches the editor on success (auto_launch_after_build).

proxy__ue_kill

Terminate the editor (confirmed=true required; no autosave).

proxy__ue_refresh_cache

Re-cache UE's tools/resources/prompts (do this after enabling more toolset plugins).

proxy__cache_stats

Catalog counts, capture time, effective config.

proxy__ue_configure

scan engine installs / write project+engine paths from inside the harness.

proxy__ue_adopt_project

Re-detect the running editor's project+engine and adopt as launch/build defaults.

proxy__ue_enable_toolset

Enable one toolset plugin (e.g. LiveCodingToolset) in the active project's .uproject (backs it up first).

proxy__ue_sync_toolsets

{dry_run?} — reconcile the project's toolset list with the engine: add gap plugins AllToolsets doesn't cover, prune redundant/vanished ones, respect Enabled:false opt-outs, idempotent.

proxy__ue_engines

List every installed engine (version, full build, path, discovery source, which is selected).

Cached UE tools are listed as ue_<toolname> (e.g. ue_list_toolsets, ue_call_tool), with their real UE schemas, at all times — UE online or not. When UE is live they forward verbatim (name demangled) and responses pass through untouched.

The intended agent loop:

(proxy__ue_status) -> ue offline
(proxy__ue_build)  -> compile the C++ you just edited
(proxy__ue_launch) -> editor up, MCP reachable, catalog refreshed
(ue_call_tool)     -> routed to the live engine
…close editor…     -> proxy stays up, tools stay listed, calls stay actionable

Configuration

config.json (copy from config.example.json), data/, and logs/ live in the root dir: the folder containing the bunny package, overridable with the BUNNY_ROOT_DIR env var — point it at a deployment folder to keep your config/state separate from the source tree and to run several independent instances. config.json is gitignored; config.example.json is the committed reference.

Key

Meaning

host / port

Proxy bind address (default 127.0.0.1:8765).

upstream_url

UE MCP endpoint (default http://127.0.0.1:8000/mcp).

project_path

Absolute path to your .uproject. Enables launch/build.

editor_binary

<engine>\Engine\Binaries\Win64\UnrealEditor.exe (GUI editor). Never UnrealEditor-Cmd.exe — see launch rules.

engine_root

Derived from editor_binary if omitted; used by proxy__ue_build.

build_target

Defaults to the Editor target discovered from Source/*.Target.cs (falls back to <ProjectName>Editor).

platform / configuration

Build parameters (default Win64 / Development).

launch_args

Appended on launch; keep -ExecCmds=ModelContextProtocol.StartServer.

launch_template / build_template

Full command templates (placeholders {editor} {project} {target} {platform} {configuration} {engine_root} {extra_args}).

poll_interval_s

UE liveness poll (default 12s; cache auto-refresh when UE comes up).

forward_timeout_s, build_timeout_s, launch_ready_timeout_s

Long-call budgets.

auto_adopt_project, auto_launch_after_build

Adoption / relaunch behaviors (see below).

scan_extra_roots

Extra folders to scan for engine installs (engines kept outside the standard roots).

Any key can be overridden per-run with env vars: BUNNY_PORT=8766, BUNNY_UPSTREAM_URL=…, BUNNY_PROJECT_PATH=… (handy for second instances). --print-config shows the effective config.

Follows any project (project adoption)

MCP tool routing is project-agnostic: UE owns 127.0.0.1:8000, so whichever project the editor has open is what the ue_* tools act on. Launch/build defaults follow it automatically: when UE comes up, the proxy detects the running editor's .uproject from its command line, resolves its engine, discovers its build targets from Source/*.Target.cs, and stores the result in data/adopted.json (survives proxy restarts; config.json stays the static default). proxy__ue_status shows active_project / active_engine / adopted_from; proxy__ue_adopt_project forces a re-scan after you open a different project.

Engine discovery is layered, never guessed: running editor's own executable → Epic Launcher manifests → Windows registry → standard install roots → scan_extra_roots. When every layer misses, the proxy reports and asks instead of guessing. Details: docs/implementation_details.md.

proxy__ue_sync_toolsets {"dry_run": true?} keeps the project's toolset list lean and engine-proof: AllToolsets is the enable-all switch (its manifest is engine-maintained), the project file stores only the gap plugins it doesn't cover. Sync adds missing gap plugins, prunes redundant/vanished ones, respects explicit Enabled:false opt-outs, never touches marketplace plugins, backs up the .uproject, and is idempotent. After a real sync run proxy__ue_build (UE closed) to compile new modules — it auto-relaunches the editor.

proxy__ue_enable_toolset {"plugin": "LiveCodingToolset"} enables any engine toolset plugin in the active project's .uproject; afterwards run proxy__ue_build and ue_list_toolsets shows the new toolset. LiveCodingToolset ships EnabledByDefault: false, so a harness that wants in-editor compiles calls this once per project.

Notes & limits

  • The proxy binds to 127.0.0.1 and does not add auth; treat it like UE's own MCP server (local only). UE's optional MCP auth token, if you enable it, is not forwarded — leave UE's server unauthenticated on loopback.

  • Server→client SSE streams (GET /mcp) answer 405: allowed by the spec; clients fall back to polling. Catalog freshness comes from refresh-on- launch / refresh-on-UE-boot / 60s-refresh-on-call.

  • Tool-name collisions with harnesses are avoided by the ue_ prefix. Unprefixed cached names are also accepted, in case a harness strips prefixes.

  • Resources/prompts are served from cache when offline; resources/read / prompts/get need UE live (forwarded, actionable error otherwise).

  • proxy__ue_launch detects "editor running but MCP server off" and tells the agent to run ModelContextProtocol.StartServer inside it.

  • Launch args must not include -unattended: UE then runs the ExecCmds and exits seconds later. -ExecCmds=ModelContextProtocol.StartServer -nosplash keeps the editor alive.

  • The proxy only starts a persistent editor with the GUI UnrealEditor.exe. UnrealEditor-Cmd.exe is for command/test runs that are supposed to close the engine afterwards. If editor_binary points at -Cmd, the proxy silently switches to the sibling UnrealEditor.exe; if that sibling doesn't exist it refuses with cmd_editor_not_allowed and tells you to fix editor_binary. Editor detection (running_project, proxy__ue_status) still sees -Cmd processes — reading is fine, launching is not. Why these rules exist: docs/implementation_details.md.

Running as a Windows service (nssm)

install_service.bat (run elevated — elevate_install.bat elevates it with one UAC click; set NSSM= at the top of install_service.bat to your nssm.exe) (re)installs ue-mcp-bunny: app = .venv\Scripts\python.exe -m bunny.server, working dir = the folder containing the script, autostart, account LocalSystem. Run it from the folder you want the proxy to live in (that folder holds its config.json / data/ / logs/).

  • LocalSystem is mandatory — an editor launched from a service must land on your desktop, and only LocalSystem has the privileges to reach the interactive session. Quick check: sc qc ue-mcp-bunny → SERVICE_START_NAME : LocalSystem, and proxy__ue_status → window.cross_session_route: "WTSQueryUserToken".

  • restart to load new code: nssm restart ue-mcp-bunny (elevated).

  • The full story (why running the service as your user fails, session-0 spawn routes, environment blocks): docs/implementation_details.md.

Tests

:: self-contained (mocked, no real UE needed)
.venv\Scripts\python.exe tests\test_proxy.py         :: proxy vs a mock UE (incl. the strict-SDK version deadlock)
.venv\Scripts\python.exe tests\test_sdk_client.py    :: via the official MCP Python SDK client
.venv\Scripts\python.exe tests\test_targets.py       :: build-target discovery
.venv\Scripts\python.exe tests\test_workspace.py     :: harness workspace detection
.venv\Scripts\python.exe tests\test_cmd_guard.py     :: proxy never launches UnrealEditor-Cmd.exe
.venv\Scripts\python.exe tests\test_crosssession.py  :: session-0 -> interactive-session spawn logic
.venv\Scripts\python.exe tests\test_session_policy.py:: client session/policy bookkeeping
.venv\Scripts\python.exe tests\test_window.py        :: child window policies (spawns short consoles)
.venv\Scripts\python.exe tests\test_guardian.py      :: keeps build consoles minimized

:: real end-to-end (needs a running proxy + configured project/engine)
.venv\Scripts\python.exe tests\e2e_real.py           :: launch -> cache -> real tool calls
.venv\Scripts\python.exe tests\e2e_lifecycle.py --keep-ue  :: kill UE -> offline cached tools -> proxy relaunch -> live routing
.venv\Scripts\python.exe tests\e2e_soak.py --minutes 3     :: editor survives 3 min after proxy launch
.venv\Scripts\python.exe tests\e2e_adoption.py       :: running project auto-adoption recorded cleanly in data/adopted.json
.venv\Scripts\python.exe tests\e2e_build.py          :: build with UE closed -> auto-relaunch

Logs: logs/bunny.log (proxy), logs/editor_launch.log (UE), logs/build.log.

Docs

  • docs/implementation_details.md — design notes: protocol shim internals, engine resolution layers, launch rules and the -Cmd trap, the service/session-0 story, toolset sync semantics.

Credits

Vibecode by Qwen Flash Next GSQ RCO IQ3_XXS on Strata engine, Human QA by Charnet3D

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers