ue-mcp 1.2.4 → 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 (243) 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/lean-context.js +8 -3
  34. package/dist/lean-context.js.map +1 -1
  35. package/dist/locking.js +11 -2
  36. package/dist/locking.js.map +1 -1
  37. package/dist/project.d.ts +11 -0
  38. package/dist/project.js +23 -2
  39. package/dist/project.js.map +1 -1
  40. package/dist/python-gate.d.ts +95 -0
  41. package/dist/python-gate.js +175 -0
  42. package/dist/python-gate.js.map +1 -0
  43. package/dist/tool-counts.json +16 -16
  44. package/dist/tools/animation.js +288 -24
  45. package/dist/tools/animation.js.map +1 -1
  46. package/dist/tools/asset.js +79 -14
  47. package/dist/tools/asset.js.map +1 -1
  48. package/dist/tools/audio.js +59 -5
  49. package/dist/tools/audio.js.map +1 -1
  50. package/dist/tools/blueprint.js +29 -12
  51. package/dist/tools/blueprint.js.map +1 -1
  52. package/dist/tools/chooser.js +4 -1
  53. package/dist/tools/chooser.js.map +1 -1
  54. package/dist/tools/editor.js +81 -43
  55. package/dist/tools/editor.js.map +1 -1
  56. package/dist/tools/feedback.js +214 -48
  57. package/dist/tools/feedback.js.map +1 -1
  58. package/dist/tools/foliage.js +27 -1
  59. package/dist/tools/foliage.js.map +1 -1
  60. package/dist/tools/gameplay.js +38 -11
  61. package/dist/tools/gameplay.js.map +1 -1
  62. package/dist/tools/gas.js +17 -5
  63. package/dist/tools/gas.js.map +1 -1
  64. package/dist/tools/landscape.js +22 -7
  65. package/dist/tools/landscape.js.map +1 -1
  66. package/dist/tools/level.js +322 -51
  67. package/dist/tools/level.js.map +1 -1
  68. package/dist/tools/material.js +10 -4
  69. package/dist/tools/material.js.map +1 -1
  70. package/dist/tools/niagara.js +3 -2
  71. package/dist/tools/niagara.js.map +1 -1
  72. package/dist/tools/pcg.js +6 -5
  73. package/dist/tools/pcg.js.map +1 -1
  74. package/dist/tools/project.js +47 -20
  75. package/dist/tools/project.js.map +1 -1
  76. package/dist/types.d.ts +21 -0
  77. package/dist/types.js +46 -3
  78. package/dist/types.js.map +1 -1
  79. package/dist/ue-mcp.default.yml +493 -125
  80. package/package.json +2 -2
  81. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  82. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  83. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  84. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  85. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  86. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  87. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  242. package/skills/ue-mcp-animation/SKILL.md +154 -0
  243. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -22,6 +22,9 @@ private:
22
22
  static TSharedPtr<FJsonValue> DeleteAsset(const TSharedPtr<FJsonObject>& Params);
23
23
  static TSharedPtr<FJsonValue> DeleteAssetBatch(const TSharedPtr<FJsonObject>& Params);
24
24
  static TSharedPtr<FJsonValue> BulkRename(const TSharedPtr<FJsonObject>& Params);
25
+ // Bounded redirector clean-up after a move (#908). Lives in
26
+ // AssetHandlers_Redirectors.cpp.
27
+ static TSharedPtr<FJsonValue> FixupRedirectors(const TSharedPtr<FJsonObject>& Params);
25
28
  static TSharedPtr<FJsonValue> CreateDataAsset(const TSharedPtr<FJsonObject>& Params);
26
29
  // Bounded batch create-or-update of UDataAsset instances. Lives in
27
30
  // AssetHandlers_BulkUpsert.cpp.
@@ -32,6 +35,9 @@ private:
32
35
  // #726: create an asset of any concrete UObject class via its registered
33
36
  // factory (or NewObject fallback), not just UDataAsset subclasses.
34
37
  static TSharedPtr<FJsonValue> CreateAssetByClass(const TSharedPtr<FJsonObject>& Params);
38
+ // A named subobject inside an existing asset's package (#975). Lives in
39
+ // AssetHandlers_Subobject.cpp.
40
+ static TSharedPtr<FJsonValue> CreateSubobject(const TSharedPtr<FJsonObject>& Params);
35
41
  static TSharedPtr<FJsonValue> SaveAsset(const TSharedPtr<FJsonObject>& Params);
36
42
  // #429: bulk save of every dirty package - one-shot end-of-workflow flush.
37
43
  static TSharedPtr<FJsonValue> SaveAllDirty(const TSharedPtr<FJsonObject>& Params);
@@ -97,6 +103,8 @@ private:
97
103
  static TSharedPtr<FJsonValue> SetStringTableEntry(const TSharedPtr<FJsonObject>& Params);
98
104
  static TSharedPtr<FJsonValue> RemoveStringTableEntry(const TSharedPtr<FJsonObject>& Params);
99
105
  static TSharedPtr<FJsonValue> ImportStringTable(const TSharedPtr<FJsonObject>& Params);
106
+ // CSV import with key-set validation and an explicit save result (#978).
107
+ static TSharedPtr<FJsonValue> ImportStringTableCsv(const TSharedPtr<FJsonObject>& Params);
100
108
 
101
109
  // Export
102
110
  static TSharedPtr<FJsonValue> ExportAsset(const TSharedPtr<FJsonObject>& Params);
@@ -205,7 +205,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkSetAssetProperties(const TSharedPtr<F
205
205
  continue;
206
206
  }
207
207
 
208
- UObject* LoadedAsset = LoadObject<UObject>(nullptr, *AssetPath);
208
+ UObject* LoadedAsset = MCPLoadAssetObject(AssetPath);
209
209
  if (!LoadedAsset)
210
210
  {
211
211
  RejectItem(BulkStatusNotFound, FString::Printf(TEXT("Preflight failed: could not load asset '%s'"), *AssetPath));
@@ -0,0 +1,503 @@
1
+ // asset(bulk_read_properties) - read the same properties off many assets in
2
+ // one call (#909).
3
+ //
4
+ // The reporter needed voice-management and concurrency settings across 582
5
+ // SoundWave, SoundCue and MetaSoundSource assets. audio(list) does not report
6
+ // those fields, and asset(read_properties) answers one asset per call, so the
7
+ // question became a Python loop. This is the same shape as
8
+ // level(query_components), one level up: the directory and class filters, the
9
+ // property reads, the predicates and the aggregates all run in the editor, and
10
+ // only the answer crosses the wire.
11
+ //
12
+ // This file exists as its own translation unit with its own registration so
13
+ // the change lands without touching AssetHandlers.cpp.
14
+ //
15
+ // It never writes. Loading an asset to read it does not dirty its package, and
16
+ // the response says so rather than asking to be believed.
17
+
18
+ #include "AssetHandlers_BulkRead.h"
19
+
20
+ #include "AssetRegistry/AssetRegistryModule.h"
21
+ #include "AssetRegistry/IAssetRegistry.h"
22
+ #include "Engine/Blueprint.h"
23
+ #include "HandlerEditorState.h"
24
+ #include "HandlerQuery.h"
25
+ #include "HandlerRegistry.h"
26
+ #include "HandlerUtils.h"
27
+ #include "Misc/FileHelper.h"
28
+ #include "Misc/Paths.h"
29
+ #include "Serialization/JsonSerializer.h"
30
+ #include "Serialization/JsonWriter.h"
31
+ #include "UObject/Package.h"
32
+
33
+ namespace
34
+ {
35
+ // Every one of these is here because the action it replaces was unbounded.
36
+ constexpr int32 MCPBulkReadDefaultRowLimit = 200;
37
+ constexpr int32 MCPBulkReadMaxRowLimit = 2000;
38
+ constexpr int32 MCPBulkReadDefaultMaxAssets = 2000;
39
+ constexpr int32 MCPBulkReadHardMaxAssets = 20000;
40
+ constexpr int32 MCPBulkReadMaxProperties = 32;
41
+ constexpr int32 MCPBulkReadMaxPredicates = 24;
42
+ constexpr int32 MCPBulkReadMaxGroups = 200;
43
+ constexpr int32 MCPBulkReadMaxCountByPaths = 8;
44
+ constexpr int32 MCPBulkReadMaxCountByValues = 64;
45
+ constexpr int32 MCPBulkReadDefaultGroupSamples = 5;
46
+ constexpr int32 MCPBulkReadMaxGroupSamples = 25;
47
+ constexpr int32 MCPBulkReadMaxExplicitPaths = 5000;
48
+
49
+ /**
50
+ * A Blueprint asset's properties live on its generated class default
51
+ * object, not on the UBlueprint wrapper, so reading `bCanEverTick` off the
52
+ * wrapper would always report absent.
53
+ */
54
+ UObject* MCPBulkReadTarget(UObject* Asset)
55
+ {
56
+ if (UBlueprint* Blueprint = Cast<UBlueprint>(Asset))
57
+ {
58
+ if (UClass* GeneratedClass = Blueprint->GeneratedClass)
59
+ {
60
+ if (UObject* CDO = GeneratedClass->GetDefaultObject())
61
+ {
62
+ return CDO;
63
+ }
64
+ }
65
+ }
66
+ return Asset;
67
+ }
68
+
69
+ /** Render one row set as pretty JSON for the outputPath variant. */
70
+ FString MCPBulkReadSerializeRows(const TArray<TSharedPtr<FJsonValue>>& Rows)
71
+ {
72
+ TSharedPtr<FJsonObject> Document = MakeShared<FJsonObject>();
73
+ Document->SetNumberField(TEXT("count"), Rows.Num());
74
+ Document->SetArrayField(TEXT("rows"), Rows);
75
+ FString Output;
76
+ const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
77
+ FJsonSerializer::Serialize(Document.ToSharedRef(), Writer);
78
+ return Output;
79
+ }
80
+ }
81
+
82
+ void FAssetBulkReadHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
83
+ {
84
+ // Loading hundreds of assets to read them takes longer than the default
85
+ // handler timeout allows, and a timeout here would look like a hang.
86
+ Registry.RegisterHandlerWithTimeout(
87
+ TEXT("bulk_read_asset_properties"), &BulkReadAssetProperties, 300.0f);
88
+ }
89
+
90
+ TSharedPtr<FJsonValue> FAssetBulkReadHandlers::BulkReadAssetProperties(const TSharedPtr<FJsonObject>& Params)
91
+ {
92
+ MCP_CHECK_GAME_THREAD();
93
+
94
+ // ── Properties to read ──────────────────────────────────────────────────
95
+ TArray<FString> PropertyPaths;
96
+ {
97
+ const TArray<TSharedPtr<FJsonValue>>* PropertyValues = nullptr;
98
+ if (Params->TryGetArrayField(TEXT("propertyNames"), PropertyValues) && PropertyValues)
99
+ {
100
+ PropertyPaths = JsonArrayToStringList(PropertyValues);
101
+ }
102
+ }
103
+ if (PropertyPaths.IsEmpty())
104
+ {
105
+ return MCPError(TEXT("Missing required 'propertyNames' array (dotted paths into nested structs are supported, e.g. 'CullDistance.Max')"));
106
+ }
107
+ if (PropertyPaths.Num() > MCPBulkReadMaxProperties)
108
+ {
109
+ return MCPError(FString::Printf(
110
+ TEXT("'propertyNames' exceeds the maximum of %d entries"), MCPBulkReadMaxProperties));
111
+ }
112
+
113
+ // ── Selection ───────────────────────────────────────────────────────────
114
+ TArray<FString> ExplicitPaths;
115
+ {
116
+ const TArray<TSharedPtr<FJsonValue>>* PathValues = nullptr;
117
+ if (Params->TryGetArrayField(TEXT("assetPaths"), PathValues) && PathValues)
118
+ {
119
+ ExplicitPaths = JsonArrayToStringList(PathValues);
120
+ }
121
+ }
122
+ const FString Directory = OptionalString(Params, TEXT("directory"));
123
+ if (ExplicitPaths.IsEmpty() && Directory.IsEmpty())
124
+ {
125
+ return MCPError(TEXT("Pass either 'assetPaths' (explicit list) or 'directory' (with optional 'classNames')"));
126
+ }
127
+ if (ExplicitPaths.Num() > MCPBulkReadMaxExplicitPaths)
128
+ {
129
+ return MCPError(FString::Printf(
130
+ TEXT("'assetPaths' exceeds the maximum of %d entries"), MCPBulkReadMaxExplicitPaths));
131
+ }
132
+
133
+ const bool bRecursive = OptionalBool(Params, TEXT("recursive"), true);
134
+ const bool bMatchSubclasses = OptionalBool(Params, TEXT("matchSubclasses"), true);
135
+ const int32 MaxAssets = FMath::Clamp(
136
+ OptionalInt(Params, TEXT("maxAssets"), MCPBulkReadDefaultMaxAssets), 1, MCPBulkReadHardMaxAssets);
137
+
138
+ TArray<UClass*> ClassFilters;
139
+ {
140
+ const TArray<TSharedPtr<FJsonValue>>* ClassValues = nullptr;
141
+ if (Params->TryGetArrayField(TEXT("classNames"), ClassValues) && ClassValues)
142
+ {
143
+ for (const FString& Spec : JsonArrayToStringList(ClassValues))
144
+ {
145
+ UClass* Resolved = MCPResolveClass(Spec, true);
146
+ if (!Resolved)
147
+ {
148
+ return MCPClassNotFoundError(Spec, TEXT("classNames"));
149
+ }
150
+ ClassFilters.Add(Resolved);
151
+ }
152
+ }
153
+ }
154
+
155
+ // ── Predicates and aggregates ───────────────────────────────────────────
156
+ TArray<MCPQuery::FPredicate> Predicates;
157
+ {
158
+ const TArray<TSharedPtr<FJsonValue>>* WhereValues = nullptr;
159
+ Params->TryGetArrayField(TEXT("where"), WhereValues);
160
+ FString ParseError;
161
+ if (!MCPQuery::ParsePredicates(WhereValues, MCPBulkReadMaxPredicates, Predicates, ParseError))
162
+ {
163
+ return MCPError(ParseError);
164
+ }
165
+ }
166
+ const FString WhereMode = OptionalString(Params, TEXT("whereMode"), TEXT("all")).ToLower();
167
+ if (WhereMode != TEXT("all") && WhereMode != TEXT("any"))
168
+ {
169
+ return MCPError(TEXT("'whereMode' must be either 'all' or 'any'"));
170
+ }
171
+ const bool bSuspectOnly = OptionalBool(Params, TEXT("suspectOnly"), false);
172
+ if (bSuspectOnly)
173
+ {
174
+ if (WhereMode == TEXT("any") && Predicates.Num() > 0)
175
+ {
176
+ return MCPError(TEXT("'suspectOnly' cannot be combined with whereMode 'any'; add {field:'suspect', op:'isTrue'} to 'where' instead"));
177
+ }
178
+ MCPQuery::FPredicate Suspect;
179
+ Suspect.Field = TEXT("suspect");
180
+ Suspect.Op = TEXT("isTrue");
181
+ Predicates.Add(MoveTemp(Suspect));
182
+ }
183
+
184
+ const FString GroupBy = OptionalString(Params, TEXT("groupBy"));
185
+ const int32 GroupSamples = FMath::Clamp(
186
+ OptionalInt(Params, TEXT("sampleLimit"), MCPBulkReadDefaultGroupSamples), 0, MCPBulkReadMaxGroupSamples);
187
+ TArray<FString> CountByPaths;
188
+ {
189
+ const TArray<TSharedPtr<FJsonValue>>* CountByValues = nullptr;
190
+ if (Params->TryGetArrayField(TEXT("countBy"), CountByValues) && CountByValues)
191
+ {
192
+ CountByPaths = JsonArrayToStringList(CountByValues);
193
+ }
194
+ }
195
+ if (CountByPaths.Num() > MCPBulkReadMaxCountByPaths)
196
+ {
197
+ return MCPError(FString::Printf(
198
+ TEXT("'countBy' exceeds the maximum of %d paths"), MCPBulkReadMaxCountByPaths));
199
+ }
200
+
201
+ const bool bCountOnly = OptionalBool(Params, TEXT("countOnly"), false);
202
+ const int32 Limit = FMath::Clamp(
203
+ OptionalInt(Params, TEXT("limit"), MCPBulkReadDefaultRowLimit), 0, MCPBulkReadMaxRowLimit);
204
+ const int32 StartIndex = FMath::Max(0, OptionalInt(Params, TEXT("startIndex"), 0));
205
+ const FString OutputPath = OptionalString(Params, TEXT("outputPath"));
206
+
207
+ // ── Gather candidate asset paths ────────────────────────────────────────
208
+ TArray<FString> CandidatePaths;
209
+ bool bScanTruncated = false;
210
+ if (!ExplicitPaths.IsEmpty())
211
+ {
212
+ CandidatePaths = MoveTemp(ExplicitPaths);
213
+ }
214
+ else
215
+ {
216
+ FAssetRegistryModule& AssetRegistryModule =
217
+ FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
218
+ IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
219
+
220
+ FARFilter Filter;
221
+ Filter.bRecursivePaths = bRecursive;
222
+ Filter.PackagePaths.Add(FName(*Directory));
223
+ for (const UClass* Class : ClassFilters)
224
+ {
225
+ Filter.ClassPaths.Add(Class->GetClassPathName());
226
+ }
227
+ Filter.bRecursiveClasses = bMatchSubclasses;
228
+
229
+ TArray<FAssetData> Found;
230
+ AssetRegistry.GetAssets(Filter, Found);
231
+ for (const FAssetData& Data : Found)
232
+ {
233
+ if (Data.AssetClassPath.GetAssetName() == FName(TEXT("ObjectRedirector")))
234
+ {
235
+ continue;
236
+ }
237
+ CandidatePaths.Add(Data.GetObjectPathString());
238
+ }
239
+ }
240
+ CandidatePaths.Sort();
241
+ if (CandidatePaths.Num() > MaxAssets)
242
+ {
243
+ bScanTruncated = true;
244
+ CandidatePaths.SetNum(MaxAssets);
245
+ }
246
+
247
+ TArray<FString> DirtyBefore;
248
+ MCPEditorState::CollectDirtyEditorPackageNames(DirtyBefore);
249
+
250
+ // ── Read ────────────────────────────────────────────────────────────────
251
+ int32 Loaded = 0;
252
+ int32 Matched = 0;
253
+ TArray<FString> UnloadableAssets;
254
+ TArray<FString> ClassFilteredOut;
255
+ TArray<TSharedPtr<FJsonValue>> Rows;
256
+ TMap<FString, int32> GroupCounts;
257
+ TMap<FString, TArray<FString>> GroupSamplesByKey;
258
+ TArray<FString> GroupOrder;
259
+ TMap<FString, TMap<FString, int32>> CountByHistograms;
260
+ bool bGroupsTruncated = false;
261
+
262
+ for (const FString& AssetPath : CandidatePaths)
263
+ {
264
+ UObject* Asset = LoadObject<UObject>(nullptr, *AssetPath);
265
+ if (!Asset)
266
+ {
267
+ if (UnloadableAssets.Num() < 50)
268
+ {
269
+ UnloadableAssets.Add(AssetPath);
270
+ }
271
+ continue;
272
+ }
273
+ ++Loaded;
274
+
275
+ // An explicit assetPaths list bypasses the registry filter, so the
276
+ // class filter has to be applied here too or classNames would silently
277
+ // mean nothing for that shape of request.
278
+ if (ClassFilters.Num() > 0)
279
+ {
280
+ bool bClassMatches = false;
281
+ for (UClass* Class : ClassFilters)
282
+ {
283
+ if (bMatchSubclasses ? Asset->IsA(Class) : (Asset->GetClass() == Class))
284
+ {
285
+ bClassMatches = true;
286
+ break;
287
+ }
288
+ }
289
+ if (!bClassMatches)
290
+ {
291
+ if (ClassFilteredOut.Num() < 50)
292
+ {
293
+ ClassFilteredOut.Add(AssetPath);
294
+ }
295
+ continue;
296
+ }
297
+ }
298
+
299
+ UObject* Target = MCPBulkReadTarget(Asset);
300
+
301
+ TSharedPtr<FJsonObject> Props = MakeShared<FJsonObject>();
302
+ TArray<FString> MissingProperties;
303
+ bool bAnyNull = false;
304
+ for (const FString& PropertyPath : PropertyPaths)
305
+ {
306
+ FString ResolvedType;
307
+ const TSharedPtr<FJsonValue> Value = MCPQuery::ReadDottedProperty(Target, PropertyPath, ResolvedType);
308
+ if (!Value.IsValid())
309
+ {
310
+ // Absent is not null. "This class has no such property" and
311
+ // "this property is unset" are different findings, and
312
+ // collapsing them is what makes an audit unactionable.
313
+ MissingProperties.Add(PropertyPath);
314
+ continue;
315
+ }
316
+ if (Value->Type == EJson::Null)
317
+ {
318
+ bAnyNull = true;
319
+ }
320
+ Props->SetField(PropertyPath, Value);
321
+ }
322
+
323
+ TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
324
+ Row->SetStringField(TEXT("assetPath"), Asset->GetPathName());
325
+ Row->SetStringField(TEXT("assetName"), Asset->GetName());
326
+ Row->SetStringField(TEXT("className"), Asset->GetClass()->GetName());
327
+ Row->SetStringField(TEXT("packagePath"), Asset->GetOutermost()->GetName());
328
+ if (Target != Asset)
329
+ {
330
+ Row->SetStringField(TEXT("readFrom"), Target->GetPathName());
331
+ }
332
+ Row->SetObjectField(TEXT("props"), Props);
333
+ Row->SetArrayField(TEXT("missingProperties"), MCPStringListToJson(MissingProperties));
334
+ Row->SetBoolField(TEXT("suspect"), MissingProperties.Num() > 0 || bAnyNull);
335
+
336
+ if (!MCPQuery::EvaluateAll(Predicates, Row, WhereMode == TEXT("all")))
337
+ {
338
+ continue;
339
+ }
340
+ ++Matched;
341
+
342
+ if (!GroupBy.IsEmpty())
343
+ {
344
+ const FString Key = MCPQuery::ValueKey(MCPQuery::ResolvePath(Row, GroupBy));
345
+ if (int32* Existing = GroupCounts.Find(Key))
346
+ {
347
+ ++(*Existing);
348
+ }
349
+ else if (GroupCounts.Num() < MCPBulkReadMaxGroups)
350
+ {
351
+ GroupCounts.Add(Key, 1);
352
+ GroupOrder.Add(Key);
353
+ }
354
+ else
355
+ {
356
+ bGroupsTruncated = true;
357
+ }
358
+ if (GroupCounts.Contains(Key))
359
+ {
360
+ TArray<FString>& Samples = GroupSamplesByKey.FindOrAdd(Key);
361
+ if (Samples.Num() < GroupSamples)
362
+ {
363
+ Samples.Add(Asset->GetName());
364
+ }
365
+ }
366
+ }
367
+
368
+ for (const FString& CountPath : CountByPaths)
369
+ {
370
+ TMap<FString, int32>& Histogram = CountByHistograms.FindOrAdd(CountPath);
371
+ const FString Key = MCPQuery::ValueKey(MCPQuery::ResolvePath(Row, CountPath));
372
+ if (int32* Existing = Histogram.Find(Key))
373
+ {
374
+ ++(*Existing);
375
+ }
376
+ else if (Histogram.Num() < MCPBulkReadMaxCountByValues)
377
+ {
378
+ Histogram.Add(Key, 1);
379
+ }
380
+ }
381
+
382
+ if (!bCountOnly)
383
+ {
384
+ Rows.Add(MakeShared<FJsonValueObject>(Row));
385
+ }
386
+ }
387
+
388
+ // ── Response ────────────────────────────────────────────────────────────
389
+ auto Result = MCPSuccess();
390
+ Result->SetNumberField(TEXT("candidates"), CandidatePaths.Num());
391
+ Result->SetNumberField(TEXT("loaded"), Loaded);
392
+ Result->SetNumberField(TEXT("matched"), Matched);
393
+ Result->SetBoolField(TEXT("countOnly"), bCountOnly);
394
+ Result->SetBoolField(TEXT("scanTruncated"), bScanTruncated);
395
+ if (bScanTruncated)
396
+ {
397
+ Result->SetStringField(TEXT("scanNote"), FString::Printf(
398
+ TEXT("More than maxAssets (%d) candidates matched the selection; narrow 'directory' or 'classNames'."),
399
+ MaxAssets));
400
+ }
401
+ Result->SetArrayField(TEXT("unloadableAssets"), MCPStringListToJson(UnloadableAssets));
402
+ if (ClassFilteredOut.Num() > 0)
403
+ {
404
+ Result->SetArrayField(TEXT("classFilteredOut"), MCPStringListToJson(ClassFilteredOut));
405
+ }
406
+
407
+ if (!bCountOnly)
408
+ {
409
+ if (!OutputPath.IsEmpty())
410
+ {
411
+ // File output is the escape valve for a result set too large to be
412
+ // worth paging through. The rows still exist; they just do not go
413
+ // through the model's context to get to disk.
414
+ const FString Absolute = FPaths::ConvertRelativePathToFull(OutputPath);
415
+ if (!FFileHelper::SaveStringToFile(MCPBulkReadSerializeRows(Rows), *Absolute))
416
+ {
417
+ return MCPError(FString::Printf(TEXT("Failed to write 'outputPath': %s"), *Absolute));
418
+ }
419
+ Result->SetStringField(TEXT("outputPath"), Absolute);
420
+ Result->SetNumberField(TEXT("written"), Rows.Num());
421
+ }
422
+ else
423
+ {
424
+ const int32 First = FMath::Min(StartIndex, Rows.Num());
425
+ const int32 Last = FMath::Min(First + Limit, Rows.Num());
426
+ TArray<TSharedPtr<FJsonValue>> Page;
427
+ for (int32 Index = First; Index < Last; ++Index)
428
+ {
429
+ Page.Add(Rows[Index]);
430
+ }
431
+ Result->SetArrayField(TEXT("rows"), Page);
432
+ Result->SetNumberField(TEXT("returned"), Page.Num());
433
+ Result->SetNumberField(TEXT("startIndex"), StartIndex);
434
+ Result->SetNumberField(TEXT("limit"), Limit);
435
+ const int32 NextStartIndex = StartIndex + Page.Num();
436
+ Result->SetBoolField(TEXT("hasMore"), NextStartIndex < Rows.Num());
437
+ if (NextStartIndex < Rows.Num())
438
+ {
439
+ Result->SetNumberField(TEXT("nextStartIndex"), NextStartIndex);
440
+ }
441
+ }
442
+ }
443
+
444
+ if (!GroupBy.IsEmpty())
445
+ {
446
+ GroupOrder.Sort([&GroupCounts](const FString& A, const FString& B)
447
+ {
448
+ const int32 CountA = GroupCounts.FindChecked(A);
449
+ const int32 CountB = GroupCounts.FindChecked(B);
450
+ return CountA != CountB ? CountA > CountB : A < B;
451
+ });
452
+ TArray<TSharedPtr<FJsonValue>> GroupArray;
453
+ for (const FString& Key : GroupOrder)
454
+ {
455
+ TSharedPtr<FJsonObject> GroupObject = MakeShared<FJsonObject>();
456
+ GroupObject->SetStringField(TEXT("key"), Key);
457
+ GroupObject->SetNumberField(TEXT("count"), GroupCounts.FindChecked(Key));
458
+ if (const TArray<FString>* Samples = GroupSamplesByKey.Find(Key))
459
+ {
460
+ GroupObject->SetArrayField(TEXT("samples"), MCPStringListToJson(*Samples));
461
+ }
462
+ GroupArray.Add(MakeShared<FJsonValueObject>(GroupObject));
463
+ }
464
+ Result->SetStringField(TEXT("groupBy"), GroupBy);
465
+ Result->SetArrayField(TEXT("groups"), GroupArray);
466
+ Result->SetBoolField(TEXT("groupsTruncated"), bGroupsTruncated);
467
+ }
468
+
469
+ if (CountByPaths.Num() > 0)
470
+ {
471
+ TSharedPtr<FJsonObject> CountsObject = MakeShared<FJsonObject>();
472
+ for (const FString& CountPath : CountByPaths)
473
+ {
474
+ TSharedPtr<FJsonObject> Histogram = MakeShared<FJsonObject>();
475
+ if (const TMap<FString, int32>* Values = CountByHistograms.Find(CountPath))
476
+ {
477
+ for (const TPair<FString, int32>& Pair : *Values)
478
+ {
479
+ Histogram->SetNumberField(Pair.Key, Pair.Value);
480
+ }
481
+ }
482
+ CountsObject->SetObjectField(CountPath, Histogram);
483
+ }
484
+ Result->SetObjectField(TEXT("counts"), CountsObject);
485
+ }
486
+
487
+ // The read guarantee, asserted rather than assumed. Loading an asset to
488
+ // read it does not dirty its package; if something did, the caller sees
489
+ // which package rather than an unexplained unsaved-changes marker.
490
+ TArray<FString> DirtyAfter;
491
+ MCPEditorState::CollectDirtyEditorPackageNames(DirtyAfter);
492
+ for (const FString& Already : DirtyBefore)
493
+ {
494
+ DirtyAfter.Remove(Already);
495
+ }
496
+ Result->SetBoolField(TEXT("dirtiedPackages"), DirtyAfter.Num() > 0);
497
+ if (DirtyAfter.Num() > 0)
498
+ {
499
+ Result->SetArrayField(TEXT("dirtyPackages"), MCPStringListToJson(DirtyAfter));
500
+ }
501
+
502
+ return MCPResult(Result);
503
+ }
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #include "CoreMinimal.h"
4
+ #include "Dom/JsonObject.h"
5
+ #include "Dom/JsonValue.h"
6
+
7
+ /**
8
+ * asset(bulk_read_properties) (#909).
9
+ *
10
+ * Its own class rather than another FAssetHandlers member, with its own
11
+ * registration entry in BridgeServer.cpp, so this lands without touching
12
+ * AssetHandlers.cpp at all.
13
+ */
14
+ class FAssetBulkReadHandlers
15
+ {
16
+ public:
17
+ static void RegisterHandlers(class FMCPHandlerRegistry& Registry);
18
+
19
+ private:
20
+ static TSharedPtr<FJsonValue> BulkReadAssetProperties(const TSharedPtr<FJsonObject>& Params);
21
+ };
@@ -351,7 +351,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkRestoreDataAssets(const TSharedPtr<FJ
351
351
  continue;
352
352
  }
353
353
 
354
- UObject* Asset = LoadObject<UObject>(nullptr, *AssetPath);
354
+ UObject* Asset = MCPLoadAssetObject(AssetPath);
355
355
  if (!Asset)
356
356
  {
357
357
  Errors.Add(MakeShared<FJsonValueString>(FString::Printf(TEXT("Unable to load '%s' for rollback"), *AssetPath)));
@@ -510,7 +510,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::BulkUpsertDataAssets(const TSharedPtr<FJs
510
510
  *Prepared.DataClass->GetPathName()));
511
511
  }
512
512
 
513
- Prepared.ExistingAsset = LoadObject<UObject>(nullptr, *Prepared.AssetPath);
513
+ Prepared.ExistingAsset = MCPLoadAssetObject(Prepared.AssetPath);
514
514
  if (Prepared.ExistingAsset)
515
515
  {
516
516
  if (OnConflict == TEXT("error"))
@@ -136,7 +136,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::ListEnumValues(const TSharedPtr<FJsonObje
136
136
  FString AssetPath;
137
137
  if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
138
138
 
139
- UEnum* Enum = Cast<UEnum>(UEditorAssetLibrary::LoadAsset(AssetPath));
139
+ UEnum* Enum = LoadAssetByPath<UEnum>(AssetPath);
140
140
  if (!Enum) return MCPError(FString::Printf(TEXT("Enum not found: %s"), *AssetPath));
141
141
 
142
142
  TSharedPtr<FJsonObject> Res = MCPSuccess();
@@ -162,7 +162,7 @@ TSharedPtr<FJsonValue> FAssetHandlers::EditUserDefinedEnum(const TSharedPtr<FJso
162
162
  FString Op;
163
163
  if (auto Err = RequireString(Params, TEXT("op"), Op)) return Err;
164
164
 
165
- UUserDefinedEnum* Enum = Cast<UUserDefinedEnum>(UEditorAssetLibrary::LoadAsset(AssetPath));
165
+ UUserDefinedEnum* Enum = LoadAssetByPath<UUserDefinedEnum>(AssetPath);
166
166
  if (!Enum) return MCPError(FString::Printf(TEXT("UserDefinedEnum not found (native UEnums cannot be edited): %s"), *AssetPath));
167
167
 
168
168
  TSharedPtr<FJsonObject> Res = MCPSuccess();