Skip to content

Commit 98589b4

Browse files
committed
Add reflection-driven actor instance control to the editor MCP
Before this change, manipulating actors in a level was tightly limited. spawn_actor only accepted a hardcoded handful of types — StaticMeshActor, PointLight, a few others — and there was no way to write arbitrary properties to a placed instance. set_blueprint_class_defaults hits the CDO, not the placed actor, so per-instance edits were unreachable. PostProcessVolume settings, SkyAtmosphere parameters, and any project-specific actor's fields all sat behind that wall. This change opens it up properly. Five tools land here, all driven by Unreal's reflection system so they work with engine classes, plugin classes, and any USTRUCT or UPROPERTY a project defines without further code on our side. set_actor_property writes to the live placed actor or one of its components. It walks dotted paths like "Settings.BloomIntensity" or "Settings.LensFlareTints[3]", handling structs, dynamic arrays, sets, and C-style static arrays through Field[N] syntax. PostEditChangeProperty fires on the right top-level FProperty so the renderer reacts. Modify() registers with the transaction system so Ctrl+Z works. JSON objects, UE text-format strings, asset paths for object refs, enum names — all accepted and all routed through one universal dispatcher (SetPropertyValueAtAddr) that replaced about 140 lines of duplicated type handling. SetProperty, SetPropertyAtPath, and the struct-field walker now share that single path. spawn_actor_by_class takes any UClass path, Blueprint asset path, or short class name, validates that it's a placeable AActor subclass, and spawns through UEditorActorSubsystem::SpawnActorFromClass so undo works out of the box. get_actor_property_metadata is the type-discovery companion. It returns the cpp_type, ue_type, category, display_name, tooltip, current value, clamp_min/max, ui_min/max, valid_values for enums, inner/element/key/ value types for containers, and the inheritance chain. Callers — AI agents in particular — can now know exactly what they're allowed to write before they try. To keep responses sane on heavy classes (a UPointLightComponent has ~200 inherited FProperties), paths landing on FObjectProperty no longer auto-descend into the target's class. They return a single ref entry plus a hint pointing at the existing component_name parameter. When descent is genuinely wanted, descend_into_objects opts in. Every metadata response carries a _summary header with total_available, total_returned, truncated, next_cursor, own_count, inherited_count, the full class_chain, and a UPROPERTY(Category) breakdown. A single max_entries=0 call gives a complete map of what's available without paying for any of the body. max_entries and cursor handle pagination the same way find_actors already does. find_actors searches the level by name pattern, outliner label, class filter (resolves to a UClass for IsA, falls back to substring on the class name and path), tag, exact_class, or any combination. Reports total_scanned and total_matched independently of result truncation, so you know whether to widen max_results. delete_actor now matches both the outliner label and the internal UObject name, calls Modify() on actor and level before destroy, and prefers UEditorActorSubsystem::DestroyActor so deletions land in the undo stack like any other editor action. get_actor_properties picks up a flat dotted-path mode with the same filter, depth, category, and pagination story as the metadata tool. A query like "everything containing 'bloom' on this PostProcessVolume" actually finds Settings.BloomIntensity, not just top-level FProperty names. The original nested mode is still the default, unchanged. A second non-obvious fix tucked into this change: SetStructFieldsFromJson used to silently ignore unknown JSON keys. A typo like {"R","Q","B","A"} on an FLinearColor returned success but wrote nothing. Now those errors propagate as "[field 'Q' not found on struct 'LinearColor']" and the whole write fails — which is the right answer for a tool that other tools build on. Engine APIs used throughout, all matching the patterns Epic uses internally: UEditorActorSubsystem for spawn and destroy with transaction support; Actor->Modify() and Level->Modify() before mutation; FProperty's ContainerPtrToValuePtr with the index parameter for static C array addressing; TFieldIterator with IncludeSuper/ExcludeSuper for inheritance control; FProperty::GetMetaData and GetOwnerClass for category and ownership tracking; UEnum::NumEnums()-1 paired with HasMetaData("Hidden", i) to list valid enum entries the same way the Details panel does. Python tools (unrealmcp/tools/level.py) and the Go CLI (cli/cmd/editor.go) both pick up matching wrappers with documented parameters and worked examples. Files touched: Source/UnrealMCPBridge/Public/Commands/EpicUnrealMCPPropertyUtils.h Source/UnrealMCPBridge/Private/Commands/EpicUnrealMCPPropertyUtils.cpp Source/UnrealMCPBridge/Public/Commands/EpicUnrealMCPEditorCommands.h Source/UnrealMCPBridge/Private/Commands/EpicUnrealMCPEditorCommands.cpp Source/UnrealMCPBridge/Private/EpicUnrealMCPBridge.cpp unrealmcp/tools/level.py cli/cmd/editor.go
1 parent 325f270 commit 98589b4

7 files changed

Lines changed: 1766 additions & 173 deletions

File tree

0 commit comments

Comments
 (0)