ue-mcp 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +4 -3
  2. package/dist/action-class.js +56 -3
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/bridge-timeouts.d.ts +64 -0
  5. package/dist/bridge-timeouts.js +118 -0
  6. package/dist/bridge-timeouts.js.map +1 -0
  7. package/dist/bridge.js +10 -2
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/editor-control.d.ts +72 -5
  10. package/dist/editor-control.js +164 -140
  11. package/dist/editor-control.js.map +1 -1
  12. package/dist/editor-target.d.ts +55 -1
  13. package/dist/editor-target.js +107 -10
  14. package/dist/editor-target.js.map +1 -1
  15. package/dist/engine-observer.d.ts +59 -5
  16. package/dist/engine-observer.js +249 -29
  17. package/dist/engine-observer.js.map +1 -1
  18. package/dist/engine-root.d.ts +91 -0
  19. package/dist/engine-root.js +448 -0
  20. package/dist/engine-root.js.map +1 -0
  21. package/dist/epic-enrich.js +16 -1
  22. package/dist/epic-enrich.js.map +1 -1
  23. package/dist/feedback-cli.js +4 -0
  24. package/dist/feedback-cli.js.map +1 -1
  25. package/dist/feedback-deferred.d.ts +9 -0
  26. package/dist/feedback-deferred.js +2 -0
  27. package/dist/feedback-deferred.js.map +1 -1
  28. package/dist/feedback-fallback.d.ts +99 -0
  29. package/dist/feedback-fallback.js +173 -0
  30. package/dist/feedback-fallback.js.map +1 -0
  31. package/dist/flow/write-methods.js +20 -0
  32. package/dist/flow/write-methods.js.map +1 -1
  33. package/dist/index.js +41 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lean-context.js +8 -3
  36. package/dist/lean-context.js.map +1 -1
  37. package/dist/locking.js +11 -2
  38. package/dist/locking.js.map +1 -1
  39. package/dist/plugin/loader.d.ts +4 -0
  40. package/dist/plugin/loader.js +22 -8
  41. package/dist/plugin/loader.js.map +1 -1
  42. package/dist/plugin/manifest.d.ts +72 -52
  43. package/dist/plugin/manifest.js +118 -21
  44. package/dist/plugin/manifest.js.map +1 -1
  45. package/dist/plugin-cli.js +16 -3
  46. package/dist/plugin-cli.js.map +1 -1
  47. package/dist/project.d.ts +11 -0
  48. package/dist/project.js +23 -2
  49. package/dist/project.js.map +1 -1
  50. package/dist/python-gate.d.ts +95 -0
  51. package/dist/python-gate.js +175 -0
  52. package/dist/python-gate.js.map +1 -0
  53. package/dist/tool-counts.json +16 -16
  54. package/dist/tools/animation.js +288 -24
  55. package/dist/tools/animation.js.map +1 -1
  56. package/dist/tools/asset.js +79 -14
  57. package/dist/tools/asset.js.map +1 -1
  58. package/dist/tools/audio.js +59 -5
  59. package/dist/tools/audio.js.map +1 -1
  60. package/dist/tools/blueprint.js +29 -12
  61. package/dist/tools/blueprint.js.map +1 -1
  62. package/dist/tools/chooser.js +4 -1
  63. package/dist/tools/chooser.js.map +1 -1
  64. package/dist/tools/editor.js +81 -43
  65. package/dist/tools/editor.js.map +1 -1
  66. package/dist/tools/feedback.js +214 -48
  67. package/dist/tools/feedback.js.map +1 -1
  68. package/dist/tools/foliage.js +27 -1
  69. package/dist/tools/foliage.js.map +1 -1
  70. package/dist/tools/gameplay.js +38 -11
  71. package/dist/tools/gameplay.js.map +1 -1
  72. package/dist/tools/gas.js +17 -5
  73. package/dist/tools/gas.js.map +1 -1
  74. package/dist/tools/landscape.js +22 -7
  75. package/dist/tools/landscape.js.map +1 -1
  76. package/dist/tools/level.js +322 -51
  77. package/dist/tools/level.js.map +1 -1
  78. package/dist/tools/material.js +10 -4
  79. package/dist/tools/material.js.map +1 -1
  80. package/dist/tools/niagara.js +3 -2
  81. package/dist/tools/niagara.js.map +1 -1
  82. package/dist/tools/pcg.js +6 -5
  83. package/dist/tools/pcg.js.map +1 -1
  84. package/dist/tools/plugins.js +2 -0
  85. package/dist/tools/plugins.js.map +1 -1
  86. package/dist/tools/project.js +47 -20
  87. package/dist/tools/project.js.map +1 -1
  88. package/dist/types.d.ts +24 -0
  89. package/dist/types.js +46 -3
  90. package/dist/types.js.map +1 -1
  91. package/dist/ue-mcp.default.yml +493 -125
  92. package/package.json +2 -2
  93. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  94. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  95. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  242. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  243. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  244. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  245. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  246. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  247. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  248. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  249. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  250. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  251. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  252. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  253. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  254. package/skills/ue-mcp-animation/SKILL.md +154 -0
  255. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -9,6 +9,7 @@
9
9
  #include "HandlerUtils.h"
10
10
 
11
11
  #include "UObject/GCObjectScopeGuard.h"
12
+ #include "HandlerFunctionCall.h"
12
13
  #include "HandlerJsonProperty.h"
13
14
  #include "JsonSerializer.h"
14
15
  #include "Containers/Ticker.h"
@@ -386,6 +387,12 @@ TSharedPtr<FJsonValue> FEditorHandlers::PieGetRuntimeValue(const TSharedPtr<FJso
386
387
  return MCPError(TEXT("PIE is not active. Start a PIE session first."));
387
388
  }
388
389
 
390
+ // This action shipped before #983 with 'actorPath' as a label|name|path
391
+ // token, not a strict path, and with 'actorLabel' as its fallback spelling.
392
+ // The shared resolver treats a path as precise, so the loose reading is
393
+ // kept here explicitly rather than silently dropped: a caller who has been
394
+ // passing a label in 'actorPath' for two releases must not start getting
395
+ // "no actor at that path".
389
396
  FString ActorPath;
390
397
  if (!Params->TryGetStringField(TEXT("actorPath"), ActorPath))
391
398
  {
@@ -401,8 +408,25 @@ TSharedPtr<FJsonValue> FEditorHandlers::PieGetRuntimeValue(const TSharedPtr<FJso
401
408
 
402
409
  // Search for the actor in the PIE world (accept label, name, or full path).
403
410
  // #778: honour pieInstance so a client world is reachable.
411
+ // #983: through the shared resolver, so actorPath wins over actorLabel and
412
+ // a duplicated label is refused rather than read off one of the copies.
404
413
  UWorld* PIEWorld = ResolveWorldFromParams(Params, TEXT("pie"));
405
- AActor* TargetActor = FindActorByLabelNameOrPath(PIEWorld, ActorPath);
414
+ FMCPActorSelector PieSel;
415
+ PieSel.Match = EMCPActorMatch::LabelNameOrPath;
416
+ PieSel.WorldLabel = TEXT("PIE");
417
+ TSharedPtr<FJsonValue> PieActorErr;
418
+ AActor* TargetActor = MCPResolveActor(PIEWorld, Params, PieActorErr, PieSel);
419
+ if (!TargetActor && MCPIsAmbiguousActorError(PieActorErr)) return PieActorErr;
420
+
421
+ // The legacy tolerance: retry the value of 'actorPath' as a label / name
422
+ // token. Ambiguity is still refused, so the loose reading cannot bring the
423
+ // silent wrong pick back with it.
424
+ if (!TargetActor && !ActorPath.IsEmpty())
425
+ {
426
+ TSharedPtr<FJsonValue> LegacyErr;
427
+ TargetActor = MCPResolveActorToken(PIEWorld, ActorPath, LegacyErr, PieSel);
428
+ if (!TargetActor && MCPIsAmbiguousActorError(LegacyErr)) return LegacyErr;
429
+ }
406
430
 
407
431
  if (!TargetActor)
408
432
  {
@@ -656,19 +680,127 @@ namespace
656
680
  Out->SetObjectField(Field, O); return true;
657
681
  }
658
682
  }
683
+ // #885: a container is real JSON here too. The export-text fallback
684
+ // below renders a TArray<FString> as an empty string, so a path whose
685
+ // leaf is an array read back as though it held nothing.
686
+ if (MCPFunctionCall::IsContainerProperty(Prop))
687
+ {
688
+ Out->SetField(Field, MCPFunctionCall::ValueToJson(Prop, ValuePtr, nullptr));
689
+ return true;
690
+ }
659
691
  FString Exported;
660
692
  Prop->ExportTextItem_Direct(Exported, ValuePtr, nullptr, nullptr, PPF_None);
661
693
  Out->SetStringField(Field, Exported);
662
694
  return true;
663
695
  }
664
696
 
697
+ /**
698
+ * #969: evaluate a path segment that resolved to a UFUNCTION, binding the
699
+ * literal arguments the segment carried.
700
+ *
701
+ * A read-only accessor keyed by an id (a tally by option id, a balance by
702
+ * currency id, an attribute by tag) is a very common shape and used to be
703
+ * unreachable here: anything taking a parameter was refused, and the
704
+ * workaround was one invoke_object_function per key with an exact
705
+ * objectPath, which loses the multi-instance table this action exists for.
706
+ *
707
+ * The literals are coerced by MCPJsonProperty::SetJsonOnProperty, the same
708
+ * setter invoke_object_function's `args` go through. There is deliberately
709
+ * no second coercion: a string reads into an FName, an FString, an integer,
710
+ * a float, a bool or an enum there already, so a keyed accessor behaves the
711
+ * same whichever action reaches it.
712
+ */
713
+ static bool CallPathGetter(
714
+ UObject* Target,
715
+ UFunction* Fn,
716
+ const TArray<FString>& ArgLiterals,
717
+ TSharedPtr<FJsonObject> Out,
718
+ const TCHAR* FieldKey,
719
+ FString& OutErr)
720
+ {
721
+ FProperty* RetProp = Fn->GetReturnProperty();
722
+ if (!RetProp)
723
+ {
724
+ OutErr = FString::Printf(TEXT("UFUNCTION '%s' has no return value"), *Fn->GetName());
725
+ return false;
726
+ }
727
+
728
+ // Inputs are the parameters a caller can supply: not the return, and not
729
+ // a plain out param, which the function writes rather than reads.
730
+ TArray<FProperty*> Inputs;
731
+ for (TFieldIterator<FProperty> It(Fn); It && (It->PropertyFlags & CPF_Parm); ++It)
732
+ {
733
+ FProperty* Parm = *It;
734
+ if (Parm->PropertyFlags & CPF_ReturnParm) continue;
735
+ if ((Parm->PropertyFlags & CPF_OutParm) && !(Parm->PropertyFlags & CPF_ReferenceParm)) continue;
736
+ Inputs.Add(Parm);
737
+ }
738
+
739
+ if (ArgLiterals.Num() != Inputs.Num())
740
+ {
741
+ TArray<FString> Signature;
742
+ for (FProperty* Parm : Inputs)
743
+ {
744
+ Signature.Add(FString::Printf(TEXT("%s %s"), *Parm->GetCPPType(), *Parm->GetName()));
745
+ }
746
+ OutErr = FString::Printf(
747
+ TEXT("UFUNCTION '%s' takes %d argument(s) (%s) but the path supplied %d. Write them into the path, e.g. '%s(key)'."),
748
+ *Fn->GetName(), Inputs.Num(), *FString::Join(Signature, TEXT(", ")), ArgLiterals.Num(), *Fn->GetName());
749
+ return false;
750
+ }
751
+
752
+ // ParmsSize, not PropertiesSize: a Blueprint function's locals live past
753
+ // the parameter block and initialising them would run off the frame.
754
+ TArray<uint8> Frame;
755
+ Frame.SetNumZeroed(Fn->ParmsSize);
756
+ for (TFieldIterator<FProperty> It(Fn); It && (It->PropertyFlags & CPF_Parm); ++It)
757
+ {
758
+ It->InitializeValue_InContainer(Frame.GetData());
759
+ }
760
+
761
+ bool bBound = true;
762
+ for (int32 ArgIndex = 0; ArgIndex < Inputs.Num(); ++ArgIndex)
763
+ {
764
+ FProperty* Parm = Inputs[ArgIndex];
765
+ const TSharedPtr<FJsonValue> AsJson = MakeShared<FJsonValueString>(ArgLiterals[ArgIndex]);
766
+ FString BindErr;
767
+ if (!MCPJsonProperty::SetJsonOnProperty(
768
+ Parm, Parm->ContainerPtrToValuePtr<void>(Frame.GetData()), AsJson, BindErr))
769
+ {
770
+ OutErr = FString::Printf(
771
+ TEXT("UFUNCTION '%s' argument '%s': %s"), *Fn->GetName(), *Parm->GetName(), *BindErr);
772
+ bBound = false;
773
+ break;
774
+ }
775
+ }
776
+
777
+ if (bBound)
778
+ {
779
+ {
780
+ // #806: without this guard an actor getter called against the
781
+ // editor world is skipped and the zeroed frame reads back as a
782
+ // real value.
783
+ FEditorScriptExecutionGuard ScriptGuard;
784
+ Target->ProcessEvent(Fn, Frame.GetData());
785
+ }
786
+ WritePropertyValue(Out, FieldKey, RetProp, RetProp->ContainerPtrToValuePtr<void>(Frame.GetData()));
787
+ }
788
+
789
+ MCPFunctionCall::DestroyFrame(Fn, Frame.GetData());
790
+ return bBound;
791
+ }
792
+
665
793
  // Walk one dotted path starting at Root. Per segment: property hop, sub-object
666
- // hop, or - at the leaf - a zero-arg UFUNCTION call. Writes the result onto Out.
794
+ // hop, or - at the leaf - a UFUNCTION call, which may carry literal arguments
795
+ // as 'GetTallyWeight(overclock)'. Writes the result onto Out.
667
796
  static void ResolvePath(UObject* Root, const FString& Path, TSharedPtr<FJsonObject> Out, const TCHAR* FieldKey, FString& OutErr)
668
797
  {
669
798
  if (!Root) { OutErr = TEXT("null root"); return; }
670
799
  TArray<FString> Parts;
671
- Path.ParseIntoArray(Parts, TEXT("."));
800
+ // Split on dots that separate segments, not on a dot inside an argument
801
+ // list: 'GetWeightAt(1.5)' is one segment, and splitting it would look
802
+ // up a property named 'GetWeightAt(1'.
803
+ MCPFunctionCall::SplitPathSegments(Path, Parts);
672
804
  if (Parts.Num() == 0) { OutErr = TEXT("empty path"); return; }
673
805
 
674
806
  UStruct* CurStruct = Root->GetClass();
@@ -677,9 +809,24 @@ namespace
677
809
 
678
810
  for (int32 i = 0; i < Parts.Num(); ++i)
679
811
  {
680
- const FString& Seg = Parts[i];
812
+ const FString& RawSeg = Parts[i];
681
813
  const bool bLast = (i == Parts.Num() - 1);
682
- FProperty* Prop = CurStruct->FindPropertyByName(FName(*Seg));
814
+
815
+ // A segment may be 'Name' or 'Name(literal, literal)'. Reading the
816
+ // argument list off the front makes the rest of this loop the same
817
+ // as it always was.
818
+ FString Seg;
819
+ TArray<FString> CallArgs;
820
+ bool bHasArgList = false;
821
+ if (!MCPFunctionCall::ParseCallSegment(RawSeg, Seg, CallArgs, bHasArgList, OutErr))
822
+ {
823
+ return;
824
+ }
825
+
826
+ // An argument list says "call this", so no property or component of
827
+ // that name is considered: silently reading a property while the
828
+ // caller asked for a call would answer a question nobody asked.
829
+ FProperty* Prop = bHasArgList ? nullptr : CurStruct->FindPropertyByName(FName(*Seg));
683
830
 
684
831
  if (bLast && Prop)
685
832
  {
@@ -711,9 +858,9 @@ namespace
711
858
 
712
859
  // No property by that name. At the head of the path, try matching an
713
860
  // actor component by name (mirrors get_runtime_value behavior). At
714
- // any later segment OR the leaf, try a zero-arg UFUNCTION call - that
715
- // covers GetRequired() / IsPowered() etc.
716
- if (i == 0)
861
+ // any later segment OR the leaf, try a UFUNCTION call - that covers
862
+ // GetRequired() / IsPowered() and, since #969, GetTally(overclock).
863
+ if (i == 0 && !bHasArgList)
717
864
  {
718
865
  if (AActor* AsActor = Cast<AActor>(CurObject))
719
866
  {
@@ -730,53 +877,21 @@ namespace
730
877
  }
731
878
  }
732
879
 
733
- // UFUNCTION zero-arg getter at this segment.
880
+ // UFUNCTION getter at this segment, with or without arguments.
734
881
  if (UFunction* Fn = CurObject ? CurObject->FindFunction(FName(*Seg)) : nullptr)
735
882
  {
736
- if (Fn->NumParms == 1 && Fn->ReturnValueOffset != MAX_uint16)
883
+ if (!bLast)
737
884
  {
738
- uint8* Frame = (uint8*)FMemory_Alloca(Fn->ParmsSize);
739
- FMemory::Memzero(Frame, Fn->ParmsSize);
740
- for (TFieldIterator<FProperty> It(Fn); It; ++It)
741
- {
742
- It->InitializeValue_InContainer(Frame);
743
- }
744
- {
745
- // #806: without this guard an actor getter called against
746
- // the editor world is skipped and the zeroed frame reads
747
- // back as a real value.
748
- FEditorScriptExecutionGuard ScriptGuard;
749
- CurObject->ProcessEvent(Fn, Frame);
750
- }
751
- FProperty* RetProp = Fn->GetReturnProperty();
752
- if (RetProp)
753
- {
754
- if (bLast)
755
- {
756
- void* RetVal = RetProp->ContainerPtrToValuePtr<void>(Frame);
757
- WritePropertyValue(Out, FieldKey, RetProp, RetVal);
758
- }
759
- else
760
- {
761
- OutErr = FString::Printf(TEXT("UFUNCTION '%s' must be the leaf segment - cannot descend into its return"), *Seg);
762
- }
763
- }
764
- else
765
- {
766
- OutErr = FString::Printf(TEXT("UFUNCTION '%s' has no return value"), *Seg);
767
- }
768
- for (TFieldIterator<FProperty> It(Fn); It; ++It)
769
- {
770
- It->DestroyValue_InContainer(Frame);
771
- }
885
+ OutErr = FString::Printf(TEXT("UFUNCTION '%s' must be the leaf segment - cannot descend into its return"), *Seg);
772
886
  return;
773
887
  }
774
- OutErr = FString::Printf(TEXT("UFUNCTION '%s' must be zero-arg with a return"), *Seg);
888
+ CallPathGetter(CurObject, Fn, CallArgs, Out, FieldKey, OutErr);
775
889
  return;
776
890
  }
777
891
 
778
- OutErr = FString::Printf(TEXT("Segment '%s' is neither a property, component, nor a zero-arg UFUNCTION on %s"),
779
- *Seg, *CurStruct->GetName());
892
+ OutErr = FString::Printf(
893
+ TEXT("Segment '%s' is neither a property, component, nor a UFUNCTION on %s. A UFUNCTION that takes arguments is written '%s(value)'."),
894
+ *Seg, *CurStruct->GetName(), *Seg);
780
895
  return;
781
896
 
782
897
  NextSegment:;
@@ -983,7 +1098,7 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeFunction(const TSharedPtr<FJsonObj
983
1098
  FString FunctionName;
984
1099
  if (auto Err = RequireString(Params, TEXT("functionName"), FunctionName)) return Err;
985
1100
  FString ActorLabel;
986
- if (auto Err = RequireString(Params, TEXT("actorLabel"), ActorLabel)) return Err;
1101
+ if (auto Err = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Err;
987
1102
 
988
1103
  // #778: this used GEditor->GetPIEWorldContext(), which is always the
989
1104
  // primary (server) context, so 'pieInstance' could never reach it and a
@@ -1008,11 +1123,14 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeFunction(const TSharedPtr<FJsonObj
1008
1123
  // fixed label -> name -> path order, and a miss is an error. There is no
1009
1124
  // class-default fallback here and there must never be one: a default object
1010
1125
  // answers every call with default state, which reads as success.
1011
- AActor* Target = FindActorByLabelNameOrPath(World, ActorLabel);
1012
- if (!Target)
1013
- {
1014
- return MCPError(MCPDescribeActorLookupMiss(World, ActorLabel, WorldLabel));
1015
- }
1126
+ // #983: actorPath wins when given, and a label that names more than one
1127
+ // actor is refused rather than invoked on whichever came first.
1128
+ FMCPActorSelector TargetSel;
1129
+ TargetSel.Match = EMCPActorMatch::LabelNameOrPath;
1130
+ TargetSel.WorldLabel = *WorldLabel;
1131
+ TSharedPtr<FJsonValue> TargetErr;
1132
+ AActor* Target = MCPResolveActor(World, Params, TargetErr, TargetSel);
1133
+ if (!Target) return TargetErr;
1016
1134
  // Defensive: the lookup iterates placed actors, so neither of these can fire
1017
1135
  // today. They exist so that a future change which lets an archetype or an
1018
1136
  // actor from another world through fails loudly instead of quietly
@@ -1125,7 +1243,22 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeFunction(const TSharedPtr<FJsonObj
1125
1243
  continue;
1126
1244
  }
1127
1245
 
1128
- AActor* RefActor = FindActorByLabel(World, ActorArgLabel);
1246
+ // #983: an actor argument is an actor selector like any other, so a
1247
+ // duplicated label is refused rather than passed as whichever copy
1248
+ // the iterator reached first.
1249
+ TArray<AActor*> ArgMatches;
1250
+ MCPCollectActorsByToken(World, ActorArgLabel, EMCPActorMatch::LabelNameOrPath, ArgMatches);
1251
+ if (ArgMatches.Num() > 1)
1252
+ {
1253
+ for (TFieldIterator<FProperty> CleanupIt(Func); CleanupIt && (CleanupIt->PropertyFlags & CPF_Parm); ++CleanupIt)
1254
+ {
1255
+ CleanupIt->DestroyValue_InContainer(ParamBuf.GetData());
1256
+ }
1257
+ return MCPAmbiguousActorError(
1258
+ ActorArgLabel, TEXT("actorArgs"), TEXT("actorPath"),
1259
+ MCPDescribeActorMatchTier(ActorArgLabel, ArgMatches[0]), ArgMatches);
1260
+ }
1261
+ AActor* RefActor = ArgMatches.Num() == 1 ? ArgMatches[0] : nullptr;
1129
1262
  if (!RefActor)
1130
1263
  {
1131
1264
  for (TFieldIterator<FProperty> CleanupIt(Func); CleanupIt && (CleanupIt->PropertyFlags & CPF_Parm); ++CleanupIt)
@@ -1169,18 +1302,15 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeFunction(const TSharedPtr<FJsonObj
1169
1302
  // ProcessEvent can run arbitrary game code, including code that tears down
1170
1303
  // the world and collects garbage, and CallTarget is read again below.
1171
1304
  FGCObjectScopeGuard CallTargetGuard(CallTarget);
1172
- // #806: AActor::ProcessEvent refuses to run script in a world whose actors
1173
- // were never initialised for play, which is every editor world, unless the
1174
- // function is marked CallInEditor. The refusal is silent: the parameter
1175
- // frame stays zero-initialised and those zeros are then exported as if they
1176
- // were the answer, so K2_GetActorLocation on a placed actor reported
1177
- // (X=0,Y=0,Z=0) with success. FEditorScriptExecutionGuard opens the same
1178
- // gate the editor itself uses for CallInEditor functions, for this call
1179
- // only, so the placed instance actually runs the function.
1180
- {
1181
- FEditorScriptExecutionGuard ScriptGuard;
1182
- CallTarget->ProcessEvent(Func, ParamBuf.GetData());
1183
- }
1305
+
1306
+ // #973: read the callspace BEFORE the guard opens. That is the only moment
1307
+ // it is observable: inside the guard GAllowActorScriptExecutionInEditor
1308
+ // makes AActor::GetFunctionCallspace answer Local in its first branch, so a
1309
+ // UFUNCTION(Server) runs its implementation on this copy instead of being
1310
+ // sent, and the result used to say nothing about it.
1311
+ FString NaturalCallspace;
1312
+ const bool bCallspaceForcedLocal =
1313
+ MCPFunctionCall::WouldForceNetCallspaceLocal(CallTarget, Func, NaturalCallspace);
1184
1314
 
1185
1315
  auto Result = MCPSuccess();
1186
1316
  Result->SetStringField(TEXT("actorLabel"), ActorLabel);
@@ -1193,36 +1323,60 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeFunction(const TSharedPtr<FJsonObj
1193
1323
  if (!ComponentName.IsEmpty()) Result->SetStringField(TEXT("component"), ComponentName);
1194
1324
  Result->SetStringField(TEXT("functionName"), FunctionName);
1195
1325
 
1196
- TSharedPtr<FJsonObject> OutVals = MakeShared<FJsonObject>();
1197
- for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
1326
+ // #973: the opt-in escape from the override above. Queued for the next
1327
+ // engine tick, the send happens after the guard's scope has ended and the
1328
+ // call routes the way it would from game code.
1329
+ if (OptionalBool(Params, TEXT("deferToNextTick"), false))
1198
1330
  {
1199
- FProperty* P = *It;
1200
- if (P->PropertyFlags & (CPF_ReturnParm | CPF_OutParm))
1331
+ Result->SetBoolField(TEXT("deferred"), true);
1332
+ if (!NaturalCallspace.IsEmpty())
1201
1333
  {
1202
- // ParamBuf is raw bytes and invisible to GC, so an object out-param
1203
- // the call destroyed would be dereferenced by ExportTextItem_Direct.
1204
- // The scope guard above covers the target, not the results.
1205
- if (FObjectPropertyBase* OP = CastField<FObjectPropertyBase>(P))
1206
- {
1207
- UObject* Out = OP->GetObjectPropertyValue(OP->ContainerPtrToValuePtr<void>(ParamBuf.GetData()));
1208
- if (!Out) { OutVals->SetStringField(P->GetName(), TEXT("None")); continue; }
1209
- if (!IsValid(Out))
1210
- {
1211
- OutVals->SetStringField(P->GetName(), TEXT("(collected during the call)"));
1212
- continue;
1213
- }
1214
- }
1215
- FString S;
1216
- P->ExportTextItem_Direct(S, P->ContainerPtrToValuePtr<void>(ParamBuf.GetData()), nullptr, CallTarget, PPF_None);
1217
- OutVals->SetStringField(P->GetName(), S);
1334
+ Result->SetStringField(TEXT("netCallspace"), NaturalCallspace);
1218
1335
  }
1336
+ Result->SetStringField(TEXT("note"), TEXT(
1337
+ "Queued for the next engine tick, outside the editor script-execution guard, so a replicated function "
1338
+ "routes through GetFunctionCallspace normally instead of being forced to Local. Return and out parameters "
1339
+ "are not reported: the response is written before the call runs. Read the effect back afterwards with "
1340
+ "editor(get_runtime_values) or editor(get_object_properties)."));
1341
+ if (WorldLabel == TEXT("editor"))
1342
+ {
1343
+ Result->SetStringField(TEXT("warning"), TEXT(
1344
+ "The target is in the editor world, whose actors were never initialised for play. Outside the guard "
1345
+ "AActor::ProcessEvent will skip the call entirely. deferToNextTick is for a live PIE session."));
1346
+ }
1347
+ MCPFunctionCall::DeferProcessEventToNextTick(CallTarget, Func, MoveTemp(ParamBuf));
1348
+ return MCPResult(Result);
1219
1349
  }
1220
- Result->SetObjectField(TEXT("returnValues"), OutVals);
1221
1350
 
1222
- for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
1351
+ // #806: AActor::ProcessEvent refuses to run script in a world whose actors
1352
+ // were never initialised for play, which is every editor world, unless the
1353
+ // function is marked CallInEditor. The refusal is silent: the parameter
1354
+ // frame stays zero-initialised and those zeros are then exported as if they
1355
+ // were the answer, so K2_GetActorLocation on a placed actor reported
1356
+ // (X=0,Y=0,Z=0) with success. FEditorScriptExecutionGuard opens the same
1357
+ // gate the editor itself uses for CallInEditor functions, for this call
1358
+ // only, so the placed instance actually runs the function.
1359
+ {
1360
+ FEditorScriptExecutionGuard ScriptGuard;
1361
+ CallTarget->ProcessEvent(Func, ParamBuf.GetData());
1362
+ }
1363
+
1364
+ if (bCallspaceForcedLocal)
1223
1365
  {
1224
- It->DestroyValue_InContainer(ParamBuf.GetData());
1366
+ Result->SetStringField(TEXT("netCallspace"), NaturalCallspace);
1367
+ Result->SetBoolField(TEXT("callspaceForcedLocal"), true);
1368
+ Result->SetStringField(TEXT("warning"),
1369
+ MCPFunctionCall::DescribeForcedLocalCallspace(FunctionName, NaturalCallspace));
1225
1370
  }
1371
+
1372
+ // #885: containers come back as real JSON. Export text renders a
1373
+ // TArray<FString> return as an empty string, which made every
1374
+ // array-returning accessor unreadable through the bridge.
1375
+ TSharedPtr<FJsonObject> OutVals = MakeShared<FJsonObject>();
1376
+ MCPFunctionCall::WriteOutputs(OutVals, Func, ParamBuf.GetData(), CallTarget);
1377
+ Result->SetObjectField(TEXT("returnValues"), OutVals);
1378
+
1379
+ MCPFunctionCall::DestroyFrame(Func, ParamBuf.GetData());
1226
1380
  return MCPResult(Result);
1227
1381
  }
1228
1382
 
@@ -1245,18 +1399,23 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeStaticFunction(const TSharedPtr<FJ
1245
1399
  FString FunctionName;
1246
1400
  if (auto Err = RequireString(Params, TEXT("functionName"), FunctionName)) return Err;
1247
1401
 
1402
+ // #971: route through the shared resolver, so world=editor|pie|game|auto
1403
+ // and pieInstance select here exactly as they do for invoke_function. The
1404
+ // old code special-cased the literal string "pie" and sent everything else
1405
+ // to the editor world, which has no GameInstance: a server-authoritative
1406
+ // entry point implemented as a static that looks a UGameInstanceSubsystem up
1407
+ // off its world context could not be exercised in PIE at all.
1248
1408
  const FString WorldScope = OptionalString(Params, TEXT("world"), TEXT("editor")).ToLower();
1249
- UWorld* World = nullptr;
1250
- if (WorldScope == TEXT("pie"))
1251
- {
1252
- World = ResolveWorldFromParams(Params, TEXT("pie"));
1253
- if (!World) return MCPError(TEXT("PIE not running - cannot invoke against PIE world"));
1254
- }
1255
- else
1409
+ UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
1410
+ if (!World)
1256
1411
  {
1257
- World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
1258
- if (!World) return MCPError(TEXT("No editor world available"));
1412
+ return MCPError(WorldScope == TEXT("pie") || WorldScope == TEXT("game")
1413
+ ? TEXT("PIE not running (or no such pieInstance) - cannot invoke against a PIE world. See editor(list_pie_instances).")
1414
+ : TEXT("No editor world available"));
1259
1415
  }
1416
+ // Describe the world that was actually resolved, not the requested scope:
1417
+ // world="auto" resolves to PIE when a session is running.
1418
+ const FString WorldLabel = World->IsPlayInEditor() ? TEXT("PIE") : TEXT("editor");
1260
1419
 
1261
1420
  // Resolve the function-library class: a /Script/Module.Class path, or a bare
1262
1421
  // class name (with or without the leading U).
@@ -1332,11 +1491,22 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeStaticFunction(const TSharedPtr<FJ
1332
1491
  if (!OP) continue;
1333
1492
  FString ActorArgLabel;
1334
1493
  if (!(*ActorArgObj)->TryGetStringField(P->GetName(), ActorArgLabel) || ActorArgLabel.IsEmpty()) continue;
1335
- AActor* RefActor = FindActorByLabel(World, ActorArgLabel);
1494
+ // #983: refuse a duplicated label rather than passing whichever
1495
+ // copy the actor iterator reached first.
1496
+ TArray<AActor*> ArgMatches;
1497
+ MCPCollectActorsByToken(World, ActorArgLabel, EMCPActorMatch::LabelNameOrPath, ArgMatches);
1498
+ if (ArgMatches.Num() > 1)
1499
+ {
1500
+ Cleanup();
1501
+ return MCPAmbiguousActorError(
1502
+ ActorArgLabel, TEXT("actorArgs"), TEXT("actorPath"),
1503
+ MCPDescribeActorMatchTier(ActorArgLabel, ArgMatches[0]), ArgMatches);
1504
+ }
1505
+ AActor* RefActor = ArgMatches.Num() == 1 ? ArgMatches[0] : nullptr;
1336
1506
  if (!RefActor)
1337
1507
  {
1338
1508
  Cleanup();
1339
- return MCPError(FString::Printf(TEXT("actorArgs[%s]: actor '%s' not found in %s world"), *P->GetName(), *ActorArgLabel, WorldScope == TEXT("pie") ? TEXT("PIE") : TEXT("editor")));
1509
+ return MCPError(FString::Printf(TEXT("actorArgs[%s]: actor '%s' not found in %s world"), *P->GetName(), *ActorArgLabel, *WorldLabel));
1340
1510
  }
1341
1511
  if (!RefActor->IsA(OP->PropertyClass))
1342
1512
  {
@@ -1362,8 +1532,22 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeStaticFunction(const TSharedPtr<FJ
1362
1532
 
1363
1533
  // World-context injection: static library functions commonly take a UObject*
1364
1534
  // WorldContextObject. Fill any unset object param matching worldContextParam
1365
- // (or named WorldContextObject) with the resolved world.
1535
+ // (or the name the function's own WorldContext metadata gives, or a name
1536
+ // containing WorldContext) with the world resolved above.
1537
+ //
1538
+ // #971: worldContextParam only ever chose WHICH parameter to fill. What goes
1539
+ // into it is the selected world, so the parameter that decides PIE versus
1540
+ // editor is `world`, and the metadata lookup means a library that names its
1541
+ // context parameter something else is still recognised.
1366
1542
  const FString WcParam = OptionalString(Params, TEXT("worldContextParam"), TEXT(""));
1543
+ FString MetaWcParam;
1544
+ #if WITH_METADATA
1545
+ if (const FString* Declared = Func->FindMetaData(TEXT("WorldContext")))
1546
+ {
1547
+ MetaWcParam = *Declared;
1548
+ }
1549
+ #endif
1550
+ FString FilledWcParam;
1367
1551
  for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
1368
1552
  {
1369
1553
  FProperty* P = *It;
@@ -1371,13 +1555,17 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeStaticFunction(const TSharedPtr<FJ
1371
1555
  FObjectProperty* OP = CastField<FObjectProperty>(P);
1372
1556
  if (!OP) continue;
1373
1557
  const FString PName = P->GetName();
1374
- const bool bIsWc = (!WcParam.IsEmpty() && PName == WcParam) || PName.Contains(TEXT("WorldContext"));
1558
+ const bool bIsWc =
1559
+ (!WcParam.IsEmpty() && PName == WcParam) ||
1560
+ (!MetaWcParam.IsEmpty() && PName == MetaWcParam) ||
1561
+ PName.Contains(TEXT("WorldContext"));
1375
1562
  if (!bIsWc) continue;
1376
1563
  void* Addr = P->ContainerPtrToValuePtr<void>(ParamBuf.GetData());
1377
1564
  if (OP->GetObjectPropertyValue(Addr) != nullptr) continue;
1378
1565
  if (World->IsA(OP->PropertyClass) || OP->PropertyClass == UObject::StaticClass())
1379
1566
  {
1380
1567
  OP->SetObjectPropertyValue(Addr, World);
1568
+ FilledWcParam = PName;
1381
1569
  }
1382
1570
  }
1383
1571
 
@@ -1387,31 +1575,21 @@ TSharedPtr<FJsonValue> FEditorHandlers::InvokeStaticFunction(const TSharedPtr<FJ
1387
1575
  auto Result = MCPSuccess();
1388
1576
  Result->SetStringField(TEXT("className"), LibClass->GetName());
1389
1577
  Result->SetStringField(TEXT("functionName"), FunctionName);
1390
-
1391
- TSharedPtr<FJsonObject> OutVals = MakeShared<FJsonObject>();
1392
- for (TFieldIterator<FProperty> It(Func); It && (It->PropertyFlags & CPF_Parm); ++It)
1578
+ // #971: say which world the call actually ran against and which parameter
1579
+ // carried it. "subsystem unavailable" from a static that looks a
1580
+ // GameInstance subsystem up off its context is unreadable without this.
1581
+ Result->SetStringField(TEXT("world"), WorldLabel);
1582
+ Result->SetStringField(TEXT("worldPath"), World->GetPathName());
1583
+ Result->SetStringField(TEXT("netMode"), DescribePIENetMode(World));
1584
+ if (!FilledWcParam.IsEmpty())
1393
1585
  {
1394
- FProperty* P = *It;
1395
- if (P->PropertyFlags & (CPF_ReturnParm | CPF_OutParm))
1396
- {
1397
- // ParamBuf is raw bytes and invisible to GC, so an object out-param
1398
- // the call destroyed would be dereferenced by ExportTextItem_Direct.
1399
- // The scope guard above covers the CDO, not the results.
1400
- if (FObjectPropertyBase* OP = CastField<FObjectPropertyBase>(P))
1401
- {
1402
- UObject* Out = OP->GetObjectPropertyValue(OP->ContainerPtrToValuePtr<void>(ParamBuf.GetData()));
1403
- if (!Out) { OutVals->SetStringField(P->GetName(), TEXT("None")); continue; }
1404
- if (!IsValid(Out))
1405
- {
1406
- OutVals->SetStringField(P->GetName(), TEXT("(collected during the call)"));
1407
- continue;
1408
- }
1409
- }
1410
- FString S;
1411
- P->ExportTextItem_Direct(S, P->ContainerPtrToValuePtr<void>(ParamBuf.GetData()), nullptr, CDO, PPF_None);
1412
- OutVals->SetStringField(P->GetName(), S);
1413
- }
1586
+ Result->SetStringField(TEXT("worldContextParam"), FilledWcParam);
1414
1587
  }
1588
+
1589
+ // #885: containers come back as real JSON, scalars and structs keep their
1590
+ // export-text spelling. See MCPFunctionCall::OutputToJson.
1591
+ TSharedPtr<FJsonObject> OutVals = MakeShared<FJsonObject>();
1592
+ MCPFunctionCall::WriteOutputs(OutVals, Func, ParamBuf.GetData(), CDO);
1415
1593
  Result->SetObjectField(TEXT("returnValues"), OutVals);
1416
1594
 
1417
1595
  Cleanup();