From 8c632bd062b9bbb0107ee16c4b832a662337e234 Mon Sep 17 00:00:00 2001 From: Joseph Ibrahim Date: Thu, 2 Jul 2026 12:42:53 -0400 Subject: [PATCH 1/2] =?UTF-8?q?release:=20v0.2.0=20=E2=80=94=20the=20Epic?= =?UTF-8?q?=20MCP=20era?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Version hygiene: - single source: pyproject dynamic version -> ue_mcp/__version__.py (0.2.0); both .uplugin VersionName -> 0.2.0. Public line continues from v0.1.1; the never-released internal 2.x numbering is retired. Docs (all reflect the M4 post-flip state): - README rewritten ADHD-friendly: the one-paragraph coexistence story, who-does-what table, the core-20 by capability, the commodity tier behind UE_MCP_PROFILE=full, "honesty as architecture", two updated mermaid diagrams (added Epic MCP :8000 + test-layer nodes). - CHANGELOG [0.2.0]: retirement flip, exec-sim harness, 11 bug fixes, decruft, version-renumbering note. - INVENTORY refreshed: 58 tools (56+2) / 20 mounted / 14 modules / 580 tests. UE 5.8 port (verified: editor target compiles clean against 5.8): - ViewportPerception FrameProducer: OnBackBufferReadyToPresent's 2nd param changed from FTextureRHIRef to ISlateViewportProvider& in 5.8. Dual-version #if guard calls GetBackBufferResource() on 5.8, the raw ref on 5.7 — one source tree, both engines. UEBridge plugin needed no changes. Fact-checked: ue_measure moved to its correct module row; unverifiable test-count figure softened to cite the harness README. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 56 +++ INVENTORY.md | 11 +- Plugins/UEBridge/UEBridge.uplugin | 2 +- .../Private/FrameProducer.cpp | 14 +- .../Private/FrameProducer.h | 15 +- .../ViewportPerception.uplugin | 2 +- README.md | 386 +++++++----------- pyproject.toml | 6 +- ue_mcp/__version__.py | 11 +- 9 files changed, 248 insertions(+), 255 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e353d2c..7610db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,62 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2026-07-02 — the Epic MCP era + +> **Version renumbering:** the public line continues from v0.1.1. An internal +> 2.x numbering existed in-repo but was never released; it was retired in this +> release. `pyproject` now reads the single version source +> (`ue_mcp/__version__.py`) via hatchling, and tags must match it. + +UE 5.8 ships Epic's official **Unreal MCP** (830 tools across 52 toolsets with +`AllToolsets`). This release repositions the bridge around what Epic does NOT +ship, with every decision grounded in a live probe of the real surface. + +### Changed — the retirement flip +- **Tiered tool registry** at the `register_all_tools()` seam. Default profile + is `core`: **20 tools mounted** (the differentiated layer + `ue_status`/ + `ue_health_check`). `UE_MCP_PROFILE=full` remounts the 36 Epic-covered + commodity tools; `all` adds the two honest not-implemented slots. +- `ue_health_check` now reports the active profile, mount counts, and Epic + MCP (`:8000`) reachability. +- `ue_undo`/`ue_redo` return an explicit not-implemented error (no editor + round-trip): no scriptable editor-transaction route exists in the UE Python + API, and they previously probed nonexistent APIs on every call. + +### Added +- **`docs/EPIC_MCP_MATRIX.md`** — the retirement contract-of-record: verdicts + for all 58 tools against the probed Epic surface, plus raw captures + (`docs/epic_mcp/`) and a repeatable prober (`scripts/probe_epic_mcp.py`). +- **Exec-simulating test harness** (`tests/exec_sim/`): a strict fake `unreal` + module, per-tool sentinel registry, compile/exec/sentinel gates, and + scripted-failure honesty contracts. Proven red on the pre-fix tree — every + failure mapped 1:1 to a known bug (see `tests/exec_sim/README.md`). + **580 tests total** (was 415). +- `tests/test_registry_tiers.py` pins the matrix arithmetic and profile + semantics; tier/tool drift fails CI in both directions. +- Two-server `.mcp.json` (this bridge over stdio + Epic's server on `:8000`); + `ModelContextProtocol` + `AllToolsets` staged `Optional: true` in the + `.uproject` (no-ops on 5.7, self-enables on 5.8). + +### Fixed +- **All 11 confirmed bugs** from the 2026-06-11 hand-verified review: the + level-actor resolver (asset-API misuse in delete/transform), `true`→`True` + NameError, spawn-blueprint label indent, `load_level` false success, + `focus_actor` unconditional success, phantom `is_hidden()`, unescaped + `find_assets` patterns, the cloner arg-discard, the viewport-fallback race, + material-parameter API-family mixing, and the wheel-killing shim import. +- Adversarial-verification findings fixed pre-merge: a stale-frame regression + in the perception fallback, wrong ClonerEffector class names for 5.7 (all + cloner writes are now read-back-verified), a working `CAMERA ALIGN` focus + route, and `find_assets` escaping moved into the codegen chokepoint. +- `metrics` uptime rounding (coarse-clock test flake). + +### Removed +- **The legacy Translators questionnaire runtime**: the game-flow state + machine entry points, trivia UI, Blueprint relay component, and the + repo-root runner. The `usd_bridge/` package is parked in-repo, out of the + ship path. + ## [0.1.0] - 2026-05-30 First tagged release. Demo-ready on a maintainer's machine; see **Status** below for what stands between this and a distributable plugin. diff --git a/INVENTORY.md b/INVENTORY.md index edd434f..cc7cd5f 100644 --- a/INVENTORY.md +++ b/INVENTORY.md @@ -1,4 +1,4 @@ -# UnrealEngine_Bridge v2.1.0 — Inventory +# UnrealEngine_Bridge v0.2.0 — Inventory MCP server bridging Claude Code to Unreal Engine 5 via Remote Control API. @@ -6,11 +6,10 @@ MCP server bridging Claude Code to Unreal Engine 5 via Remote Control API. | Metric | Count | |--------|-------| -| MCP Tools | 56 | -| Tool Modules | 11 | -| Tests | 222 | -| Python source files | 27 | -| Total lines (Python) | ~8,000 | +| MCP Tools | 58 (56 registry + 2 server) | +| Mounted by default | 20 (`core` profile; `UE_MCP_PROFILE=full` remounts the commodity tier) | +| Tool Modules | 14 | +| Tests | 580 | ## Dependencies diff --git a/Plugins/UEBridge/UEBridge.uplugin b/Plugins/UEBridge/UEBridge.uplugin index 181e18f..6c96e2f 100644 --- a/Plugins/UEBridge/UEBridge.uplugin +++ b/Plugins/UEBridge/UEBridge.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "2.1.0", + "VersionName": "0.2.0", "EngineVersion": "5.7.0", "FriendlyName": "UE Bridge", "Description": "Agentic AI bridge for Unreal Engine. Provides MCP tool integration, file-based bridge protocol with USD transport, behavioral signal collection, and Remote Control API access for Claude Code.", diff --git a/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.cpp b/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.cpp index e41b848..1a7710f 100644 --- a/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.cpp +++ b/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.cpp @@ -7,6 +7,9 @@ #include "RenderingThread.h" #include "Framework/Application/SlateApplication.h" #include "RHISurfaceDataConversion.h" +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8 +#include "Slate/SlateViewportProvider.h" +#endif FFrameProducer::FFrameProducer() : FrameCounter(0) @@ -72,13 +75,20 @@ void FFrameProducer::SetThrottleInterval(double Seconds) MinCaptureInterval = FMath::Max(Seconds, 0.01); // Cap at 100fps } -void FFrameProducer::OnFrameBufferReady(SWindow& SlateWindow, const FTextureRHIRef& FrameBuffer) +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8 +void FFrameProducer::OnFrameBufferReady(SWindow& SlateWindow, ISlateViewportProvider& ViewportProvider) { + FRHITexture* FrameBuffer = ViewportProvider.GetBackBufferResource(); +#else +void FFrameProducer::OnFrameBufferReady(SWindow& SlateWindow, const FTextureRHIRef& FrameBufferRef) +{ + FRHITexture* FrameBuffer = FrameBufferRef.GetReference(); +#endif // Runs on the render thread. All readback state below is render-thread-only, // so no synchronization is needed for it. We NEVER block the render thread: // a copy is enqueued on one present and drained on a later one once the GPU // has finished, instead of a synchronous ReadSurfaceData stall. - if (!PixelBus || !FrameBuffer.IsValid()) + if (!PixelBus || !FrameBuffer) { return; } diff --git a/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.h b/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.h index 275e71e..69c1b88 100644 --- a/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.h +++ b/Plugins/ViewportPerception/Source/ViewportPerception/Private/FrameProducer.h @@ -7,8 +7,12 @@ #include "CoreMinimal.h" #include "RHI.h" #include "RHIGPUReadback.h" +#include "Runtime/Launch/Resources/Version.h" class FPixelBus; +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8 +class ISlateViewportProvider; +#endif class FFrameProducer { @@ -29,8 +33,15 @@ class FFrameProducer bool IsActive() const { return bActive; } private: - /** Called on the render thread when the backbuffer is ready. */ - void OnFrameBufferReady(SWindow& SlateWindow, const FTextureRHIRef& FrameBuffer); + /** Called on the render thread when the backbuffer is ready. + * 5.8 changed OnBackBufferReadyToPresent's second param from the raw + * texture to an ISlateViewportProvider — both signatures are kept so the + * plugin compiles against 5.7 and 5.8 from the same source. */ +#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8 + void OnFrameBufferReady(SWindow& SlateWindow, ISlateViewportProvider& ViewportProvider); +#else + void OnFrameBufferReady(SWindow& SlateWindow, const FTextureRHIRef& FrameBufferRef); +#endif FDelegateHandle DelegateHandle; FPixelBus* PixelBus = nullptr; diff --git a/Plugins/ViewportPerception/ViewportPerception.uplugin b/Plugins/ViewportPerception/ViewportPerception.uplugin index 3b94d0f..8b344c6 100644 --- a/Plugins/ViewportPerception/ViewportPerception.uplugin +++ b/Plugins/ViewportPerception/ViewportPerception.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, "Version": 1, - "VersionName": "2.1.0", + "VersionName": "0.2.0", "EngineVersion": "5.7.0", "FriendlyName": "Viewport Perception", "Description": "AI visual awareness for UE5 — captures the editor viewport and serves perception packets (frame + metadata) to the MCP bridge.", diff --git a/README.md b/README.md index ada864d..83774dd 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,24 @@ [![Release](https://img.shields.io/github/v/release/JosephOIbrahim/UnrealEngine_Bridge?sort=semver)](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/releases/latest) [![CI](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Unreal Engine 5.7](https://img.shields.io/badge/Unreal%20Engine-5.7-0a0a14)](https://www.unrealengine.com/) +[![Unreal Engine 5.7 · 5.8-ready](https://img.shields.io/badge/Unreal%20Engine-5.7%20·%205.8--ready-0a0a14)](https://www.unrealengine.com/) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/) -**Give Claude Code full control of Unreal Engine 5.7.** Spawn actors, tweak materials, light scenes, reason about space, capture the viewport, keyframe animations — all through natural language via the [Model Context Protocol](https://modelcontextprotocol.io/). +**Claude Code, working inside your Unreal editor.** This bridge gives Claude the abilities Epic's own MCP doesn't ship: run real editor Python, see the viewport continuously, light scenes with one command, reason about space with surface normals, and stay honest about every result. -58 MCP tools (20 mounted by default · commodity tier via `UE_MCP_PROFILE=full`) | 14 tool modules | 570+ tests | Python 3.11+ · [Changelog](CHANGELOG.md) · [Security](SECURITY.md) +**58 MCP tools · 20 mounted by default · 580 tests** · [Changelog](CHANGELOG.md) · [Security](SECURITY.md) -> **Epic MCP era:** UE 5.8 ships an official [Unreal MCP](docs/EPIC_MCP_MATRIX.md) covering the commodity control plane (830 tools with AllToolsets). This bridge now mounts its **differentiated core** by default — arbitrary editor Python, console exec, lighting moods, ClonerEffector mograph, continuous perception + scene diffing, normal-aware spatial reasoning, resilience — and keeps the 36 Epic-covered tools available behind `UE_MCP_PROFILE=full`. Dispositions: [EPIC_MCP_MATRIX.md](docs/EPIC_MCP_MATRIX.md). +--- + +## The one-paragraph story + +UE 5.8 ships Epic's official **Unreal MCP** — 830 tools once you enable `AllToolsets`. That's the commodity control plane: spawn, transform, materials, Blueprints, Sequencer. **So this bridge stopped competing and specialized.** We live-probed Epic's entire surface, wrote the verdict for every one of our tools into [EPIC_MCP_MATRIX.md](docs/EPIC_MCP_MATRIX.md), and flipped the default: you get our **differentiated core** out of the box, Epic handles the basics, and the 36 overlapping tools stay one env var away. --- -## How It Works +## How it works + +Two MCP servers, one editor. Claude uses both. ```mermaid graph LR @@ -22,45 +28,51 @@ graph LR A[MCP Client] end - subgraph Python + subgraph B1["This bridge — Python"] B[MCP Server
FastMCP · stdio] - B --> C[Tool Modules
14 modules · 56 tools] + B --> C[Tiered Registry
core: 20 · full: 56] C --> D[Remote Control Bridge
httpx · circuit breaker] end - subgraph Unreal Engine 5.7 + subgraph Unreal Editor E[Remote Control API
localhost:30010] E --> F[Editor Runtime] G[ViewportPerception
localhost:30011] G --> F + H[Epic Unreal MCP
localhost:8000 · UE 5.8] + H --> F end A -- stdio --> B + A -. HTTP /mcp .-> H D -- HTTP --> E C -. viewport capture .-> G style A fill:#5CFFDB,color:#05050D style B fill:#5CFFDB,color:#05050D - style C fill:#12121F,color:#fff + style C fill:#5CFFDB,color:#05050D style D fill:#12121F,color:#fff style E fill:#FFCC33,color:#05050D style F fill:#12121F,color:#fff style G fill:#FFCC33,color:#05050D + style H fill:#FFCC33,color:#05050D ``` ---- +**Who does what:** -## Quick Start +| Job | Server | +|---|---| +| Spawn / transform / materials / Blueprints / Sequencer | **Epic's MCP** (UE 5.8, `AllToolsets`) | +| Editor Python, console, lighting moods, cloner, perception, spatial reasoning | **This bridge** | +| Works on UE 5.7 (no Epic MCP there) | **This bridge**, with `UE_MCP_PROFILE=full` | -### Prerequisites +--- -| Requirement | Version | -|---|---| -| Unreal Engine | 5.7 | -| Python | 3.11+ | -| Remote Control API plugin | Enabled in UE5 (ships with engine) | +## Quick start -### 1. Clone & Install +**You need:** UE 5.7 or 5.8 · Python 3.11+ · the Remote Control API plugin enabled (ships with the engine). + +**1 — Install** ```bash git clone https://github.com/JosephOIbrahim/UnrealEngine_Bridge.git @@ -68,197 +80,146 @@ cd UnrealEngine_Bridge pip install -e . ``` -### 2. Open the UE5 Project +**2 — Open the project** -1. Open `UnrealEngine_Bridge.uproject` in Unreal Engine 5.7 -2. Verify the **Remote Control API** plugin is enabled (Edit > Plugins) -3. Confirm `localhost:30010` is reachable (check Output Log for "Remote Control Web Server started") +Open `UnrealEngine_Bridge.uproject`. Check the Output Log for *"Remote Control Web Server started"* — that's `localhost:30010` up. -### 3. Connect Claude Code +On **UE 5.8**, the `ModelContextProtocol` and `AllToolsets` plugins self-enable (they're staged `Optional` in the project). Start Epic's server from the console: `ModelContextProtocol.StartServer`. -Add this to your Claude Code MCP configuration (`~/.claude/settings.json` or project `.mcp.json`): +**3 — Connect Claude Code** -```json -{ - "mcpServers": { - "unreal": { - "command": "python", - "args": ["-m", "ue_mcp.mcp_server"], - "cwd": "/path/to/UnrealEngine_Bridge" - } - } -} -``` +The repo ships a ready [`.mcp.json`](.mcp.json) — open the repo folder in Claude Code and both servers are configured. For other projects, copy it and fix the path. + +**4 — Try it** -### 4. Start Building +> *"Set golden hour and snap the crates to the terrain."* -Open Claude Code and try: +Claude uses `ue_apply_mood_preset` and `ue_snap_to_ground` — two things no other UE MCP can do. -> *"Spawn a cube at the origin, make it red, and rotate it 45 degrees"* +**Want the commodity tools from this bridge instead of Epic's?** Set the env var and restart the server: -Claude will use `ue_spawn_actor`, `ue_create_material`, `ue_assign_material`, and `ue_set_transform` automatically. +```json +"unreal": { "command": "python", "args": ["-m", "ue_mcp.mcp_server"], "env": { "UE_MCP_PROFILE": "full" } } +``` --- -## MCP Tools Reference +## What's mounted by default (the core 20) -### Actors (6 tools) -| Tool | Description | -|---|---| -| `ue_spawn_actor` | Create an actor by class name with position/rotation | -| `ue_delete_actor` | Remove an actor from the level | -| `ue_list_actors` | List all level actors, optionally filtered by class | -| `ue_set_transform` | Set location, rotation, and/or scale | -| `ue_duplicate_actor` | Clone an actor with an offset | -| `ue_get_actor_bounds` | Get axis-aligned bounding box | - -### Scene Understanding (4 tools) -| Tool | Description | -|---|---| -| `ue_get_actor_details` | Full inspection: class, transform, components, tags, parent | -| `ue_query_scene` | Multi-filter search (class, tag, name pattern, spatial proximity) | -| `ue_get_component_details` | Deep component info (mesh assets, materials, light properties) | -| `ue_get_actor_hierarchy` | Recursive parent-child attachment tree | +These are the tools Epic's MCP does **not** have — verified against all 830 of theirs ([the matrix](docs/EPIC_MCP_MATRIX.md)). -### Spatial Reasoning (4 tools) -| Tool | Description | +### 🐍 Editor Python & console (2) +| Tool | What it does | |---|---| -| `ue_ground_trace` | Line-trace down at (x, y) to find the ground — hit point, normal, distance, actor | -| `ue_snap_to_ground` | Drop an actor onto the surface beneath it, optionally tilting to the slope | -| `ue_spatial_query` | Nearest-N, AABB overlap, combined-bounds footprint, or box-contents queries | -| `ue_measure` | Straight-line distance between actors, or extent + footprint area of one | +| `ue_execute_python` | **The escape hatch.** Run real Python in the editor with the full `unreal` API (AST-sandboxed). Epic's `execute_tool_script` can't even `import unreal`. | +| `ue_console_command` | Run console commands with structured output parsing. No console exec exists anywhere in Epic's surface. | -### Materials (4 tools) -| Tool | Description | +### 👁 Perception (4) +| Tool | What it does | |---|---| -| `ue_create_material_instance` | Create a MaterialInstanceConstant from a parent material | -| `ue_set_material_parameter` | Set scalar, vector, or texture parameters | -| `ue_get_material_parameters` | List all exposed parameters with current values | -| `ue_assign_material` | Apply a material to a specific mesh slot | +| `ue_viewport_percept` | One frame + camera, selection, and scene metadata in a single call | +| `ue_viewport_watch` | Continuous capture at a set rate — Claude keeps watching | +| `ue_viewport_diff` | Two-snapshot structural diff: what moved, appeared, disappeared | +| `ue_viewport_config` | Resolution / format / rate for the capture pipeline | -### Blueprints (7 tools) -| Tool | Description | +### 🌅 Lighting & atmosphere (5) +| Tool | What it does | |---|---| -| `ue_create_blueprint` | Create a new Blueprint asset | -| `ue_add_component` | Add a component to a live actor | -| `ue_set_component_property` | Set a property on a component | -| `ue_set_blueprint_defaults` | Override CDO default values | -| `ue_compile_blueprint` | Compile and save a Blueprint | -| `ue_get_actor_components` | List all components on an actor | -| `ue_spawn_blueprint` | Spawn a Blueprint instance into the level | - -### Sequencer & Animation (4 tools) -| Tool | Description | +| `ue_apply_mood_preset` | One command = coordinated sun + fog + clouds + color grade | +| `ue_blend_mood_presets` | Interpolate two moods (t 0→1) for in-between looks | +| `ue_set_time_of_day` | Hour 0–24 → sun elevation, azimuth, color, intensity | +| `ue_setup_sky_atmosphere` | Build or update the whole sky rig, idempotently | +| `ue_list_mood_presets` | See the built-in cinematic presets | + +### 📐 Spatial reasoning (4) +| Tool | What it does | |---|---| -| `ue_create_level_sequence` | Create a new LevelSequence asset | -| `ue_play_sequence` | Play or scrub a sequence at a given time/rate | -| `ue_add_actor_to_sequence` | Bind a level actor to a sequence | -| `ue_add_keyframe` | Add a keyframe for a property at a given time | +| `ue_ground_trace` | Trace down at (x, y): hit point, **surface normal**, distance, actor. Epic's `trace_world` returns a bare distance. | +| `ue_snap_to_ground` | Drop an actor onto the surface, optionally tilting to the slope | +| `ue_spatial_query` | Nearest-N, AABB overlap, combined bounds, box contents | +| `ue_get_actor_hierarchy` | The full recursive attachment tree in one call | -### Perception (4 tools) -| Tool | Description | +### 🧩 Scene gaps Epic left open (3) +| Tool | What it does | |---|---| -| `ue_viewport_percept` | Capture the viewport with camera, selection, and scene metadata | -| `ue_viewport_watch` | Start/stop continuous viewport capture | -| `ue_viewport_config` | Configure capture resolution, format, and rate | -| `ue_viewport_diff` | Two-snapshot structural diff (actors, camera, selection changes) | - -### Level (4 tools) -| Tool | Description | -|---|---| -| `ue_save_level` | Save the current level | -| `ue_get_level_info` | Get level name and actor count | -| `ue_load_level` | Load a level by content path | +| `ue_duplicate_actor` | Clone a level actor with an offset (no Epic equivalent) | | `ue_get_world_info` | Streaming levels, world settings, game mode | +| `ue_create_cloner` | ClonerEffector procedural instancing, with read-back-verified configuration | -### Assets (3 tools) -| Tool | Description | +### ❤️ Bridge health (2) +| Tool | What it does | |---|---| -| `ue_find_assets` | Search the Content Browser by pattern and class | -| `ue_create_material` | Create a material with BaseColor/Roughness/Metallic nodes | -| `ue_delete_asset` | Delete an asset from the Content Browser | +| `ue_status` | Is the editor up? Is Remote Control reachable? | +| `ue_health_check` | Version, uptime, circuit breaker, per-call metrics, active tool profile, **Epic MCP reachability** | -### Motion Graphics (3 tools) -| Tool | Description | -|---|---| -| `ue_create_cloner` | ClonerEffector procedural instancing | -| `ue_create_niagara_system` | Spawn a Niagara particle system | -| `ue_create_pcg_graph` | Create a PCG procedural generation volume | +--- -### Lighting & Atmosphere (5 tools) -| Tool | Description | -|---|---| -| `ue_setup_sky_atmosphere` | Build/update the sky rig (sun, sky, fog, clouds, post) idempotently | -| `ue_set_time_of_day` | Map hour 0–24 → sun elevation/azimuth/colour/intensity | -| `ue_list_mood_presets` | List the built-in cinematic mood presets | -| `ue_apply_mood_preset` | Apply a coordinated sun + fog + clouds + colour-grade package | -| `ue_blend_mood_presets` | Interpolate between two presets (t in 0..1) and apply | - -### Editor Utilities (5 tools) -| Tool | Description | -|---|---| -| `ue_console_command` | Execute console commands with structured output parsing | -| `ue_undo` / `ue_redo` | Undo or redo the last editor transaction | -| `ue_focus_actor` | Focus the viewport camera on an actor | -| `ue_select_actors` | Set editor selection by label | +## The commodity tier (36 tools, `UE_MCP_PROFILE=full`) -### Properties (2 tools) -| Tool | Description | -|---|---| -| `ue_get_property` | Read any UObject property by path | -| `ue_set_property` | Write any UObject property by path | +Epic's MCP covers these — the matrix cites the exact equivalent for every row. They stay in the codebase, tested and honest, for UE 5.7 or as a fallback: -### Python Execution (1 tool) -| Tool | Description | +| Module | Tools | |---|---| -| `ue_execute_python` | Run arbitrary Python in the editor (AST-sandboxed) | +| Actors | spawn · delete · list · set_transform · get_bounds | +| Scene | actor details · scene query · component details | +| Blueprints | create · add component · set properties · CDO defaults · compile · list components · spawn | +| Materials | create instance · set / get parameters · assign to slot | +| Sequencer | create sequence · play/scrub · bind actor · keyframe | +| Level | save · info · load | +| Assets | find · create material · delete | +| Mograph | Niagara system · PCG graph | +| Properties | get / set any UObject property | +| Editor | focus actor · select actors | +| Spatial | measure (distance / extent) | + +**Experimental tier** (`UE_MCP_PROFILE=all`): `ue_undo` / `ue_redo` — honest not-implemented slots. The UE Python API has no scriptable editor-transaction route; these say so instead of pretending. --- -## Architecture +## Why trust the results? Honesty as architecture + +This bridge's tools **cannot silently lie** — that's enforced, not promised: + +- **Exec-simulated codegen tests.** Every generated editor script is compiled and executed against a strict fake `unreal` module in CI. Phantom APIs raise. Dropped arguments fail a sentinel gate. Hard-coded success prints fail honesty contracts. +- **Read-back verification.** Writes that UE can silently ignore (cloner layout names) are read back before being reported "applied". +- **Honest statuses.** The viewport fallback reports `capture_status: timeout` instead of an empty image with `success: true`. Not-implemented tools say "not implemented". +- **580 tests**, including scripted-failure contracts for every historical lying-tool bug. ```mermaid graph TB - subgraph "Python Layer" + subgraph Python layer MCP["MCP Server
ue_mcp/mcp_server.py"] + Reg["Tiered Registry
core / full / all"] Tools["Tool Modules (14)
ue_mcp/tools/"] + Val["Validation & AST Sandbox
_validation.py"] RC["Remote Control Bridge
remote_control/"] - Val["Validation & Sandbox
_validation.py"] - Met["Metrics & Circuit Breaker
metrics.py"] - Log["JSON Logger
logging.py"] + Met["Metrics & Circuit Breaker"] end - subgraph "File Bridge Layer" - Orch["Bridge Orchestrator
bridge_orchestrator.py"] - USD["USD Bridge
usd_bridge/"] - FS[("~/.translators/
bridge_state.usda
heartbeat.json")] + subgraph Test layer + Sim["Exec-Sim Harness
strict fake unreal · honesty contracts"] end - subgraph "C++ Plugins (UE5)" - Sub["UEBridgeSubsystem
State machine · polling"] - Ed["BridgeEditorSubsystem
File watching · process mgmt"] - VP["ViewportPerception
GPU readback · ring buffer"] - RCAPI["Remote Control API
REST · port 30010"] + subgraph Unreal Editor + RCAPI["Remote Control API
REST · :30010"] + VP["ViewportPerception plugin
GPU readback · :30011"] + EPIC["Epic Unreal MCP
HTTP+SSE · :8000 · UE 5.8"] end - MCP --> Tools + MCP --> Reg --> Tools Tools --> Val - Tools --> RC - RC --> Met - MCP --> Log - RC -- "HTTP" --> RCAPI - RCAPI --> Sub - Orch --> USD - USD --> FS - FS --> Sub - Sub --> Ed - VP -- "HTTP :30011" --> Tools + Tools --> RC --> Met + RC -- HTTP --> RCAPI + Tools -. capture .-> VP + Sim -. gates every generated script .-> Tools style MCP fill:#5CFFDB,color:#05050D + style Reg fill:#5CFFDB,color:#05050D style Tools fill:#5CFFDB,color:#05050D + style Sim fill:#12121F,color:#fff style RCAPI fill:#FFCC33,color:#05050D - style Sub fill:#12121F,color:#fff style VP fill:#FFCC33,color:#05050D + style EPIC fill:#FFCC33,color:#05050D ``` ### Resilience @@ -269,116 +230,63 @@ graph TB | **Connection pooling** | 10 max connections, 5 keepalive | | **Timeout** | 10s adaptive default | | **Atomic file I/O** | `tempfile` + `os.replace` (NTFS-safe) | -| **Python sandbox** | AST-based validation blocking `os`, `subprocess`, `open`, `getattr`, dunders | - -### Security - -All Python code executed in UE5 is validated through a multi-layer sandbox: - -- **Blocked modules**: `os`, `sys`, `subprocess`, `shutil`, `socket`, `pickle`, `importlib`, and 20+ more -- **Blocked builtins**: `exec`, `eval`, `open`, `getattr`, `globals`, `__import__` -- **Blocked attributes**: `system`, `popen`, `rmtree`, `kill`, plus dangerous dunders -- **Path validation**: Content path traversal (`..`) prevention on all inputs -- **Console safety**: Blocked commands (`exit`, `quit`, `crash`) and newline injection prevention +| **Python sandbox** | AST validation blocking `os`, `subprocess`, `open`, `getattr`, dunders | ### Network surface & trust model -The bridge talks to the editor over two **localhost** services: the Unreal Remote Control API (`:30010`) and ViewportPerception (`:30011`). They are designed for a **single trusted operator on a single machine** — the developer running Claude Code and the editor on the same workstation — and should not be exposed to untrusted networks. Viewport perception is **opt-in (disabled by default)**. +Everything is **localhost, single trusted operator**: Remote Control (`:30010`), ViewportPerception (`:30011`, **off by default**), and — on 5.8 — Epic's MCP (`:8000`, no auth, serial on the game thread; Epic's EULA §6(e) governs what your LLM provider may do with transmitted data). Don't expose any of them to untrusted networks. -See **[SECURITY.md](SECURITY.md)** for the full trust model, operator hardening guidance, and how to report a vulnerability. +Full trust model and hardening guidance: **[SECURITY.md](SECURITY.md)**. --- -## Project Structure +## Project structure ``` UnrealEngine_Bridge/ ├── ue_mcp/ # MCP server package -│ ├── mcp_server.py # FastMCP entry point (stdio) +│ ├── mcp_server.py # FastMCP entry point (stdio) + health tools │ ├── metrics.py # Telemetry + observability -│ ├── logging.py # Structured JSON logging -│ └── tools/ # 14 tool modules (56 tools) -│ ├── actors.py # Spawn, delete, list, transform -│ ├── scene.py # Query, details, hierarchy -│ ├── spatial.py # Ground trace, snap-to-ground, measure -│ ├── materials.py # Create, set params, assign -│ ├── lighting.py # Sky/atmosphere rig, time of day, mood presets -│ ├── blueprints.py # Create, compile, components -│ ├── sequencer.py # Animation / Level Sequence -│ ├── perception.py # Viewport capture + diff -│ ├── editor.py # Console, undo/redo, focus -│ ├── level.py # Save, load, world info -│ ├── assets.py # Find, create, delete -│ ├── mograph.py # Cloner, Niagara, PCG -│ ├── properties.py # Get/set UObject properties -│ ├── python_exec.py # Sandboxed Python execution -│ ├── _validation.py # Input sanitization + AST sandbox -│ ├── _codegen.py # Shared code generation snippets -│ ├── _console_parsers.py # Structured stat output parsers -│ └── _types.py # Protocol types -│ -├── remote_control/ # UE5 HTTP bridge package -│ ├── circuit_breaker.py # CLOSED/OPEN/HALF_OPEN state machine -│ ├── async_client.py # AsyncUnrealRemoteControl (MCP) -│ ├── sync_client.py # UnrealRemoteControl (standalone) -│ ├── codegen.py # UE5 Python script generation -│ ├── execution.py # File-based result polling -│ └── constants.py # URLs, timeouts, pool config -│ -├── usd_bridge/ # USD file I/O package -│ ├── io.py # Atomic writes, locking, paths -│ ├── question.py # Question read/write -│ ├── transition.py # State transitions, finales -│ ├── signals.py # Behavioral signal extraction -│ ├── profile.py # Cognitive profiling + checksums -│ └── validation.py # Bridge state validation -│ +│ ├── ue_logging.py # Structured JSON logging +│ └── tools/ # 14 modules · 56 tools · tiered registry in __init__.py +├── remote_control/ # UE5 HTTP bridge (circuit breaker, codegen, polling) +├── usd_bridge/ # USD file I/O package (parked, out of the ship path) ├── Plugins/ -│ ├── UEBridge/ # Core bridge C++ plugin -│ │ ├── UEBridgeRuntime/ # Subsystem, types, UI, style -│ │ └── UEBridgeEditor/ # File watching, process management -│ └── ViewportPerception/ # GPU readback, ring buffer, HTTP endpoint -│ -├── bridge_orchestrator.py # Game flow orchestration -├── remote_control_bridge.py # Backward-compat shim → remote_control/ -├── usd_bridge.py # Backward-compat shim → usd_bridge/ -├── tests/ # 415 tests (pytest) -├── pyproject.toml # Build config, dependencies, tooling -└── .github/workflows/ci.yml # CI: Python 3.11/3.12, coverage, lint +│ ├── UEBridge/ # Editor panel, file watcher (C++) +│ └── ViewportPerception/ # GPU readback + HTTP endpoint (C++) +├── docs/ +│ ├── EPIC_MCP_MATRIX.md # Retirement contract-of-record (probe-grounded) +│ └── epic_mcp/ # Raw probe captures of Epic's 830-tool surface +├── scripts/probe_epic_mcp.py # Re-probe Epic's surface per engine version +├── tests/ # 580 tests, incl. tests/exec_sim/ + tier gates +├── smoke_live.py # Live-editor smoke harness (on-demand) +└── .mcp.json # Two-server config: this bridge + Epic's MCP ``` --- ## Development -### Running Tests +**Run the tests** ```bash pip install -e ".[dev]" -python -m pytest tests/ -v -``` - -### Running with Coverage - -```bash -python -m pytest tests/ --cov=ue_mcp --cov=remote_control --cov-report=term-missing +python -m pytest -q # 580 tests ``` -### Linting +**Lint** ```bash -pip install ruff ruff check ue_mcp/ remote_control/ tests/ ``` -### Adding a New Tool +**Add a tool** (each step is checked by CI) -1. Create `ue_mcp/tools/your_module.py` -2. Define a `register(server: MCPServer, ue: UEBridge) -> None` function -3. Use `@server.tool()` decorators inside `register()` -4. Validate inputs with sanitizers from `_validation.py` -5. Register in `ue_mcp/tools/__init__.py` -6. Add tests in `tests/` +1. Create `ue_mcp/tools/your_module.py` with a `register(server, ue)` function +2. Decorate tools with `@server.tool(...)`; validate inputs via `_validation.py` +3. Wire the module into `ue_mcp/tools/__init__.py` — **and classify the tool in `TIERS`** (an unclassified tool fails CI) +4. Add a sentinel entry in `tests/exec_sim/registry.py` (a missing entry fails CI) +5. Add tests --- diff --git a/pyproject.toml b/pyproject.toml index 109b374..7b4e5d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ue-bridge" -version = "2.1.0" +dynamic = ["version"] description = "MCP server bridging Claude Code to Unreal Engine 5 via Remote Control API" readme = "README.md" requires-python = ">=3.11" @@ -27,6 +27,10 @@ usd = [ [project.scripts] ue-mcp = "ue_mcp.mcp_server:main" +[tool.hatch.version] +# Single version source; the tag gate is `git tag vX.Y.Z` == this value. +path = "ue_mcp/__version__.py" + [tool.hatch.build.targets.wheel] # Flat layout: the distribution name (ue-bridge) has no matching directory, so the # first-party packages must be named explicitly — newer hatchling no longer diff --git a/ue_mcp/__version__.py b/ue_mcp/__version__.py index bc0f8ee..180684e 100644 --- a/ue_mcp/__version__.py +++ b/ue_mcp/__version__.py @@ -1,4 +1,9 @@ -"""Version metadata for ue-bridge MCP server.""" +"""Version metadata for ue-bridge MCP server. -__version__ = "2.1.0" -__version_info__ = (2, 1, 0) +Single source of truth: pyproject reads it via hatchling; git tags must match +(v0.2.0 <-> 0.2.0). The public line continues from v0.1.1 — the internal 2.x +numbering was never released and was retired at the Epic-MCP-era reset. +""" + +__version__ = "0.2.0" +__version_info__ = (0, 2, 0) From 33f322666a3776f7a2f189e48298a03787e1fbcc Mon Sep 17 00:00:00 2001 From: Joseph Ibrahim Date: Thu, 2 Jul 2026 13:09:16 -0400 Subject: [PATCH 2/2] =?UTF-8?q?feat(plugins):=20FAB=20marketplace-ready=20?= =?UTF-8?q?=E2=80=94=20icons,=20metadata,=205.8,=20strict=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both plugins now package cleanly via RunUAT BuildPlugin (installed/ marketplace-strict) and install engine-wide, so they appear in Edit -> Plugins for any UE 5.8 project. Icons & metadata: - Real branded 128x128 Icon128.png for both (bridge motif / perception eye; brand palette). UEBridge's was a 650-byte stub; ViewportPerception had none. - Storefront-grade .uplugin metadata: Category "Code Plugins", FriendlyName, Description, CreatedBy/URL, DocsURL, SupportURL, EngineVersion 5.8.0. - docs/FAB_LISTING.md: storefront copy + FAB technical fields. Strict-build fixes (latent bugs the project's unity build masked; a non-unity installed build exposes them): - PerceptionTypes.h: explicit Category on every Blueprint-exposed UPROPERTY (required for engine-module distribution). - UEBridgeSubsystem.h: forward-declare FJsonObject (header was not self-contained; pulled in transitively only under unity). - UEBridgeSubsystem.cpp: FEngineVersion::Current().ToString() replaces the transitive ENGINE_VERSION_STRING macro; include Misc/EngineVersion.h. - UEBridgeRuntime.Build.cs: drop the removed Public/UI + Private/UI include paths (decruft leftover; fatal under -WarningsAsErrors); BRIDGE_VERSION 0.2.0. 5.8 retarget (completes M5; verified: both plugins compile clean on 5.8): - .uproject EngineAssociation 5.8; README badge 5.8; CHANGELOG engine section. - (FrameProducer 5.8 delegate port shipped in the prior release commit.) 574 tests still green. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 13 ++ Plugins/UEBridge/Config/FilterPlugin.ini | 8 ++ Plugins/UEBridge/Resources/Icon128.png | Bin 650 -> 10264 bytes .../Private/UEBridgeSubsystem.cpp | 5 +- .../Public/UEBridgeSubsystem.h | 5 + .../UEBridgeRuntime/UEBridgeRuntime.Build.cs | 15 +-- Plugins/UEBridge/UEBridge.uplugin | 15 ++- .../Config/FilterPlugin.ini | 8 ++ .../ViewportPerception/Resources/Icon128.png | Bin 0 -> 14494 bytes .../Public/PerceptionTypes.h | 36 +++--- .../ViewportPerception.uplugin | 21 ++- README.md | 2 +- UnrealEngine_Bridge.uproject | 2 +- docs/FAB_LISTING.md | 122 ++++++++++++++++++ 14 files changed, 205 insertions(+), 47 deletions(-) create mode 100644 Plugins/UEBridge/Config/FilterPlugin.ini create mode 100644 Plugins/ViewportPerception/Config/FilterPlugin.ini create mode 100644 Plugins/ViewportPerception/Resources/Icon128.png create mode 100644 docs/FAB_LISTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7610db9..e3a1320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,19 @@ ship, with every decision grounded in a live probe of the real surface. repo-root runner. The `usd_bridge/` package is parked in-repo, out of the ship path. +### Engine & packaging (UE 5.8) +- **Retargeted to UE 5.8** (`EngineAssociation` 5.8; both plugins + `EngineVersion` 5.8.0). The `ViewportPerception` `FrameProducer` was ported + for 5.8's changed `OnBackBufferReadyToPresent` signature (`FTextureRHIRef` + → `ISlateViewportProvider&`) behind a dual-version `#if`, so it still + compiles against 5.7. Verified: the editor target builds clean on 5.8. +- **Marketplace-ready plugins**: branded 128×128 icons, storefront-grade + `.uplugin` metadata, explicit `Category` specifiers on every + Blueprint-exposed property (required for engine-module distribution), and a + clean `RunUAT BuildPlugin -installed` package. Draft storefront copy in + `docs/FAB_LISTING.md`. The plugins can now be installed engine-wide and + picked from Edit → Plugins in any 5.8 project. + ## [0.1.0] - 2026-05-30 First tagged release. Demo-ready on a maintainer's machine; see **Status** below for what stands between this and a distributable plugin. diff --git a/Plugins/UEBridge/Config/FilterPlugin.ini b/Plugins/UEBridge/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/Plugins/UEBridge/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/Plugins/UEBridge/Resources/Icon128.png b/Plugins/UEBridge/Resources/Icon128.png index 62545c25a84b52db37f43f3d6a6687dd281a354a..541cf842d8b01a3eb8c160f08c77716add2cecb6 100644 GIT binary patch literal 10264 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 diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp b/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp index f94f3c0..1e3858f 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp @@ -14,6 +14,7 @@ #include "Serialization/JsonSerializer.h" #include "Internationalization/Regex.h" #include "Engine/Engine.h" +#include "Misc/EngineVersion.h" // === LIFECYCLE === @@ -147,7 +148,7 @@ void UUEBridgeSubsystem::SendAcknowledge() Content = UpdateUsdaVariant(Content, TEXT("message_type"), TEXT("ack")); Content = UpdateUsdaAttribute(Content, TEXT("Ack"), TEXT("ready"), TEXT("true"), false); - Content = UpdateUsdaAttribute(Content, TEXT("Ack"), TEXT("ue_version"), FString(ENGINE_VERSION_STRING), true); + Content = UpdateUsdaAttribute(Content, TEXT("Ack"), TEXT("ue_version"), FEngineVersion::Current().ToString(), true); Content = UpdateUsdaAttribute(Content, TEXT("Ack"), TEXT("project"), TEXT("UnrealEngineBridge"), true); Content = UpdateUsdaAttribute(Content, TEXT("Ack"), TEXT("timestamp"), Timestamp, true); @@ -167,7 +168,7 @@ void UUEBridgeSubsystem::SendAcknowledge() TSharedPtr AckObj = MakeShared(); AckObj->SetBoolField(TEXT("ready"), true); - AckObj->SetStringField(TEXT("ue_version"), FString(ENGINE_VERSION_STRING)); + AckObj->SetStringField(TEXT("ue_version"), FEngineVersion::Current().ToString()); AckObj->SetStringField(TEXT("project"), TEXT("UnrealEngineBridge")); JsonObj->SetObjectField(TEXT("ack"), AckObj); diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h index cb1e45a..12a8fc8 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h @@ -13,6 +13,11 @@ #include "BridgeTypes.h" #include "UEBridgeSubsystem.generated.h" +// Used by reference only in method signatures below; forward-declared so the +// header is self-contained under non-unity / installed-plugin builds (a unity +// build would otherwise pull FJsonObject in transitively). +class FJsonObject; + UCLASS() class UEBRIDGERUNTIME_API UUEBridgeSubsystem : public UGameInstanceSubsystem diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/UEBridgeRuntime.Build.cs b/Plugins/UEBridge/Source/UEBridgeRuntime/UEBridgeRuntime.Build.cs index b1cdf93..f90e709 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/UEBridgeRuntime.Build.cs +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/UEBridgeRuntime.Build.cs @@ -11,17 +11,8 @@ public UEBridgeRuntime(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicIncludePaths.AddRange(new string[] - { - Path.Combine(ModuleDirectory, "Public"), - Path.Combine(ModuleDirectory, "Public", "UI") - }); - - PrivateIncludePaths.AddRange(new string[] - { - Path.Combine(ModuleDirectory, "Private"), - Path.Combine(ModuleDirectory, "Private", "UI") - }); + PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "Public")); + PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "Private")); // Core runtime dependencies — ship in packaged builds PublicDependencyModuleNames.AddRange(new string[] @@ -50,7 +41,7 @@ public UEBridgeRuntime(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.Add("Projects"); // Version definition — single source of truth - PublicDefinitions.Add("BRIDGE_VERSION=TEXT(\"2.1.0\")"); + PublicDefinitions.Add("BRIDGE_VERSION=TEXT(\"0.2.0\")"); // USD support flag: editor-only via pxr, runtime uses text-based USDA parser if (Target.bBuildEditor) diff --git a/Plugins/UEBridge/UEBridge.uplugin b/Plugins/UEBridge/UEBridge.uplugin index 6c96e2f..19f74fd 100644 --- a/Plugins/UEBridge/UEBridge.uplugin +++ b/Plugins/UEBridge/UEBridge.uplugin @@ -2,10 +2,10 @@ "FileVersion": 3, "Version": 1, "VersionName": "0.2.0", - "EngineVersion": "5.7.0", - "FriendlyName": "UE Bridge", - "Description": "Agentic AI bridge for Unreal Engine. Provides MCP tool integration, file-based bridge protocol with USD transport, behavioral signal collection, and Remote Control API access for Claude Code.", - "Category": "Gameplay", + "EngineVersion": "5.8.0", + "FriendlyName": "UE Bridge — AI Editor Control", + "Description": "Give Claude Code and other MCP clients editor-side control of Unreal Engine: an in-editor status panel, a directory-watch bridge, and Remote Control API access. Pairs with the UnrealEngine Bridge MCP server for AI-driven scene building, lighting, spatial reasoning, and viewport perception.", + "Category": "Code Plugins", "CreatedBy": "Joseph Ibrahim", "CreatedByURL": "https://github.com/JosephOIbrahim", "DocsURL": "https://github.com/JosephOIbrahim/UnrealEngine_Bridge#readme", @@ -14,18 +14,21 @@ "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, + "Installed": false, "EnabledByDefault": false, "SupportedTargetPlatforms": ["Win64"], "Modules": [ { "Name": "UEBridgeRuntime", "Type": "Runtime", - "LoadingPhase": "Default" + "LoadingPhase": "Default", + "WhitelistPlatforms": ["Win64"] }, { "Name": "UEBridgeEditor", "Type": "Editor", - "LoadingPhase": "Default" + "LoadingPhase": "Default", + "WhitelistPlatforms": ["Win64"] } ], "Plugins": [ diff --git a/Plugins/ViewportPerception/Config/FilterPlugin.ini b/Plugins/ViewportPerception/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/Plugins/ViewportPerception/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/Plugins/ViewportPerception/Resources/Icon128.png b/Plugins/ViewportPerception/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..5d72a447df28e8a53c367f01bcc54e029740620a GIT binary patch literal 14494 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 wfbCLZ1AspQc*1ov&jQFtE* SelectedActors; - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") FString MapName; - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") int32 ActorCount = 0; // Timing - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") float DeltaTime = 0.0f; - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") float FPS = 0.0f; }; @@ -76,29 +76,29 @@ struct FPerceptionPacket TArray ImageData; /** Image dimensions after resize/encode. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") int32 Width = 0; - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") int32 Height = 0; /** Format used for encoding. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") EPerceptionImageFormat Format = EPerceptionImageFormat::JPEG; /** Monotonically increasing frame counter. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") int64 FrameNumber = 0; /** Platform time at capture. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") double Timestamp = 0.0; /** Scene metadata collected at capture time. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") FPerceptionMetadata Metadata; /** True if this packet contains valid data. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY(BlueprintReadOnly, Category = "Perception") bool bValid = false; }; diff --git a/Plugins/ViewportPerception/ViewportPerception.uplugin b/Plugins/ViewportPerception/ViewportPerception.uplugin index 8b344c6..069f4c5 100644 --- a/Plugins/ViewportPerception/ViewportPerception.uplugin +++ b/Plugins/ViewportPerception/ViewportPerception.uplugin @@ -2,20 +2,27 @@ "FileVersion": 3, "Version": 1, "VersionName": "0.2.0", - "EngineVersion": "5.7.0", - "FriendlyName": "Viewport Perception", - "Description": "AI visual awareness for UE5 — captures the editor viewport and serves perception packets (frame + metadata) to the MCP bridge.", - "Category": "Editor", + "EngineVersion": "5.8.0", + "FriendlyName": "Viewport Perception — AI Visual Awareness", + "Description": "Gives AI assistants eyes inside the Unreal editor. Captures the level viewport on the render thread via async GPU readback and serves perception packets (encoded frame + camera, selection, and scene metadata) over a localhost HTTP endpoint for the UnrealEngine Bridge MCP server.", + "Category": "Code Plugins", "CreatedBy": "Joseph Ibrahim", + "CreatedByURL": "https://github.com/JosephOIbrahim", + "DocsURL": "https://github.com/JosephOIbrahim/UnrealEngine_Bridge#readme", + "MarketplaceURL": "", + "SupportURL": "https://github.com/JosephOIbrahim/UnrealEngine_Bridge/issues", "CanContainContent": false, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, "EnabledByDefault": false, - "EditorCustomVirtualPath": "", - "SupportedTargetPlatforms": [ "Win64" ], + "SupportedTargetPlatforms": ["Win64"], "Modules": [ { "Name": "ViewportPerception", "Type": "EditorNoCommandlet", - "LoadingPhase": "PostEngineInit" + "LoadingPhase": "PostEngineInit", + "WhitelistPlatforms": ["Win64"] } ], "Plugins": [ diff --git a/README.md b/README.md index 83774dd..c54e333 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Release](https://img.shields.io/github/v/release/JosephOIbrahim/UnrealEngine_Bridge?sort=semver)](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/releases/latest) [![CI](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/JosephOIbrahim/UnrealEngine_Bridge/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Unreal Engine 5.7 · 5.8-ready](https://img.shields.io/badge/Unreal%20Engine-5.7%20·%205.8--ready-0a0a14)](https://www.unrealengine.com/) +[![Unreal Engine 5.8](https://img.shields.io/badge/Unreal%20Engine-5.8-0a0a14)](https://www.unrealengine.com/) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white)](https://www.python.org/) **Claude Code, working inside your Unreal editor.** This bridge gives Claude the abilities Epic's own MCP doesn't ship: run real editor Python, see the viewport continuously, light scenes with one command, reason about space with surface normals, and stay honest about every result. diff --git a/UnrealEngine_Bridge.uproject b/UnrealEngine_Bridge.uproject index 1c7d264..2e223d1 100644 --- a/UnrealEngine_Bridge.uproject +++ b/UnrealEngine_Bridge.uproject @@ -1,6 +1,6 @@ { "FileVersion": 3, - "EngineAssociation": "5.7", + "EngineAssociation": "5.8", "Category": "Games", "Description": "UnrealEngine Bridge - Agentic AI bridge connecting Claude Code to Unreal Engine via MCP tools, Remote Control API, and USD transport", "Modules": [ diff --git a/docs/FAB_LISTING.md b/docs/FAB_LISTING.md new file mode 100644 index 0000000..36b449e --- /dev/null +++ b/docs/FAB_LISTING.md @@ -0,0 +1,122 @@ +# FAB Marketplace Listing — UnrealEngine Bridge + +Draft storefront copy + the technical fields FAB requires for a code plugin. +The plugin package (icon, `.uplugin` metadata, compiled binaries) is separate; +this is the listing that wraps it. + +--- + +## Product name + +**UnrealEngine Bridge — AI Editor Control (Claude / MCP)** + +## Tagline (≤ 80 chars) + +Let Claude Code drive your editor — the AI abilities Epic's MCP doesn't ship. + +## Category + +Code Plugins → Editor / Utility + +## Price + +Suggested: free or low-cost dev tool (the value is the workflow, not the C++). +The Python MCP server is MIT on GitHub; the marketplace product is the +signed, precompiled, one-click editor plugins. + +--- + +## Short description (≤ 160 chars) + +Two editor plugins that give Claude Code eyes and hands in Unreal 5.8: +viewport perception + a Remote-Control bridge. Pairs with the open-source MCP server. + +## Full description + +Unreal Engine 5.8 ships Epic's official **Unreal MCP** for the commodity control +plane — spawn, transform, materials, Blueprints. **UnrealEngine Bridge is the +other half**: the differentiated AI abilities Epic's server deliberately does +not expose. + +Install these two editor plugins, run the open-source MCP server, and Claude +Code (or any MCP client) can: + +- **See the viewport** — continuous, render-thread GPU-readback capture served + as perception packets (frame + camera + selection + scene metadata). Single + frames, continuous watch, and structural before/after diffs. Epic's MCP has + no viewport capture at all. +- **Run real editor Python** — the full `unreal` API, not a sandboxed + tool-script runner. +- **Light a scene by mood** — one command sets a coordinated sun + fog + clouds + + colour-grade package, or blends two looks. +- **Reason about space with surface normals** — ground traces that return the + hit point, normal, distance, and actor; snap-to-slope placement. +- **Stay honest** — every tool's result is enforced by an exec-simulated test + suite; tools report real status, never a hard-coded success. + +The bridge coexists cleanly with Epic's official MCP: Epic handles the basics, +this handles the rest, and both run against the same editor. + +### What you get (the plugins) + +| Plugin | What it does | +|---|---| +| **UE Bridge** | In-editor status panel, directory-watch bridge, Remote Control access — the editor-side control surface. | +| **Viewport Perception** | Render-thread viewport capture over a localhost HTTP endpoint — the AI's "eyes." | + +### What you also need (disclosed up front — required by FAB) + +This is an **editor-integration + AI-tooling** product, not a self-contained +runtime feature. To use it you also need, all free: + +- The **UnrealEngine Bridge MCP server** (open-source, MIT) — a Python process + the AI client launches. `pip install` from the linked GitHub repo. +- An **MCP client** — Claude Code, Claude Desktop, Cursor, etc. +- The engine's built-in **Remote Control** plugin (enable it; ships with UE). + +The plugins do nothing at runtime in a packaged game — they are **editor-only +developer tooling**. + +--- + +## Technical details (FAB fields) + +- **Code Modules:** + - `UEBridgeRuntime` (Runtime) + - `UEBridgeEditor` (Editor) + - `ViewportPerception` (EditorNoCommandlet) +- **Number of Blueprints:** 0 +- **Number of C++ Classes:** ~12 (subsystems, HTTP endpoint, frame producer, pixel bus, types) +- **Network Replicated:** No +- **Supported Development Platforms:** Windows +- **Supported Target Build Platforms:** Windows (Win64) — editor-only +- **Engine Version:** 5.8 +- **Documentation:** https://github.com/JosephOIbrahim/UnrealEngine_Bridge#readme +- **Example / setup guide:** README + `docs/EPIC_MCP_MATRIX.md` (what this adds over Epic's MCP) +- **Support:** https://github.com/JosephOIbrahim/UnrealEngine_Bridge/issues + +### Important notes for reviewers + +- Both plugins are **editor-only** (`Editor` / `EditorNoCommandlet` module + types); nothing loads in a cooked build. +- Two **localhost** HTTP surfaces (Remote Control `:30010`, Viewport + Perception `:30011`) — single-operator dev trust model, not for untrusted + networks. Viewport Perception is opt-in. See `SECURITY.md`. +- No third-party binaries; no external runtime dependencies beyond the + engine's own Remote Control + (optional) Python Script plugins. + +--- + +## Gallery assets to produce (human step) + +FAB needs storefront imagery the package can't carry: + +1. **Featured image** 1920×1080 — the two-server architecture diagram + a + "Claude driving the editor" screenshot. +2. **Screenshots** (≥ 5, 1920×1080): the in-editor UE Bridge panel; a + perception frame Claude captured; a mood-preset before/after; a + ground-trace/snap demo; the health-check output showing both servers. +3. **Thumbnail** 512×512 — the plugin icon on the brand background. + +The plugin `Icon128.png` (shipped in each plugin's `Resources/`) is the +in-editor browser icon, not the storefront thumbnail.