unreal-engine-mcp-server 0.5.4 → 0.5.5

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 (468) hide show
  1. package/dist/automation/bridge.d.ts.map +1 -0
  2. package/dist/automation/bridge.js.map +1 -0
  3. package/dist/automation/connection-manager.d.ts.map +1 -0
  4. package/dist/automation/connection-manager.js.map +1 -0
  5. package/dist/automation/handshake.d.ts.map +1 -0
  6. package/dist/automation/handshake.js.map +1 -0
  7. package/dist/automation/index.d.ts.map +1 -0
  8. package/dist/automation/index.js.map +1 -0
  9. package/dist/automation/message-handler.d.ts.map +1 -0
  10. package/dist/automation/message-handler.js.map +1 -0
  11. package/dist/automation/request-tracker.d.ts.map +1 -0
  12. package/dist/automation/request-tracker.js.map +1 -0
  13. package/dist/automation/types.d.ts.map +1 -0
  14. package/dist/automation/types.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +4 -3
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/class-aliases.d.ts.map +1 -0
  19. package/dist/config/class-aliases.js.map +1 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js.map +1 -0
  24. package/dist/graphql/loaders.d.ts.map +1 -0
  25. package/dist/graphql/loaders.js.map +1 -0
  26. package/dist/graphql/resolvers.d.ts.map +1 -0
  27. package/dist/graphql/resolvers.js +29 -29
  28. package/dist/graphql/resolvers.js.map +1 -0
  29. package/dist/graphql/schema.d.ts.map +1 -0
  30. package/dist/graphql/schema.js.map +1 -0
  31. package/dist/graphql/server.d.ts.map +1 -0
  32. package/dist/graphql/server.js.map +1 -0
  33. package/dist/graphql/types.d.ts.map +1 -0
  34. package/dist/graphql/types.js.map +1 -0
  35. package/dist/handlers/resource-handlers.d.ts.map +1 -0
  36. package/dist/handlers/resource-handlers.js.map +1 -0
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +64 -7
  40. package/dist/index.js.map +1 -0
  41. package/dist/resources/actors.d.ts.map +1 -0
  42. package/dist/resources/actors.js.map +1 -0
  43. package/dist/resources/assets.d.ts.map +1 -0
  44. package/dist/resources/assets.js +6 -4
  45. package/dist/resources/assets.js.map +1 -0
  46. package/dist/resources/levels.d.ts.map +1 -0
  47. package/dist/resources/levels.js.map +1 -0
  48. package/dist/server/resource-registry.d.ts.map +1 -0
  49. package/dist/server/resource-registry.js.map +1 -0
  50. package/dist/server/tool-registry.d.ts.map +1 -0
  51. package/dist/server/tool-registry.js.map +1 -0
  52. package/dist/server-setup.d.ts.map +1 -0
  53. package/dist/server-setup.js.map +1 -0
  54. package/dist/services/health-monitor.d.ts.map +1 -0
  55. package/dist/services/health-monitor.js.map +1 -0
  56. package/dist/services/metrics-server.d.ts.map +1 -0
  57. package/dist/services/metrics-server.js.map +1 -0
  58. package/dist/tools/actors.d.ts.map +1 -0
  59. package/dist/tools/actors.js +3 -1
  60. package/dist/tools/actors.js.map +1 -0
  61. package/dist/tools/animation.d.ts.map +1 -0
  62. package/dist/tools/animation.js +2 -2
  63. package/dist/tools/animation.js.map +1 -0
  64. package/dist/tools/assets.d.ts.map +1 -0
  65. package/dist/tools/assets.js.map +1 -0
  66. package/dist/tools/audio.d.ts.map +1 -0
  67. package/dist/tools/audio.js.map +1 -0
  68. package/dist/tools/base-tool.d.ts.map +1 -0
  69. package/dist/tools/base-tool.js.map +1 -0
  70. package/dist/tools/behavior-tree.d.ts.map +1 -0
  71. package/dist/tools/behavior-tree.js.map +1 -0
  72. package/dist/tools/blueprint.d.ts.map +1 -0
  73. package/dist/tools/blueprint.js +4 -2
  74. package/dist/tools/blueprint.js.map +1 -0
  75. package/dist/tools/consolidated-tool-definitions.d.ts.map +1 -0
  76. package/dist/tools/consolidated-tool-definitions.js.map +1 -0
  77. package/dist/tools/consolidated-tool-handlers.d.ts.map +1 -0
  78. package/dist/tools/consolidated-tool-handlers.js.map +1 -0
  79. package/dist/tools/debug.d.ts.map +1 -0
  80. package/dist/tools/debug.js +3 -1
  81. package/dist/tools/debug.js.map +1 -0
  82. package/dist/tools/dynamic-handler-registry.d.ts.map +1 -0
  83. package/dist/tools/dynamic-handler-registry.js +3 -1
  84. package/dist/tools/dynamic-handler-registry.js.map +1 -0
  85. package/dist/tools/editor.d.ts.map +1 -0
  86. package/dist/tools/editor.js +1 -1
  87. package/dist/tools/editor.js.map +1 -0
  88. package/dist/tools/engine.d.ts.map +1 -0
  89. package/dist/tools/engine.js.map +1 -0
  90. package/dist/tools/environment.d.ts.map +1 -0
  91. package/dist/tools/environment.js +2 -2
  92. package/dist/tools/environment.js.map +1 -0
  93. package/dist/tools/foliage.d.ts.map +1 -0
  94. package/dist/tools/foliage.js.map +1 -0
  95. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  96. package/dist/tools/handlers/actor-handlers.d.ts.map +1 -0
  97. package/dist/tools/handlers/actor-handlers.js +6 -5
  98. package/dist/tools/handlers/actor-handlers.js.map +1 -0
  99. package/dist/tools/handlers/animation-handlers.d.ts.map +1 -0
  100. package/dist/tools/handlers/animation-handlers.js.map +1 -0
  101. package/dist/tools/handlers/argument-helper.d.ts.map +1 -0
  102. package/dist/tools/handlers/argument-helper.js.map +1 -0
  103. package/dist/tools/handlers/asset-handlers.d.ts.map +1 -0
  104. package/dist/tools/handlers/asset-handlers.js +5 -1
  105. package/dist/tools/handlers/asset-handlers.js.map +1 -0
  106. package/dist/tools/handlers/audio-handlers.d.ts.map +1 -0
  107. package/dist/tools/handlers/audio-handlers.js.map +1 -0
  108. package/dist/tools/handlers/blueprint-handlers.d.ts.map +1 -0
  109. package/dist/tools/handlers/blueprint-handlers.js +2 -1
  110. package/dist/tools/handlers/blueprint-handlers.js.map +1 -0
  111. package/dist/tools/handlers/common-handlers.d.ts.map +1 -0
  112. package/dist/tools/handlers/common-handlers.js.map +1 -0
  113. package/dist/tools/handlers/editor-handlers.d.ts.map +1 -0
  114. package/dist/tools/handlers/editor-handlers.js +12 -2
  115. package/dist/tools/handlers/editor-handlers.js.map +1 -0
  116. package/dist/tools/handlers/effect-handlers.d.ts.map +1 -0
  117. package/dist/tools/handlers/effect-handlers.js.map +1 -0
  118. package/dist/tools/handlers/environment-handlers.d.ts.map +1 -0
  119. package/dist/tools/handlers/environment-handlers.js.map +1 -0
  120. package/dist/tools/handlers/graph-handlers.d.ts.map +1 -0
  121. package/dist/tools/handlers/graph-handlers.js +61 -1
  122. package/dist/tools/handlers/graph-handlers.js.map +1 -0
  123. package/dist/tools/handlers/input-handlers.d.ts.map +1 -0
  124. package/dist/tools/handlers/input-handlers.js.map +1 -0
  125. package/dist/tools/handlers/inspect-handlers.d.ts.map +1 -0
  126. package/dist/tools/handlers/inspect-handlers.js.map +1 -0
  127. package/dist/tools/handlers/level-handlers.d.ts.map +1 -0
  128. package/dist/tools/handlers/level-handlers.js.map +1 -0
  129. package/dist/tools/handlers/lighting-handlers.d.ts.map +1 -0
  130. package/dist/tools/handlers/lighting-handlers.js +23 -1
  131. package/dist/tools/handlers/lighting-handlers.js.map +1 -0
  132. package/dist/tools/handlers/performance-handlers.d.ts.map +1 -0
  133. package/dist/tools/handlers/performance-handlers.js +15 -2
  134. package/dist/tools/handlers/performance-handlers.js.map +1 -0
  135. package/dist/tools/handlers/pipeline-handlers.d.ts.map +1 -0
  136. package/dist/tools/handlers/pipeline-handlers.js.map +1 -0
  137. package/dist/tools/handlers/sequence-handlers.d.ts.map +1 -0
  138. package/dist/tools/handlers/sequence-handlers.js.map +1 -0
  139. package/dist/tools/handlers/system-handlers.d.ts.map +1 -0
  140. package/dist/tools/handlers/system-handlers.js +16 -1
  141. package/dist/tools/handlers/system-handlers.js.map +1 -0
  142. package/dist/tools/input.d.ts.map +1 -0
  143. package/dist/tools/input.js +3 -1
  144. package/dist/tools/input.js.map +1 -0
  145. package/dist/tools/introspection.d.ts.map +1 -0
  146. package/dist/tools/introspection.js.map +1 -0
  147. package/dist/tools/landscape.d.ts.map +1 -0
  148. package/dist/tools/landscape.js +3 -1
  149. package/dist/tools/landscape.js.map +1 -0
  150. package/dist/tools/level.d.ts.map +1 -0
  151. package/dist/tools/level.js.map +1 -0
  152. package/dist/tools/lighting.d.ts.map +1 -0
  153. package/dist/tools/lighting.js +3 -1
  154. package/dist/tools/lighting.js.map +1 -0
  155. package/dist/tools/logs.d.ts.map +1 -0
  156. package/dist/tools/logs.js.map +1 -0
  157. package/dist/tools/materials.d.ts.map +1 -0
  158. package/dist/tools/materials.js +3 -1
  159. package/dist/tools/materials.js.map +1 -0
  160. package/dist/tools/niagara.d.ts.map +1 -0
  161. package/dist/tools/niagara.js +7 -5
  162. package/dist/tools/niagara.js.map +1 -0
  163. package/dist/tools/performance.d.ts.map +1 -0
  164. package/dist/tools/performance.js.map +1 -0
  165. package/dist/tools/physics.d.ts.map +1 -0
  166. package/dist/tools/physics.js +9 -7
  167. package/dist/tools/physics.js.map +1 -0
  168. package/dist/tools/property-dictionary.d.ts.map +1 -0
  169. package/dist/tools/property-dictionary.js.map +1 -0
  170. package/dist/tools/sequence.d.ts.map +1 -0
  171. package/dist/tools/sequence.js +3 -1
  172. package/dist/tools/sequence.js.map +1 -0
  173. package/dist/tools/tool-definition-utils.d.ts.map +1 -0
  174. package/dist/tools/tool-definition-utils.js.map +1 -0
  175. package/dist/tools/ui.d.ts.map +1 -0
  176. package/dist/tools/ui.js +3 -1
  177. package/dist/tools/ui.js.map +1 -0
  178. package/dist/types/automation-responses.d.ts.map +1 -0
  179. package/dist/types/automation-responses.js.map +1 -0
  180. package/dist/types/env.d.ts.map +1 -0
  181. package/dist/types/env.js.map +1 -0
  182. package/dist/types/handler-types.d.ts.map +1 -0
  183. package/dist/types/handler-types.js.map +1 -0
  184. package/dist/types/tool-interfaces.d.ts.map +1 -0
  185. package/dist/types/tool-interfaces.js.map +1 -0
  186. package/dist/types/tool-types.d.ts.map +1 -0
  187. package/dist/types/tool-types.js.map +1 -0
  188. package/dist/unreal-bridge.d.ts +1 -0
  189. package/dist/unreal-bridge.d.ts.map +1 -0
  190. package/dist/unreal-bridge.js +8 -0
  191. package/dist/unreal-bridge.js.map +1 -0
  192. package/dist/utils/command-validator.d.ts.map +1 -0
  193. package/dist/utils/command-validator.js.map +1 -0
  194. package/dist/utils/elicitation.d.ts.map +1 -0
  195. package/dist/utils/elicitation.js.map +1 -0
  196. package/dist/utils/error-handler.d.ts.map +1 -0
  197. package/dist/utils/error-handler.js.map +1 -0
  198. package/dist/utils/ini-reader.d.ts.map +1 -0
  199. package/dist/utils/ini-reader.js.map +1 -0
  200. package/dist/utils/logger.d.ts.map +1 -0
  201. package/dist/utils/logger.js.map +1 -0
  202. package/dist/utils/normalize.d.ts.map +1 -0
  203. package/dist/utils/normalize.js.map +1 -0
  204. package/dist/utils/path-security.d.ts.map +1 -0
  205. package/dist/utils/path-security.js.map +1 -0
  206. package/dist/utils/response-factory.d.ts.map +1 -0
  207. package/dist/utils/response-factory.js +3 -1
  208. package/dist/utils/response-factory.js.map +1 -0
  209. package/dist/utils/response-validator.d.ts.map +1 -0
  210. package/dist/utils/response-validator.js.map +1 -0
  211. package/dist/utils/result-helpers.d.ts.map +1 -0
  212. package/dist/utils/result-helpers.js.map +1 -0
  213. package/dist/utils/safe-json.d.ts.map +1 -0
  214. package/dist/utils/safe-json.js.map +1 -0
  215. package/dist/utils/unreal-command-queue.d.ts.map +1 -0
  216. package/dist/utils/unreal-command-queue.js.map +1 -0
  217. package/dist/utils/validation.d.ts.map +1 -0
  218. package/dist/utils/validation.js.map +1 -0
  219. package/dist/wasm/index.d.ts.map +1 -0
  220. package/dist/wasm/index.js.map +1 -0
  221. package/package.json +12 -34
  222. package/server.json +2 -2
  223. package/.dockerignore +0 -57
  224. package/.env.example +0 -26
  225. package/.env.production +0 -61
  226. package/.eslintrc.json +0 -0
  227. package/.eslintrc.override.json +0 -8
  228. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -94
  229. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  230. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -56
  231. package/.github/copilot-instructions.md +0 -478
  232. package/.github/dependabot.yml +0 -19
  233. package/.github/labeler.yml +0 -24
  234. package/.github/labels.yml +0 -70
  235. package/.github/pull_request_template.md +0 -42
  236. package/.github/release-drafter-config.yml +0 -51
  237. package/.github/workflows/auto-merge.yml +0 -38
  238. package/.github/workflows/ci.yml +0 -38
  239. package/.github/workflows/dependency-review.yml +0 -17
  240. package/.github/workflows/gemini-issue-triage.yml +0 -172
  241. package/.github/workflows/greetings.yml +0 -27
  242. package/.github/workflows/labeler.yml +0 -17
  243. package/.github/workflows/links.yml +0 -80
  244. package/.github/workflows/pr-size-labeler.yml +0 -137
  245. package/.github/workflows/publish-mcp.yml +0 -79
  246. package/.github/workflows/release-drafter.yml +0 -24
  247. package/.github/workflows/release.yml +0 -112
  248. package/.github/workflows/semantic-pull-request.yml +0 -35
  249. package/.github/workflows/smoke-test.yml +0 -36
  250. package/.github/workflows/stale.yml +0 -28
  251. package/CONTRIBUTING.md +0 -140
  252. package/Dockerfile +0 -37
  253. package/GEMINI.md +0 -115
  254. package/Public/Plugin_setup_guide.mp4 +0 -0
  255. package/Public/icon.png +0 -0
  256. package/claude_desktop_config_example.json +0 -15
  257. package/dist/types/responses.d.ts +0 -249
  258. package/dist/types/responses.js +0 -2
  259. package/docs/GraphQL-API.md +0 -888
  260. package/docs/Migration-Guide-v0.5.0.md +0 -684
  261. package/docs/Roadmap.md +0 -53
  262. package/docs/WebAssembly-Integration.md +0 -628
  263. package/docs/editor-plugin-extension.md +0 -370
  264. package/docs/handler-mapping.md +0 -249
  265. package/docs/native-automation-progress.md +0 -128
  266. package/docs/testing-guide.md +0 -423
  267. package/eslint.config.mjs +0 -68
  268. package/mcp-config-example.json +0 -14
  269. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +0 -8
  270. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +0 -64
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +0 -189
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +0 -22
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +0 -30
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +0 -1983
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +0 -72
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +0 -46
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +0 -846
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +0 -2393
  279. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +0 -300
  280. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +0 -2807
  281. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +0 -1087
  282. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +0 -488
  283. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +0 -643
  284. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +0 -31
  285. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +0 -1094
  286. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +0 -5750
  287. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +0 -152
  288. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +0 -2614
  289. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +0 -42
  290. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +0 -1237
  291. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +0 -1725
  292. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +0 -2265
  293. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +0 -954
  294. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +0 -209
  295. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +0 -41
  296. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +0 -1164
  297. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +0 -762
  298. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +0 -663
  299. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +0 -136
  300. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +0 -494
  301. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +0 -278
  302. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +0 -625
  303. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +0 -401
  304. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +0 -67
  305. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +0 -472
  306. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +0 -2634
  307. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +0 -189
  308. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +0 -917
  309. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +0 -39
  310. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +0 -2706
  311. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +0 -519
  312. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +0 -38
  313. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +0 -668
  314. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +0 -346
  315. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +0 -1345
  316. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +0 -149
  317. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -782
  318. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +0 -115
  319. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +0 -796
  320. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +0 -117
  321. package/scripts/check-unreal-connection.mjs +0 -19
  322. package/scripts/clean-tmp.js +0 -23
  323. package/scripts/patch-wasm.js +0 -26
  324. package/scripts/run-all-tests.mjs +0 -136
  325. package/scripts/smoke-test.ts +0 -94
  326. package/scripts/sync-mcp-plugin.js +0 -143
  327. package/scripts/test-no-plugin-alternates.mjs +0 -113
  328. package/scripts/validate-server.js +0 -46
  329. package/scripts/verify-automation-bridge.js +0 -200
  330. package/src/automation/bridge.ts +0 -630
  331. package/src/automation/connection-manager.ts +0 -148
  332. package/src/automation/handshake.ts +0 -99
  333. package/src/automation/index.ts +0 -2
  334. package/src/automation/message-handler.ts +0 -192
  335. package/src/automation/request-tracker.ts +0 -155
  336. package/src/automation/types.ts +0 -108
  337. package/src/cli.ts +0 -34
  338. package/src/config/class-aliases.ts +0 -65
  339. package/src/config.ts +0 -73
  340. package/src/constants.ts +0 -29
  341. package/src/graphql/loaders.ts +0 -244
  342. package/src/graphql/resolvers.ts +0 -1008
  343. package/src/graphql/schema.ts +0 -452
  344. package/src/graphql/server.ts +0 -156
  345. package/src/graphql/types.ts +0 -10
  346. package/src/handlers/resource-handlers.ts +0 -186
  347. package/src/index.ts +0 -243
  348. package/src/resources/actors.ts +0 -127
  349. package/src/resources/assets.ts +0 -286
  350. package/src/resources/levels.ts +0 -68
  351. package/src/server/resource-registry.ts +0 -47
  352. package/src/server/tool-registry.ts +0 -354
  353. package/src/server-setup.ts +0 -114
  354. package/src/services/health-monitor.ts +0 -132
  355. package/src/services/metrics-server.ts +0 -176
  356. package/src/tools/actors.ts +0 -564
  357. package/src/tools/animation.ts +0 -941
  358. package/src/tools/assets.ts +0 -394
  359. package/src/tools/audio.ts +0 -499
  360. package/src/tools/base-tool.ts +0 -52
  361. package/src/tools/behavior-tree.ts +0 -45
  362. package/src/tools/blueprint.ts +0 -940
  363. package/src/tools/consolidated-tool-definitions.ts +0 -1256
  364. package/src/tools/consolidated-tool-handlers.ts +0 -302
  365. package/src/tools/debug.ts +0 -622
  366. package/src/tools/dynamic-handler-registry.ts +0 -33
  367. package/src/tools/editor.ts +0 -435
  368. package/src/tools/engine.ts +0 -43
  369. package/src/tools/environment.ts +0 -281
  370. package/src/tools/foliage.ts +0 -596
  371. package/src/tools/handlers/actor-handlers.ts +0 -244
  372. package/src/tools/handlers/animation-handlers.ts +0 -237
  373. package/src/tools/handlers/argument-helper.ts +0 -142
  374. package/src/tools/handlers/asset-handlers.ts +0 -550
  375. package/src/tools/handlers/audio-handlers.ts +0 -194
  376. package/src/tools/handlers/blueprint-handlers.ts +0 -380
  377. package/src/tools/handlers/common-handlers.ts +0 -108
  378. package/src/tools/handlers/editor-handlers.ts +0 -124
  379. package/src/tools/handlers/effect-handlers.ts +0 -224
  380. package/src/tools/handlers/environment-handlers.ts +0 -183
  381. package/src/tools/handlers/graph-handlers.ts +0 -117
  382. package/src/tools/handlers/input-handlers.ts +0 -28
  383. package/src/tools/handlers/inspect-handlers.ts +0 -450
  384. package/src/tools/handlers/level-handlers.ts +0 -253
  385. package/src/tools/handlers/lighting-handlers.ts +0 -151
  386. package/src/tools/handlers/performance-handlers.ts +0 -132
  387. package/src/tools/handlers/pipeline-handlers.ts +0 -194
  388. package/src/tools/handlers/sequence-handlers.ts +0 -438
  389. package/src/tools/handlers/system-handlers.ts +0 -564
  390. package/src/tools/input.ts +0 -160
  391. package/src/tools/introspection.ts +0 -689
  392. package/src/tools/landscape.ts +0 -649
  393. package/src/tools/level.ts +0 -989
  394. package/src/tools/lighting.ts +0 -1052
  395. package/src/tools/logs.ts +0 -219
  396. package/src/tools/materials.ts +0 -295
  397. package/src/tools/niagara.ts +0 -485
  398. package/src/tools/performance.ts +0 -661
  399. package/src/tools/physics.ts +0 -679
  400. package/src/tools/property-dictionary.ts +0 -98
  401. package/src/tools/sequence.ts +0 -385
  402. package/src/tools/tool-definition-utils.ts +0 -35
  403. package/src/tools/ui.ts +0 -452
  404. package/src/types/automation-responses.ts +0 -119
  405. package/src/types/env.ts +0 -17
  406. package/src/types/handler-types.ts +0 -442
  407. package/src/types/responses.ts +0 -355
  408. package/src/types/tool-interfaces.ts +0 -250
  409. package/src/types/tool-types.ts +0 -575
  410. package/src/unreal-bridge.ts +0 -693
  411. package/src/utils/command-validator.ts +0 -139
  412. package/src/utils/elicitation.ts +0 -132
  413. package/src/utils/error-handler.ts +0 -287
  414. package/src/utils/ini-reader.ts +0 -86
  415. package/src/utils/logger.ts +0 -35
  416. package/src/utils/normalize.test.ts +0 -162
  417. package/src/utils/normalize.ts +0 -146
  418. package/src/utils/path-security.ts +0 -43
  419. package/src/utils/response-factory.ts +0 -44
  420. package/src/utils/response-validator.ts +0 -395
  421. package/src/utils/result-helpers.ts +0 -195
  422. package/src/utils/safe-json.test.ts +0 -90
  423. package/src/utils/safe-json.ts +0 -70
  424. package/src/utils/unreal-command-queue.ts +0 -166
  425. package/src/utils/validation.test.ts +0 -184
  426. package/src/utils/validation.ts +0 -312
  427. package/src/wasm/index.ts +0 -838
  428. package/test-server.mjs +0 -100
  429. package/tests/test-animation.mjs +0 -369
  430. package/tests/test-asset-advanced.mjs +0 -82
  431. package/tests/test-asset-graph.mjs +0 -311
  432. package/tests/test-audio.mjs +0 -417
  433. package/tests/test-automation-timeouts.mjs +0 -98
  434. package/tests/test-behavior-tree.mjs +0 -444
  435. package/tests/test-blueprint-graph.mjs +0 -410
  436. package/tests/test-blueprint.mjs +0 -577
  437. package/tests/test-client-mode.mjs +0 -86
  438. package/tests/test-console-command.mjs +0 -56
  439. package/tests/test-control-actor.mjs +0 -425
  440. package/tests/test-control-editor.mjs +0 -112
  441. package/tests/test-graphql.mjs +0 -372
  442. package/tests/test-input.mjs +0 -349
  443. package/tests/test-inspect.mjs +0 -302
  444. package/tests/test-landscape.mjs +0 -316
  445. package/tests/test-lighting.mjs +0 -428
  446. package/tests/test-manage-asset.mjs +0 -438
  447. package/tests/test-manage-level.mjs +0 -89
  448. package/tests/test-materials.mjs +0 -356
  449. package/tests/test-niagara.mjs +0 -185
  450. package/tests/test-no-inline-python.mjs +0 -122
  451. package/tests/test-performance.mjs +0 -539
  452. package/tests/test-plugin-handshake.mjs +0 -82
  453. package/tests/test-runner.mjs +0 -993
  454. package/tests/test-sequence.mjs +0 -104
  455. package/tests/test-system.mjs +0 -96
  456. package/tests/test-wasm.mjs +0 -283
  457. package/tests/test-world-partition.mjs +0 -215
  458. package/tsconfig.json +0 -56
  459. package/vitest.config.ts +0 -35
  460. package/wasm/Cargo.lock +0 -363
  461. package/wasm/Cargo.toml +0 -42
  462. package/wasm/LICENSE +0 -21
  463. package/wasm/README.md +0 -253
  464. package/wasm/src/dependency_resolver.rs +0 -377
  465. package/wasm/src/lib.rs +0 -153
  466. package/wasm/src/property_parser.rs +0 -271
  467. package/wasm/src/transform_math.rs +0 -396
  468. package/wasm/tests/integration.rs +0 -109
@@ -1,2614 +0,0 @@
1
- #include "Async/Async.h"
2
- #include "Dom/JsonObject.h"
3
- #include "GameFramework/Actor.h"
4
- #include "McpAutomationBridgeGlobals.h"
5
- #include "McpAutomationBridgeHelpers.h"
6
- #include "McpAutomationBridgeSubsystem.h"
7
- #include "Misc/ScopeExit.h"
8
-
9
- #if WITH_EDITOR
10
- #include "EditorAssetLibrary.h"
11
- #include "EngineUtils.h"
12
- #if __has_include("Subsystems/EditorActorSubsystem.h")
13
- #include "Subsystems/EditorActorSubsystem.h"
14
- #elif __has_include("EditorActorSubsystem.h")
15
- #include "EditorActorSubsystem.h"
16
- #endif
17
- #if __has_include("Subsystems/UnrealEditorSubsystem.h")
18
- #include "Subsystems/UnrealEditorSubsystem.h"
19
- #define MCP_HAS_UNREALEDITOR_SUBSYSTEM 1
20
- #elif __has_include("UnrealEditorSubsystem.h")
21
- #include "UnrealEditorSubsystem.h"
22
- #define MCP_HAS_UNREALEDITOR_SUBSYSTEM 1
23
- #endif
24
- #if __has_include("Subsystems/LevelEditorSubsystem.h")
25
- #include "Subsystems/LevelEditorSubsystem.h"
26
- #define MCP_HAS_LEVELEDITOR_SUBSYSTEM 1
27
- #elif __has_include("LevelEditorSubsystem.h")
28
- #include "LevelEditorSubsystem.h"
29
- #define MCP_HAS_LEVELEDITOR_SUBSYSTEM 1
30
- #endif
31
- #if __has_include("Subsystems/AssetEditorSubsystem.h")
32
- #include "Subsystems/AssetEditorSubsystem.h"
33
- #elif __has_include("AssetEditorSubsystem.h")
34
- #include "AssetEditorSubsystem.h"
35
- #endif
36
- // Additional editor headers for viewport control
37
- #include "Components/LightComponent.h"
38
- #include "Editor.h"
39
- #include "Modules/ModuleManager.h"
40
-
41
- #if __has_include("LevelEditor.h")
42
- #include "LevelEditor.h"
43
- #define MCP_HAS_LEVEL_EDITOR_MODULE 1
44
- #else
45
- #define MCP_HAS_LEVEL_EDITOR_MODULE 0
46
- #endif
47
- #if __has_include("Settings/LevelEditorPlaySettings.h")
48
- #include "Settings/LevelEditorPlaySettings.h"
49
- #define MCP_HAS_LEVEL_EDITOR_PLAY_SETTINGS 1
50
- #else
51
- #define MCP_HAS_LEVEL_EDITOR_PLAY_SETTINGS 0
52
- #endif
53
- #include "Components/PrimitiveComponent.h"
54
- #include "EditorViewportClient.h"
55
- #include "Engine/Blueprint.h"
56
-
57
- #if __has_include("FileHelpers.h")
58
- #include "FileHelpers.h"
59
- #endif
60
- #include "Animation/SkeletalMeshActor.h"
61
- #include "Components/ActorComponent.h"
62
- #include "Components/SceneComponent.h"
63
- #include "Components/StaticMeshComponent.h"
64
- #include "Engine/EngineTypes.h"
65
- #include "Engine/SkeletalMesh.h"
66
- #include "Engine/StaticMesh.h"
67
- #include "Engine/StaticMeshActor.h"
68
- #include "Engine/World.h"
69
- #include "Exporters/Exporter.h"
70
- #include "Misc/OutputDevice.h"
71
-
72
- #endif
73
-
74
- // Helper class for capturing export output
75
- /* UE5.6: Use built-in FStringOutputDevice from UnrealString.h */
76
-
77
- // Helper functions
78
- // (ExtractVectorField and ExtractRotatorField moved to
79
- // McpAutomationBridgeHelpers.h)
80
-
81
- AActor *UMcpAutomationBridgeSubsystem::FindActorByName(const FString &Target) {
82
- #if WITH_EDITOR
83
- if (Target.IsEmpty() || !GEditor)
84
- return nullptr;
85
-
86
- // Priority: PIE World if active
87
- if (GEditor->PlayWorld) {
88
- for (TActorIterator<AActor> It(GEditor->PlayWorld); It; ++It) {
89
- AActor *A = *It;
90
- if (!A)
91
- continue;
92
- if (A->GetActorLabel().Equals(Target, ESearchCase::IgnoreCase) ||
93
- A->GetName().Equals(Target, ESearchCase::IgnoreCase) ||
94
- A->GetPathName().Equals(Target, ESearchCase::IgnoreCase)) {
95
- return A;
96
- }
97
- }
98
- // If not found in PIE, do we fall back to Editor World?
99
- // Probably not, because interacting with Editor world during PIE is
100
- // confusing. But for "Editor subsystems" usage, we usually want Editor
101
- // world. Let's fallback if not found, just in case.
102
- }
103
-
104
- UEditorActorSubsystem *ActorSS =
105
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
106
- if (!ActorSS)
107
- return nullptr;
108
-
109
- TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
110
- AActor *ExactMatch = nullptr;
111
- TArray<AActor *> FuzzyMatches;
112
-
113
- for (AActor *A : AllActors) {
114
- if (!A)
115
- continue;
116
- if (A->GetActorLabel().Equals(Target, ESearchCase::IgnoreCase) ||
117
- A->GetName().Equals(Target, ESearchCase::IgnoreCase) ||
118
- A->GetPathName().Equals(Target, ESearchCase::IgnoreCase)) {
119
- ExactMatch = A;
120
- break;
121
- }
122
- // Collect fuzzy matches
123
- if (A->GetActorLabel().Contains(Target, ESearchCase::IgnoreCase)) {
124
- FuzzyMatches.Add(A);
125
- }
126
- }
127
-
128
- if (ExactMatch) {
129
- return ExactMatch;
130
- }
131
-
132
- // If no exact match, check fuzzy matches
133
- if (FuzzyMatches.Num() == 1) {
134
- return FuzzyMatches[0];
135
- } else if (FuzzyMatches.Num() > 1) {
136
- UE_LOG(LogMcpAutomationBridgeSubsystem, Warning,
137
- TEXT("FindActorByName: Ambiguous match for '%s'. Found %d matches."),
138
- *Target, FuzzyMatches.Num());
139
- }
140
-
141
- // Fallback: try to load as asset if it looks like a path
142
- if (Target.StartsWith(TEXT("/"))) {
143
- if (UObject *Obj = UEditorAssetLibrary::LoadAsset(Target)) {
144
- return Cast<AActor>(Obj);
145
- }
146
- }
147
- #endif
148
- return nullptr;
149
- }
150
-
151
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSpawn(
152
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
153
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
154
- #if WITH_EDITOR
155
- FString ClassPath;
156
- Payload->TryGetStringField(TEXT("classPath"), ClassPath);
157
- FString ActorName;
158
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
159
- FVector Location =
160
- ExtractVectorField(Payload, TEXT("location"), FVector::ZeroVector);
161
- FRotator Rotation =
162
- ExtractRotatorField(Payload, TEXT("rotation"), FRotator::ZeroRotator);
163
-
164
- UClass *ResolvedClass = nullptr;
165
- FString MeshPath;
166
- Payload->TryGetStringField(TEXT("meshPath"), MeshPath);
167
- UStaticMesh *ResolvedStaticMesh = nullptr;
168
- USkeletalMesh *ResolvedSkeletalMesh = nullptr;
169
-
170
- // Skip LoadAsset for script classes (e.g. /Script/Engine.CameraActor) to
171
- // avoid LogEditorAssetSubsystem errors
172
- if ((ClassPath.StartsWith(TEXT("/")) || ClassPath.Contains(TEXT("/"))) &&
173
- !ClassPath.StartsWith(TEXT("/Script/"))) {
174
- if (UObject *Loaded = UEditorAssetLibrary::LoadAsset(ClassPath)) {
175
- if (UBlueprint *BP = Cast<UBlueprint>(Loaded))
176
- ResolvedClass = BP->GeneratedClass;
177
- else if (UClass *C = Cast<UClass>(Loaded))
178
- ResolvedClass = C;
179
- else if (UStaticMesh *Mesh = Cast<UStaticMesh>(Loaded))
180
- ResolvedStaticMesh = Mesh;
181
- else if (USkeletalMesh *SkelMesh = Cast<USkeletalMesh>(Loaded))
182
- ResolvedSkeletalMesh = SkelMesh;
183
- }
184
- }
185
- if (!ResolvedClass && !ResolvedStaticMesh && !ResolvedSkeletalMesh)
186
- ResolvedClass = ResolveClassByName(ClassPath);
187
-
188
- // If explicit mesh path provided for a general spawn request
189
- if (!ResolvedStaticMesh && !ResolvedSkeletalMesh && !MeshPath.IsEmpty()) {
190
- if (UObject *MeshObj = UEditorAssetLibrary::LoadAsset(MeshPath)) {
191
- ResolvedStaticMesh = Cast<UStaticMesh>(MeshObj);
192
- if (!ResolvedStaticMesh)
193
- ResolvedSkeletalMesh = Cast<USkeletalMesh>(MeshObj);
194
- }
195
- }
196
-
197
- // Force StaticMeshActor if we have a resolved mesh, regardless of class input
198
- // (unless it's a specific subclass)
199
- bool bSpawnStaticMeshActor = (ResolvedStaticMesh != nullptr);
200
- bool bSpawnSkeletalMeshActor = (ResolvedSkeletalMesh != nullptr);
201
-
202
- if (!bSpawnStaticMeshActor && !bSpawnSkeletalMeshActor && ResolvedClass) {
203
- bSpawnStaticMeshActor =
204
- ResolvedClass->IsChildOf(AStaticMeshActor::StaticClass());
205
- if (!bSpawnStaticMeshActor)
206
- bSpawnSkeletalMeshActor =
207
- ResolvedClass->IsChildOf(ASkeletalMeshActor::StaticClass());
208
- }
209
-
210
- // Explicitly use StaticMeshActor class if we have a mesh but no class, or if
211
- // we decided to spawn a static mesh actor
212
- if (bSpawnStaticMeshActor && !ResolvedClass) {
213
- ResolvedClass = AStaticMeshActor::StaticClass();
214
- } else if (bSpawnSkeletalMeshActor && !ResolvedClass) {
215
- ResolvedClass = ASkeletalMeshActor::StaticClass();
216
- }
217
-
218
- if (!ResolvedClass && !bSpawnStaticMeshActor && !bSpawnSkeletalMeshActor) {
219
- const FString ErrorMsg =
220
- FString::Printf(TEXT("Class not found: %s. Verify plugin is enabled if "
221
- "using a plugin class."),
222
- *ClassPath);
223
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("CLASS_NOT_FOUND"),
224
- ErrorMsg);
225
- return true;
226
- }
227
-
228
- UEditorActorSubsystem *ActorSS =
229
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
230
- AActor *Spawned = nullptr;
231
-
232
- // Support PIE spawning
233
- UWorld *TargetWorld = (GEditor->PlayWorld) ? GEditor->PlayWorld : nullptr;
234
-
235
- if (TargetWorld) {
236
- // PIE Path
237
- FActorSpawnParameters SpawnParams;
238
- SpawnParams.SpawnCollisionHandlingOverride =
239
- ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
240
-
241
- UClass *ClassToSpawn =
242
- ResolvedClass
243
- ? ResolvedClass
244
- : (bSpawnStaticMeshActor ? AStaticMeshActor::StaticClass()
245
- : (bSpawnSkeletalMeshActor
246
- ? ASkeletalMeshActor::StaticClass()
247
- : AActor::StaticClass()));
248
- Spawned = TargetWorld->SpawnActor(ClassToSpawn, &Location, &Rotation,
249
- SpawnParams);
250
-
251
- if (Spawned) {
252
- if (bSpawnStaticMeshActor) {
253
- if (AStaticMeshActor *StaticMeshActor =
254
- Cast<AStaticMeshActor>(Spawned)) {
255
- if (UStaticMeshComponent *MeshComponent =
256
- StaticMeshActor->GetStaticMeshComponent()) {
257
- if (ResolvedStaticMesh) {
258
- MeshComponent->SetStaticMesh(ResolvedStaticMesh);
259
- }
260
- MeshComponent->SetMobility(EComponentMobility::Movable);
261
- // PIE actors don't need MarkRenderStateDirty in the same way, but
262
- // it doesn't hurt
263
- }
264
- }
265
- } else if (bSpawnSkeletalMeshActor) {
266
- if (ASkeletalMeshActor *SkelActor = Cast<ASkeletalMeshActor>(Spawned)) {
267
- if (USkeletalMeshComponent *SkelComp =
268
- SkelActor->GetSkeletalMeshComponent()) {
269
- if (ResolvedSkeletalMesh) {
270
- SkelComp->SetSkeletalMesh(ResolvedSkeletalMesh);
271
- }
272
- SkelComp->SetMobility(EComponentMobility::Movable);
273
- }
274
- }
275
- }
276
- }
277
- } else {
278
- // Editor Path
279
- if (bSpawnStaticMeshActor) {
280
- Spawned = ActorSS->SpawnActorFromClass(
281
- ResolvedClass ? ResolvedClass : AStaticMeshActor::StaticClass(),
282
- Location, Rotation);
283
- if (Spawned) {
284
- Spawned->SetActorLocationAndRotation(Location, Rotation, false, nullptr,
285
- ETeleportType::TeleportPhysics);
286
- if (AStaticMeshActor *StaticMeshActor =
287
- Cast<AStaticMeshActor>(Spawned)) {
288
- if (UStaticMeshComponent *MeshComponent =
289
- StaticMeshActor->GetStaticMeshComponent()) {
290
- if (ResolvedStaticMesh) {
291
- MeshComponent->SetStaticMesh(ResolvedStaticMesh);
292
- }
293
- MeshComponent->SetMobility(EComponentMobility::Movable);
294
- MeshComponent->MarkRenderStateDirty();
295
- }
296
- }
297
- }
298
- } else if (bSpawnSkeletalMeshActor) {
299
- Spawned = ActorSS->SpawnActorFromClass(
300
- ResolvedClass ? ResolvedClass : ASkeletalMeshActor::StaticClass(),
301
- Location, Rotation);
302
- if (Spawned) {
303
- Spawned->SetActorLocationAndRotation(Location, Rotation, false, nullptr,
304
- ETeleportType::TeleportPhysics);
305
- if (ASkeletalMeshActor *SkelActor = Cast<ASkeletalMeshActor>(Spawned)) {
306
- if (USkeletalMeshComponent *SkelComp =
307
- SkelActor->GetSkeletalMeshComponent()) {
308
- if (ResolvedSkeletalMesh) {
309
- SkelComp->SetSkeletalMesh(ResolvedSkeletalMesh);
310
- }
311
- SkelComp->SetMobility(EComponentMobility::Movable);
312
- SkelComp->MarkRenderStateDirty();
313
- }
314
- }
315
- }
316
- } else {
317
- Spawned = ActorSS->SpawnActorFromClass(ResolvedClass, Location, Rotation);
318
- if (Spawned) {
319
- Spawned->SetActorLocationAndRotation(Location, Rotation, false, nullptr,
320
- ETeleportType::TeleportPhysics);
321
- }
322
- }
323
- }
324
-
325
- if (!Spawned) {
326
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("SPAWN_FAILED"),
327
- TEXT("Failed to spawn actor"));
328
-
329
- return true;
330
- }
331
-
332
- if (!ActorName.IsEmpty()) {
333
- Spawned->SetActorLabel(ActorName);
334
- } else {
335
- // Auto-generate a friendly label from the mesh or class name
336
- FString BaseName;
337
- if (ResolvedStaticMesh) {
338
- BaseName = ResolvedStaticMesh->GetName();
339
- } else if (ResolvedSkeletalMesh) {
340
- BaseName = ResolvedSkeletalMesh->GetName();
341
- } else if (ResolvedClass) {
342
- BaseName = ResolvedClass->GetName();
343
- if (BaseName.EndsWith(TEXT("_C"))) {
344
- BaseName.RemoveFromEnd(TEXT("_C"));
345
- }
346
- } else {
347
- BaseName = TEXT("Actor");
348
- }
349
- Spawned->SetActorLabel(BaseName);
350
- }
351
-
352
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
353
- Data->SetStringField(TEXT("id"), Spawned->GetActorLabel());
354
- Data->SetStringField(TEXT("name"), Spawned->GetActorLabel());
355
- Data->SetStringField(TEXT("objectPath"), Spawned->GetPathName());
356
- // Provide the resolved class path useful for referencing
357
- if (ResolvedClass)
358
- Data->SetStringField(TEXT("classPath"), ResolvedClass->GetPathName());
359
- else
360
- Data->SetStringField(TEXT("classPath"), ClassPath);
361
-
362
- if (ResolvedStaticMesh)
363
- Data->SetStringField(TEXT("meshPath"), ResolvedStaticMesh->GetPathName());
364
- else if (ResolvedSkeletalMesh)
365
- Data->SetStringField(TEXT("meshPath"), ResolvedSkeletalMesh->GetPathName());
366
-
367
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
368
- TEXT("ControlActor: Spawned actor '%s'"), *Spawned->GetActorLabel());
369
-
370
- SendAutomationResponse(Socket, RequestId, true, TEXT("Actor spawned"), Data);
371
- return true;
372
-
373
- #else
374
- return false;
375
- #endif
376
- }
377
-
378
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSpawnBlueprint(
379
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
380
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
381
- #if WITH_EDITOR
382
- FString BlueprintPath;
383
- Payload->TryGetStringField(TEXT("blueprintPath"), BlueprintPath);
384
- if (BlueprintPath.IsEmpty()) {
385
- SendAutomationResponse(Socket, RequestId, false,
386
- TEXT("Blueprint path required"), nullptr,
387
- TEXT("INVALID_ARGUMENT"));
388
- return true;
389
- }
390
-
391
- FString ActorName;
392
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
393
- FVector Location =
394
- ExtractVectorField(Payload, TEXT("location"), FVector::ZeroVector);
395
- FRotator Rotation =
396
- ExtractRotatorField(Payload, TEXT("rotation"), FRotator::ZeroRotator);
397
-
398
- UClass *ResolvedClass = nullptr;
399
-
400
- // Prefer the same blueprint resolution heuristics used by manage_blueprint
401
- // so that short names and package paths behave consistently.
402
- FString NormalizedPath;
403
- FString LoadError;
404
- if (!BlueprintPath.IsEmpty()) {
405
- UBlueprint *BlueprintAsset =
406
- LoadBlueprintAsset(BlueprintPath, NormalizedPath, LoadError);
407
- if (BlueprintAsset && BlueprintAsset->GeneratedClass) {
408
- ResolvedClass = BlueprintAsset->GeneratedClass;
409
- }
410
- }
411
-
412
- if (!ResolvedClass && (BlueprintPath.StartsWith(TEXT("/")) ||
413
- BlueprintPath.Contains(TEXT("/")))) {
414
- if (UObject *Loaded = UEditorAssetLibrary::LoadAsset(BlueprintPath)) {
415
- if (UBlueprint *BP = Cast<UBlueprint>(Loaded))
416
- ResolvedClass = BP->GeneratedClass;
417
- else if (UClass *C = Cast<UClass>(Loaded))
418
- ResolvedClass = C;
419
- }
420
- }
421
- if (!ResolvedClass)
422
- ResolvedClass = ResolveClassByName(BlueprintPath);
423
-
424
- if (!ResolvedClass) {
425
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
426
- Resp->SetStringField(TEXT("error"), TEXT("Blueprint class not found"));
427
- SendAutomationResponse(Socket, RequestId, false,
428
- TEXT("Blueprint class not found"), Resp,
429
- TEXT("CLASS_NOT_FOUND"));
430
- return true;
431
- }
432
-
433
- UEditorActorSubsystem *ActorSS =
434
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
435
-
436
- // Debug log the received location
437
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
438
- TEXT("spawn_blueprint: Location=(%f, %f, %f) Rotation=(%f, %f, %f)"),
439
- Location.X, Location.Y, Location.Z, Rotation.Pitch, Rotation.Yaw,
440
- Rotation.Roll);
441
-
442
- AActor *Spawned = nullptr;
443
- UWorld *TargetWorld = (GEditor->PlayWorld) ? GEditor->PlayWorld : nullptr;
444
-
445
- if (TargetWorld) {
446
- // PIE Path
447
- FActorSpawnParameters SpawnParams;
448
- SpawnParams.SpawnCollisionHandlingOverride =
449
- ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
450
- Spawned = TargetWorld->SpawnActor(ResolvedClass, &Location, &Rotation,
451
- SpawnParams);
452
- // Ensure physics/teleport if needed, though SpawnActor should handle it.
453
- } else {
454
- // Editor Path
455
- Spawned = ActorSS->SpawnActorFromClass(ResolvedClass, Location, Rotation);
456
- // Explicitly set location and rotation in case SpawnActorFromClass didn't
457
- // apply them correctly (legacy fix)
458
- if (Spawned) {
459
- Spawned->SetActorLocationAndRotation(Location, Rotation, false, nullptr,
460
- ETeleportType::TeleportPhysics);
461
- }
462
- }
463
-
464
- if (!Spawned) {
465
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
466
- Resp->SetStringField(TEXT("error"), TEXT("Failed to spawn blueprint"));
467
- SendAutomationResponse(Socket, RequestId, false,
468
- TEXT("Failed to spawn blueprint"), Resp,
469
- TEXT("SPAWN_FAILED"));
470
- return true;
471
- }
472
-
473
- if (!ActorName.IsEmpty())
474
- Spawned->SetActorLabel(ActorName);
475
-
476
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
477
- Resp->SetBoolField(TEXT("success"), true);
478
- Resp->SetStringField(TEXT("actorName"), Spawned->GetActorLabel());
479
- Resp->SetStringField(TEXT("actorPath"), Spawned->GetPathName());
480
- Resp->SetStringField(TEXT("classPath"), ResolvedClass->GetPathName());
481
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
482
- TEXT("ControlActor: Spawned blueprint '%s'"),
483
- *Spawned->GetActorLabel());
484
- SendAutomationResponse(Socket, RequestId, true, TEXT("Blueprint spawned"),
485
- Resp, FString());
486
- return true;
487
- #else
488
- return false;
489
- #endif
490
- }
491
-
492
- bool UMcpAutomationBridgeSubsystem::HandleControlActorDelete(
493
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
494
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
495
- #if WITH_EDITOR
496
- TArray<FString> Targets;
497
- const TArray<TSharedPtr<FJsonValue>> *NamesArray = nullptr;
498
- if (Payload->TryGetArrayField(TEXT("actorNames"), NamesArray) && NamesArray) {
499
- for (const TSharedPtr<FJsonValue> &Entry : *NamesArray) {
500
- if (Entry.IsValid() && Entry->Type == EJson::String) {
501
- const FString Value = Entry->AsString().TrimStartAndEnd();
502
- if (!Value.IsEmpty())
503
- Targets.AddUnique(Value);
504
- }
505
- }
506
- }
507
-
508
- FString SingleName;
509
- if (Targets.Num() == 0) {
510
- Payload->TryGetStringField(TEXT("actorName"), SingleName);
511
- if (!SingleName.IsEmpty())
512
- Targets.AddUnique(SingleName);
513
- }
514
-
515
- if (Targets.Num() == 0) {
516
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("INVALID_ARGUMENT"),
517
- TEXT("actorName or actorNames required"));
518
- return true;
519
- }
520
-
521
- UEditorActorSubsystem *ActorSS =
522
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
523
- TArray<FString> Deleted;
524
- TArray<FString> Missing;
525
-
526
- for (const FString &Name : Targets) {
527
- AActor *Found = FindActorByName(Name);
528
- if (!Found) {
529
- Missing.Add(Name);
530
- continue;
531
- }
532
- if (ActorSS->DestroyActor(Found)) {
533
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
534
- TEXT("ControlActor: Deleted actor '%s'"), *Name);
535
- Deleted.Add(Name);
536
- } else
537
- Missing.Add(Name);
538
- }
539
-
540
- const bool bAllDeleted = Missing.Num() == 0;
541
- const bool bAnyDeleted = Deleted.Num() > 0;
542
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
543
- Resp->SetBoolField(TEXT("success"), bAllDeleted);
544
- Resp->SetNumberField(TEXT("deletedCount"), Deleted.Num());
545
-
546
- TArray<TSharedPtr<FJsonValue>> DeletedArray;
547
- for (const FString &Name : Deleted)
548
- DeletedArray.Add(MakeShared<FJsonValueString>(Name));
549
- Resp->SetArrayField(TEXT("deleted"), DeletedArray);
550
-
551
- if (Missing.Num() > 0) {
552
- TArray<TSharedPtr<FJsonValue>> MissingArray;
553
- for (const FString &Name : Missing)
554
- MissingArray.Add(MakeShared<FJsonValueString>(Name));
555
- Resp->SetArrayField(TEXT("missing"), MissingArray);
556
- }
557
-
558
- FString Message;
559
- FString ErrorCode;
560
- if (!bAnyDeleted && Missing.Num() > 0) {
561
- Message = TEXT("Actors not found");
562
- ErrorCode = TEXT("NOT_FOUND");
563
- } else {
564
- Message = bAllDeleted ? TEXT("Actors deleted")
565
- : TEXT("Some actors could not be deleted");
566
- ErrorCode = bAllDeleted ? FString() : TEXT("DELETE_PARTIAL");
567
- }
568
-
569
- if (!bAllDeleted && Missing.Num() > 0 && !bAnyDeleted) {
570
- SendStandardErrorResponse(this, Socket, RequestId, ErrorCode, Message);
571
- } else {
572
- SendStandardSuccessResponse(this, Socket, RequestId, Message, Resp);
573
- }
574
- return true;
575
- #else
576
- return false;
577
- #endif
578
- }
579
-
580
- bool UMcpAutomationBridgeSubsystem::HandleControlActorApplyForce(
581
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
582
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
583
- #if WITH_EDITOR
584
- FString TargetName;
585
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
586
- FVector ForceVector =
587
- ExtractVectorField(Payload, TEXT("force"), FVector::ZeroVector);
588
-
589
- AActor *Found = FindActorByName(TargetName);
590
- if (!Found) {
591
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
592
- nullptr, TEXT("ACTOR_NOT_FOUND"));
593
- return true;
594
- }
595
-
596
- UPrimitiveComponent *Prim =
597
- Found->FindComponentByClass<UPrimitiveComponent>();
598
- if (!Prim) {
599
- if (UStaticMeshComponent *SMC =
600
- Found->FindComponentByClass<UStaticMeshComponent>())
601
- Prim = SMC;
602
- }
603
-
604
- if (!Prim) {
605
- SendAutomationResponse(Socket, RequestId, false,
606
- TEXT("No component to apply force"), nullptr,
607
- TEXT("NO_COMPONENT"));
608
- return true;
609
- }
610
-
611
- if (Prim->Mobility == EComponentMobility::Static)
612
- Prim->SetMobility(EComponentMobility::Movable);
613
-
614
- // Ensure collision is enabled for physics
615
- if (Prim->GetCollisionEnabled() == ECollisionEnabled::NoCollision) {
616
- Prim->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
617
- }
618
-
619
- // Check if collision geometry exists (common failure for empty
620
- // StaticMeshActors)
621
- if (UStaticMeshComponent *SMC = Cast<UStaticMeshComponent>(Prim)) {
622
- if (!SMC->GetStaticMesh()) {
623
- SendStandardErrorResponse(
624
- this, Socket, RequestId, TEXT("PHYSICS_FAILED"),
625
- TEXT("StaticMeshComponent has no StaticMesh assigned."), nullptr);
626
- return true;
627
- }
628
- if (!SMC->GetStaticMesh()->GetBodySetup()) {
629
- SendStandardErrorResponse(
630
- this, Socket, RequestId, TEXT("PHYSICS_FAILED"),
631
- TEXT("StaticMesh has no collision geometry (BodySetup is null)."),
632
- nullptr);
633
- return true;
634
- }
635
- }
636
-
637
- if (!Prim->IsSimulatingPhysics()) {
638
- Prim->SetSimulatePhysics(true);
639
- // Must recreate physics state for the body to be properly initialized in
640
- // Editor
641
- Prim->RecreatePhysicsState();
642
- }
643
-
644
- Prim->AddForce(ForceVector);
645
- Prim->WakeAllRigidBodies();
646
- Prim->MarkRenderStateDirty();
647
-
648
- // Verify physics state
649
- const bool bIsSimulating = Prim->IsSimulatingPhysics();
650
-
651
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
652
- Data->SetBoolField(TEXT("simulating"), bIsSimulating);
653
- TArray<TSharedPtr<FJsonValue>> Applied;
654
- Applied.Add(MakeShared<FJsonValueNumber>(ForceVector.X));
655
- Applied.Add(MakeShared<FJsonValueNumber>(ForceVector.Y));
656
- Applied.Add(MakeShared<FJsonValueNumber>(ForceVector.Z));
657
- Data->SetArrayField(TEXT("applied"), Applied);
658
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
659
-
660
- if (!bIsSimulating) {
661
- FString FailureReason = TEXT("Failed to enable physics simulation.");
662
- if (Prim->GetCollisionEnabled() == ECollisionEnabled::NoCollision) {
663
- FailureReason += TEXT(" Collision is disabled.");
664
- } else if (Prim->Mobility != EComponentMobility::Movable) {
665
- FailureReason += TEXT(" Component is not Movable.");
666
- }
667
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("PHYSICS_FAILED"),
668
- FailureReason, Data);
669
- return true;
670
- }
671
-
672
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
673
- TEXT("ControlActor: Applied force to '%s'"), *Found->GetActorLabel());
674
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Force applied"),
675
- Data);
676
- return true;
677
- #else
678
- return false;
679
- #endif
680
- }
681
-
682
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSetTransform(
683
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
684
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
685
- #if WITH_EDITOR
686
- FString TargetName;
687
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
688
- if (TargetName.IsEmpty()) {
689
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
690
- nullptr, TEXT("INVALID_ARGUMENT"));
691
- return true;
692
- }
693
-
694
- AActor *Found = FindActorByName(TargetName);
695
- if (!Found) {
696
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
697
- nullptr, TEXT("ACTOR_NOT_FOUND"));
698
- return true;
699
- }
700
-
701
- FVector Location =
702
- ExtractVectorField(Payload, TEXT("location"), Found->GetActorLocation());
703
- FRotator Rotation =
704
- ExtractRotatorField(Payload, TEXT("rotation"), Found->GetActorRotation());
705
- FVector Scale =
706
- ExtractVectorField(Payload, TEXT("scale"), Found->GetActorScale3D());
707
-
708
- Found->Modify();
709
- Found->SetActorLocation(Location, false, nullptr,
710
- ETeleportType::TeleportPhysics);
711
- Found->SetActorRotation(Rotation, ETeleportType::TeleportPhysics);
712
- Found->SetActorScale3D(Scale);
713
- Found->MarkComponentsRenderStateDirty();
714
- Found->MarkPackageDirty();
715
-
716
- // Verify transform
717
- const FVector NewLoc = Found->GetActorLocation();
718
- const FRotator NewRot = Found->GetActorRotation();
719
- const FVector NewScale = Found->GetActorScale3D();
720
-
721
- const bool bLocMatch = NewLoc.Equals(Location, 1.0f); // 1 unit tolerance
722
- // Rotation comparison is tricky due to normalization, skipping strict check
723
- // for now but logging if very different
724
- const bool bScaleMatch = NewScale.Equals(Scale, 0.01f);
725
-
726
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
727
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
728
-
729
- auto MakeArray = [](const FVector &Vec) {
730
- TArray<TSharedPtr<FJsonValue>> Arr;
731
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.X));
732
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Y));
733
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Z));
734
- return Arr;
735
- };
736
-
737
- Data->SetArrayField(TEXT("location"), MakeArray(NewLoc));
738
- Data->SetArrayField(TEXT("scale"), MakeArray(NewScale));
739
-
740
- if (!bLocMatch || !bScaleMatch) {
741
- SendStandardErrorResponse(this, Socket, RequestId,
742
- TEXT("TRANSFORM_MISMATCH"),
743
- TEXT("Failed to set transform exactly"), Data);
744
- return true;
745
- }
746
-
747
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
748
- TEXT("ControlActor: Set transform for '%s'"), *Found->GetActorLabel());
749
- SendStandardSuccessResponse(this, Socket, RequestId,
750
- TEXT("Actor transform updated"), Data);
751
- return true;
752
- #else
753
- return false;
754
- #endif
755
- }
756
-
757
- bool UMcpAutomationBridgeSubsystem::HandleControlActorGetTransform(
758
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
759
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
760
- #if WITH_EDITOR
761
- FString TargetName;
762
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
763
- if (TargetName.IsEmpty()) {
764
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("INVALID_ARGUMENT"),
765
- TEXT("actorName required"));
766
- return true;
767
- }
768
-
769
- AActor *Found = FindActorByName(TargetName);
770
- if (!Found) {
771
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("ACTOR_NOT_FOUND"),
772
- TEXT("Actor not found"));
773
- return true;
774
- }
775
-
776
- const FTransform Current = Found->GetActorTransform();
777
- const FVector Location = Current.GetLocation();
778
- const FRotator Rotation = Current.GetRotation().Rotator();
779
- const FVector Scale = Current.GetScale3D();
780
-
781
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
782
-
783
- auto MakeArray = [](const FVector &Vec) {
784
- TArray<TSharedPtr<FJsonValue>> Arr;
785
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.X));
786
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Y));
787
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Z));
788
- return Arr;
789
- };
790
-
791
- Data->SetArrayField(TEXT("location"), MakeArray(Location));
792
- TArray<TSharedPtr<FJsonValue>> RotArray;
793
- RotArray.Add(MakeShared<FJsonValueNumber>(Rotation.Pitch));
794
- RotArray.Add(MakeShared<FJsonValueNumber>(Rotation.Yaw));
795
- RotArray.Add(MakeShared<FJsonValueNumber>(Rotation.Roll));
796
- Data->SetArrayField(TEXT("rotation"), RotArray);
797
- Data->SetArrayField(TEXT("scale"), MakeArray(Scale));
798
-
799
- SendStandardSuccessResponse(this, Socket, RequestId,
800
- TEXT("Actor transform retrieved"), Data);
801
- return true;
802
- #else
803
- return false;
804
- #endif
805
- }
806
-
807
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSetVisibility(
808
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
809
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
810
- #if WITH_EDITOR
811
- FString TargetName;
812
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
813
- if (TargetName.IsEmpty()) {
814
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
815
- nullptr, TEXT("INVALID_ARGUMENT"));
816
- return true;
817
- }
818
-
819
- bool bVisible = true;
820
- if (Payload->HasField(TEXT("visible")))
821
- Payload->TryGetBoolField(TEXT("visible"), bVisible);
822
-
823
- AActor *Found = FindActorByName(TargetName);
824
- if (!Found) {
825
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
826
- nullptr, TEXT("ACTOR_NOT_FOUND"));
827
- return true;
828
- }
829
-
830
- Found->Modify();
831
- Found->SetActorHiddenInGame(!bVisible);
832
- Found->SetActorEnableCollision(bVisible);
833
-
834
- for (UActorComponent *Comp : Found->GetComponents()) {
835
- if (!Comp)
836
- continue;
837
- if (UPrimitiveComponent *Prim = Cast<UPrimitiveComponent>(Comp)) {
838
- Prim->SetVisibility(bVisible, true);
839
- Prim->SetHiddenInGame(!bVisible);
840
- }
841
- }
842
-
843
- Found->MarkComponentsRenderStateDirty();
844
- Found->MarkPackageDirty();
845
-
846
- // Verify visibility state
847
- const bool bIsHidden = Found->IsHidden();
848
- const bool bStateMatches = (bIsHidden == !bVisible);
849
-
850
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
851
- Data->SetBoolField(TEXT("visible"), !bIsHidden);
852
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
853
-
854
- if (!bStateMatches) {
855
- SendStandardErrorResponse(this, Socket, RequestId,
856
- TEXT("VISIBILITY_MISMATCH"),
857
- TEXT("Failed to set actor visibility"), Data);
858
- return true;
859
- }
860
-
861
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
862
- TEXT("ControlActor: Set visibility to %s for '%s'"),
863
- bVisible ? TEXT("True") : TEXT("False"), *Found->GetActorLabel());
864
- SendStandardSuccessResponse(this, Socket, RequestId,
865
- TEXT("Actor visibility updated"), Data);
866
- return true;
867
- #else
868
- return false;
869
- #endif
870
- }
871
-
872
- bool UMcpAutomationBridgeSubsystem::HandleControlActorAddComponent(
873
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
874
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
875
- #if WITH_EDITOR
876
- FString TargetName;
877
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
878
- if (TargetName.IsEmpty()) {
879
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
880
- nullptr, TEXT("INVALID_ARGUMENT"));
881
- return true;
882
- }
883
-
884
- FString ComponentType;
885
- Payload->TryGetStringField(TEXT("componentType"), ComponentType);
886
- if (ComponentType.IsEmpty()) {
887
- SendAutomationResponse(Socket, RequestId, false,
888
- TEXT("componentType required"), nullptr,
889
- TEXT("INVALID_ARGUMENT"));
890
- return true;
891
- }
892
-
893
- FString ComponentName;
894
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
895
-
896
- AActor *Found = FindActorByName(TargetName);
897
- if (!Found) {
898
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
899
- nullptr, TEXT("ACTOR_NOT_FOUND"));
900
- return true;
901
- }
902
-
903
- UClass *ComponentClass = ResolveClassByName(ComponentType);
904
- if (!ComponentClass ||
905
- !ComponentClass->IsChildOf(UActorComponent::StaticClass())) {
906
- SendAutomationResponse(Socket, RequestId, false,
907
- TEXT("Component class not found"), nullptr,
908
- TEXT("CLASS_NOT_FOUND"));
909
- return true;
910
- }
911
-
912
- if (ComponentName.TrimStartAndEnd().IsEmpty())
913
- ComponentName = FString::Printf(TEXT("%s_%d"), *ComponentClass->GetName(),
914
- FMath::Rand());
915
-
916
- FName DesiredName = FName(*ComponentName);
917
- UActorComponent *NewComponent = NewObject<UActorComponent>(
918
- Found, ComponentClass, DesiredName, RF_Transactional);
919
- if (!NewComponent) {
920
- SendAutomationResponse(Socket, RequestId, false,
921
- TEXT("Failed to create component"), nullptr,
922
- TEXT("CREATE_COMPONENT_FAILED"));
923
- return true;
924
- }
925
-
926
- Found->Modify();
927
- NewComponent->SetFlags(RF_Transactional);
928
- Found->AddInstanceComponent(NewComponent);
929
- NewComponent->OnComponentCreated();
930
-
931
- if (USceneComponent *SceneComp = Cast<USceneComponent>(NewComponent)) {
932
- if (Found->GetRootComponent() && !SceneComp->GetAttachParent()) {
933
- SceneComp->SetupAttachment(Found->GetRootComponent());
934
- }
935
- }
936
-
937
- // Force lights to be movable to ensure they work without baking (Issue #6
938
- // fix) We check for "LightComponent" class name to avoid dependency issues if
939
- // header is obscure, but ULightComponent is standard.
940
- if (NewComponent->IsA(ULightComponent::StaticClass())) {
941
- if (USceneComponent *SC = Cast<USceneComponent>(NewComponent)) {
942
- SC->SetMobility(EComponentMobility::Movable);
943
- }
944
- }
945
-
946
- // Special handling for StaticMeshComponent meshPath convenience
947
- if (UStaticMeshComponent *SMC = Cast<UStaticMeshComponent>(NewComponent)) {
948
- FString MeshPath;
949
- if (Payload->TryGetStringField(TEXT("meshPath"), MeshPath) &&
950
- !MeshPath.IsEmpty()) {
951
- if (UObject *LoadedMesh = UEditorAssetLibrary::LoadAsset(MeshPath)) {
952
- if (UStaticMesh *Mesh = Cast<UStaticMesh>(LoadedMesh)) {
953
- SMC->SetStaticMesh(Mesh);
954
- }
955
- }
956
- }
957
- }
958
-
959
- TArray<FString> AppliedProperties;
960
- TArray<FString> PropertyWarnings;
961
- const TSharedPtr<FJsonObject> *PropertiesPtr = nullptr;
962
- if (Payload->TryGetObjectField(TEXT("properties"), PropertiesPtr) &&
963
- PropertiesPtr && (*PropertiesPtr).IsValid()) {
964
- for (const auto &Pair : (*PropertiesPtr)->Values) {
965
- FProperty *Property = ComponentClass->FindPropertyByName(*Pair.Key);
966
- if (!Property) {
967
- PropertyWarnings.Add(
968
- FString::Printf(TEXT("Property not found: %s"), *Pair.Key));
969
- continue;
970
- }
971
- FString ApplyError;
972
- if (ApplyJsonValueToProperty(NewComponent, Property, Pair.Value,
973
- ApplyError))
974
- AppliedProperties.Add(Pair.Key);
975
- else
976
- PropertyWarnings.Add(FString::Printf(TEXT("Failed to set %s: %s"),
977
- *Pair.Key, *ApplyError));
978
- }
979
- }
980
-
981
- NewComponent->RegisterComponent();
982
- if (USceneComponent *SceneComp = Cast<USceneComponent>(NewComponent))
983
- SceneComp->UpdateComponentToWorld();
984
- NewComponent->MarkPackageDirty();
985
- Found->MarkPackageDirty();
986
-
987
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
988
- Resp->SetBoolField(TEXT("success"), true);
989
- Resp->SetStringField(TEXT("componentName"), NewComponent->GetName());
990
- Resp->SetStringField(TEXT("componentPath"), NewComponent->GetPathName());
991
- Resp->SetStringField(TEXT("componentClass"), ComponentClass->GetPathName());
992
- if (AppliedProperties.Num() > 0) {
993
- TArray<TSharedPtr<FJsonValue>> PropsArray;
994
- for (const FString &PropName : AppliedProperties)
995
- PropsArray.Add(MakeShared<FJsonValueString>(PropName));
996
- Resp->SetArrayField(TEXT("appliedProperties"), PropsArray);
997
- }
998
- if (PropertyWarnings.Num() > 0) {
999
- TArray<TSharedPtr<FJsonValue>> WarnArray;
1000
- for (const FString &Warning : PropertyWarnings)
1001
- WarnArray.Add(MakeShared<FJsonValueString>(Warning));
1002
- Resp->SetArrayField(TEXT("warnings"), WarnArray);
1003
- }
1004
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1005
- TEXT("ControlActor: Added component '%s' to '%s'"),
1006
- *NewComponent->GetName(), *Found->GetActorLabel());
1007
- SendAutomationResponse(Socket, RequestId, true, TEXT("Component added"), Resp,
1008
- FString());
1009
- return true;
1010
- #else
1011
- return false;
1012
- #endif
1013
- }
1014
-
1015
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSetComponentProperties(
1016
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1017
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1018
- #if WITH_EDITOR
1019
- FString TargetName;
1020
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1021
- if (TargetName.IsEmpty()) {
1022
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1023
- nullptr, TEXT("INVALID_ARGUMENT"));
1024
- return true;
1025
- }
1026
-
1027
- FString ComponentName;
1028
- Payload->TryGetStringField(TEXT("componentName"), ComponentName);
1029
- if (ComponentName.IsEmpty()) {
1030
- SendAutomationResponse(Socket, RequestId, false,
1031
- TEXT("componentName required"), nullptr,
1032
- TEXT("INVALID_ARGUMENT"));
1033
- return true;
1034
- }
1035
-
1036
- const TSharedPtr<FJsonObject> *PropertiesPtr = nullptr;
1037
- if (!(Payload->TryGetObjectField(TEXT("properties"), PropertiesPtr) &&
1038
- PropertiesPtr && PropertiesPtr->IsValid())) {
1039
- SendAutomationResponse(Socket, RequestId, false,
1040
- TEXT("properties object required"), nullptr,
1041
- TEXT("INVALID_ARGUMENT"));
1042
- return true;
1043
- }
1044
-
1045
- AActor *Found = FindActorByName(TargetName);
1046
- if (!Found) {
1047
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1048
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1049
- return true;
1050
- }
1051
-
1052
- UActorComponent *TargetComponent = nullptr;
1053
- for (UActorComponent *Comp : Found->GetComponents()) {
1054
- if (!Comp)
1055
- continue;
1056
- if (Comp->GetName().Equals(ComponentName, ESearchCase::IgnoreCase)) {
1057
- TargetComponent = Comp;
1058
- break;
1059
- }
1060
- }
1061
-
1062
- if (!TargetComponent) {
1063
- SendAutomationResponse(Socket, RequestId, false,
1064
- TEXT("Component not found"), nullptr,
1065
- TEXT("COMPONENT_NOT_FOUND"));
1066
- return true;
1067
- }
1068
-
1069
- TArray<FString> AppliedProperties;
1070
- TArray<FString> PropertyWarnings;
1071
- UClass *ComponentClass = TargetComponent->GetClass();
1072
- TargetComponent->Modify();
1073
-
1074
- // PRIORITY: Apply Mobility FIRST.
1075
- // Physics simulation fails if the component is generic "Static".
1076
- // Scan for Mobility key case-insensitively to ensure we find it regardless of
1077
- // JSON casing
1078
- const TSharedPtr<FJsonValue> *MobilityVal = nullptr;
1079
- FString MobilityKey;
1080
- for (const auto &Pair : (*PropertiesPtr)->Values) {
1081
- if (Pair.Key.Equals(TEXT("Mobility"), ESearchCase::IgnoreCase)) {
1082
- MobilityVal = &Pair.Value;
1083
- MobilityKey = Pair.Key;
1084
- break;
1085
- }
1086
- }
1087
-
1088
- if (MobilityVal) {
1089
- if (USceneComponent *SC = Cast<USceneComponent>(TargetComponent)) {
1090
- FString EnumVal;
1091
- if ((*MobilityVal)->TryGetString(EnumVal)) {
1092
- // Parse enum string
1093
- int64 Val =
1094
- StaticEnum<EComponentMobility::Type>()->GetValueByNameString(
1095
- EnumVal);
1096
- if (Val != INDEX_NONE) {
1097
- SC->SetMobility((EComponentMobility::Type)Val);
1098
- AppliedProperties.Add(MobilityKey);
1099
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1100
- TEXT("Explicitly set Mobility to %s"), *EnumVal);
1101
- }
1102
- } else {
1103
- double Val;
1104
- if ((*MobilityVal)->TryGetNumber(Val)) {
1105
- SC->SetMobility((EComponentMobility::Type)(int32)Val);
1106
- AppliedProperties.Add(MobilityKey);
1107
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1108
- TEXT("Explicitly set Mobility to %d"), (int32)Val);
1109
- }
1110
- }
1111
- }
1112
- }
1113
-
1114
- for (const auto &Pair : (*PropertiesPtr)->Values) {
1115
- // Skip Mobility as we already handled it
1116
- if (Pair.Key.Equals(TEXT("Mobility"), ESearchCase::IgnoreCase))
1117
- continue;
1118
-
1119
- // Special handling for SimulatePhysics
1120
- if (Pair.Key.Equals(TEXT("SimulatePhysics"), ESearchCase::IgnoreCase) ||
1121
- Pair.Key.Equals(TEXT("bSimulatePhysics"), ESearchCase::IgnoreCase)) {
1122
- if (UPrimitiveComponent *Prim =
1123
- Cast<UPrimitiveComponent>(TargetComponent)) {
1124
- bool bVal = false;
1125
- if (Pair.Value->TryGetBool(bVal)) {
1126
- Prim->SetSimulatePhysics(bVal);
1127
- AppliedProperties.Add(Pair.Key);
1128
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1129
- TEXT("Explicitly set SimulatePhysics to %s"),
1130
- bVal ? TEXT("True") : TEXT("False"));
1131
- continue;
1132
- }
1133
- }
1134
- }
1135
-
1136
- FProperty *Property = ComponentClass->FindPropertyByName(*Pair.Key);
1137
- if (!Property) {
1138
- PropertyWarnings.Add(
1139
- FString::Printf(TEXT("Property not found: %s"), *Pair.Key));
1140
- continue;
1141
- }
1142
- FString ApplyError;
1143
- if (ApplyJsonValueToProperty(TargetComponent, Property, Pair.Value,
1144
- ApplyError))
1145
- AppliedProperties.Add(Pair.Key);
1146
- else
1147
- PropertyWarnings.Add(FString::Printf(TEXT("Failed to set %s: %s"),
1148
- *Pair.Key, *ApplyError));
1149
- }
1150
-
1151
- if (USceneComponent *SceneComponent =
1152
- Cast<USceneComponent>(TargetComponent)) {
1153
- SceneComponent->MarkRenderStateDirty();
1154
- SceneComponent->UpdateComponentToWorld();
1155
- }
1156
- TargetComponent->MarkPackageDirty();
1157
-
1158
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1159
- if (AppliedProperties.Num() > 0) {
1160
- TArray<TSharedPtr<FJsonValue>> PropsArray;
1161
- for (const FString &PropName : AppliedProperties)
1162
- PropsArray.Add(MakeShared<FJsonValueString>(PropName));
1163
- Data->SetArrayField(TEXT("applied"), PropsArray);
1164
- }
1165
-
1166
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1167
- TEXT("ControlActor: Updated properties for component '%s' on '%s'"),
1168
- *TargetComponent->GetName(), *Found->GetActorLabel());
1169
-
1170
- SendStandardSuccessResponse(this, Socket, RequestId,
1171
- TEXT("Component properties updated"), Data,
1172
- PropertyWarnings);
1173
- return true;
1174
- #else
1175
- return false;
1176
- #endif
1177
- }
1178
-
1179
- bool UMcpAutomationBridgeSubsystem::HandleControlActorGetComponents(
1180
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1181
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1182
- #if WITH_EDITOR
1183
- FString TargetName;
1184
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1185
-
1186
- // Also accept "objectPath" as an alias, common in inspections
1187
- if (TargetName.IsEmpty()) {
1188
- Payload->TryGetStringField(TEXT("objectPath"), TargetName);
1189
- }
1190
-
1191
- if (TargetName.IsEmpty()) {
1192
- SendAutomationResponse(Socket, RequestId, false,
1193
- TEXT("actorName or objectPath required"), nullptr,
1194
- TEXT("INVALID_ARGUMENT"));
1195
- return true;
1196
- }
1197
-
1198
- AActor *Found = FindActorByName(TargetName);
1199
- // Fallback: Check if it's a Blueprint asset to inspect CDO components
1200
- if (!Found) {
1201
- if (UObject *Asset = UEditorAssetLibrary::LoadAsset(TargetName)) {
1202
- if (UBlueprint *BP = Cast<UBlueprint>(Asset)) {
1203
- if (BP->GeneratedClass) {
1204
- Found = Cast<AActor>(BP->GeneratedClass->GetDefaultObject());
1205
- }
1206
- }
1207
- }
1208
- }
1209
-
1210
- if (!Found) {
1211
- SendAutomationResponse(Socket, RequestId, false,
1212
- TEXT("Actor or Blueprint not found"), nullptr,
1213
- TEXT("ACTOR_NOT_FOUND"));
1214
- return true;
1215
- }
1216
-
1217
- TArray<TSharedPtr<FJsonValue>> ComponentsArray;
1218
- for (UActorComponent *Comp : Found->GetComponents()) {
1219
- if (!Comp)
1220
- continue;
1221
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1222
- Entry->SetStringField(TEXT("name"), Comp->GetName());
1223
- Entry->SetStringField(TEXT("class"), Comp->GetClass()
1224
- ? Comp->GetClass()->GetPathName()
1225
- : TEXT(""));
1226
- Entry->SetStringField(TEXT("path"), Comp->GetPathName());
1227
- if (USceneComponent *SceneComp = Cast<USceneComponent>(Comp)) {
1228
- FVector Loc = SceneComp->GetRelativeLocation();
1229
- FRotator Rot = SceneComp->GetRelativeRotation();
1230
- FVector Scale = SceneComp->GetRelativeScale3D();
1231
-
1232
- TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
1233
- LocObj->SetNumberField(TEXT("x"), Loc.X);
1234
- LocObj->SetNumberField(TEXT("y"), Loc.Y);
1235
- LocObj->SetNumberField(TEXT("z"), Loc.Z);
1236
- Entry->SetObjectField(TEXT("relativeLocation"), LocObj);
1237
-
1238
- TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
1239
- RotObj->SetNumberField(TEXT("pitch"), Rot.Pitch);
1240
- RotObj->SetNumberField(TEXT("yaw"), Rot.Yaw);
1241
- RotObj->SetNumberField(TEXT("roll"), Rot.Roll);
1242
- Entry->SetObjectField(TEXT("relativeRotation"), RotObj);
1243
-
1244
- TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
1245
- ScaleObj->SetNumberField(TEXT("x"), Scale.X);
1246
- ScaleObj->SetNumberField(TEXT("y"), Scale.Y);
1247
- ScaleObj->SetNumberField(TEXT("z"), Scale.Z);
1248
- Entry->SetObjectField(TEXT("relativeScale"), ScaleObj);
1249
- }
1250
- ComponentsArray.Add(MakeShared<FJsonValueObject>(Entry));
1251
- }
1252
-
1253
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1254
- Data->SetArrayField(TEXT("components"), ComponentsArray);
1255
- Data->SetNumberField(TEXT("count"), ComponentsArray.Num());
1256
- SendAutomationResponse(Socket, RequestId, true,
1257
- TEXT("Actor components retrieved"), Data);
1258
- return true;
1259
- #else
1260
- return false;
1261
- #endif
1262
- }
1263
-
1264
- bool UMcpAutomationBridgeSubsystem::HandleControlActorDuplicate(
1265
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1266
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1267
- #if WITH_EDITOR
1268
- FString TargetName;
1269
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1270
- if (TargetName.IsEmpty()) {
1271
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1272
- nullptr, TEXT("INVALID_ARGUMENT"));
1273
- return true;
1274
- }
1275
-
1276
- AActor *Found = FindActorByName(TargetName);
1277
- if (!Found) {
1278
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1279
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1280
- return true;
1281
- }
1282
-
1283
- FVector Offset =
1284
- ExtractVectorField(Payload, TEXT("offset"), FVector::ZeroVector);
1285
- UEditorActorSubsystem *ActorSS =
1286
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1287
- AActor *Duplicated =
1288
- ActorSS->DuplicateActor(Found, Found->GetWorld(), Offset);
1289
- if (!Duplicated) {
1290
- SendAutomationResponse(Socket, RequestId, false,
1291
- TEXT("Failed to duplicate actor"), nullptr,
1292
- TEXT("DUPLICATE_FAILED"));
1293
- return true;
1294
- }
1295
-
1296
- FString NewName;
1297
- Payload->TryGetStringField(TEXT("newName"), NewName);
1298
- if (!NewName.TrimStartAndEnd().IsEmpty())
1299
- Duplicated->SetActorLabel(NewName);
1300
-
1301
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1302
- Data->SetStringField(TEXT("source"), Found->GetActorLabel());
1303
- Data->SetStringField(TEXT("actorName"), Duplicated->GetActorLabel());
1304
- Data->SetStringField(TEXT("actorPath"), Duplicated->GetPathName());
1305
-
1306
- TArray<TSharedPtr<FJsonValue>> OffsetArray;
1307
- OffsetArray.Add(MakeShared<FJsonValueNumber>(Offset.X));
1308
- OffsetArray.Add(MakeShared<FJsonValueNumber>(Offset.Y));
1309
- OffsetArray.Add(MakeShared<FJsonValueNumber>(Offset.Z));
1310
- Data->SetArrayField(TEXT("offset"), OffsetArray);
1311
-
1312
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1313
- TEXT("ControlActor: Duplicated '%s' to '%s'"), *Found->GetActorLabel(),
1314
- *Duplicated->GetActorLabel());
1315
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actor duplicated"),
1316
- Data);
1317
- return true;
1318
- #else
1319
- return false;
1320
- #endif
1321
- }
1322
-
1323
- bool UMcpAutomationBridgeSubsystem::HandleControlActorAttach(
1324
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1325
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1326
- #if WITH_EDITOR
1327
- FString ChildName;
1328
- Payload->TryGetStringField(TEXT("childActor"), ChildName);
1329
- FString ParentName;
1330
- Payload->TryGetStringField(TEXT("parentActor"), ParentName);
1331
- if (ChildName.IsEmpty() || ParentName.IsEmpty()) {
1332
- SendAutomationResponse(Socket, RequestId, false,
1333
- TEXT("childActor and parentActor required"), nullptr,
1334
- TEXT("INVALID_ARGUMENT"));
1335
- return true;
1336
- }
1337
-
1338
- AActor *Child = FindActorByName(ChildName);
1339
- AActor *Parent = FindActorByName(ParentName);
1340
- if (!Child || !Parent) {
1341
- SendAutomationResponse(Socket, RequestId, false,
1342
- TEXT("Child or parent actor not found"), nullptr,
1343
- TEXT("ACTOR_NOT_FOUND"));
1344
- return true;
1345
- }
1346
-
1347
- if (Child == Parent) {
1348
- SendAutomationResponse(Socket, RequestId, false,
1349
- TEXT("Cannot attach actor to itself"), nullptr,
1350
- TEXT("CYCLE_DETECTED"));
1351
- return true;
1352
- }
1353
-
1354
- USceneComponent *ChildRoot = Child->GetRootComponent();
1355
- USceneComponent *ParentRoot = Parent->GetRootComponent();
1356
- if (!ChildRoot || !ParentRoot) {
1357
- SendAutomationResponse(Socket, RequestId, false,
1358
- TEXT("Actor missing root component"), nullptr,
1359
- TEXT("ROOT_MISSING"));
1360
- return true;
1361
- }
1362
-
1363
- Child->Modify();
1364
- ChildRoot->Modify();
1365
- ChildRoot->AttachToComponent(ParentRoot,
1366
- FAttachmentTransformRules::KeepWorldTransform);
1367
- Child->SetOwner(Parent);
1368
- Child->MarkPackageDirty();
1369
- Parent->MarkPackageDirty();
1370
-
1371
- // Verify attachment
1372
- bool bAttached = false;
1373
- if (Child->GetRootComponent() &&
1374
- Child->GetRootComponent()->GetAttachParent() == ParentRoot) {
1375
- bAttached = true;
1376
- }
1377
-
1378
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1379
- Data->SetStringField(TEXT("child"), Child->GetActorLabel());
1380
- Data->SetStringField(TEXT("parent"), Parent->GetActorLabel());
1381
- Data->SetBoolField(TEXT("attached"), bAttached);
1382
-
1383
- if (!bAttached) {
1384
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("ATTACH_FAILED"),
1385
- TEXT("Failed to attach actor"), Data);
1386
- return true;
1387
- }
1388
-
1389
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1390
- TEXT("ControlActor: Attached '%s' to '%s'"), *Child->GetActorLabel(),
1391
- *Parent->GetActorLabel());
1392
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actor attached"),
1393
- Data);
1394
- return true;
1395
- #else
1396
- return false;
1397
- #endif
1398
- }
1399
-
1400
- bool UMcpAutomationBridgeSubsystem::HandleControlActorDetach(
1401
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1402
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1403
- #if WITH_EDITOR
1404
- FString TargetName;
1405
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1406
- if (TargetName.IsEmpty()) {
1407
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1408
- nullptr, TEXT("INVALID_ARGUMENT"));
1409
- return true;
1410
- }
1411
-
1412
- AActor *Found = FindActorByName(TargetName);
1413
- if (!Found) {
1414
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1415
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1416
- return true;
1417
- }
1418
-
1419
- USceneComponent *RootComp = Found->GetRootComponent();
1420
- if (!RootComp || !RootComp->GetAttachParent()) {
1421
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1422
- Resp->SetBoolField(TEXT("success"), true);
1423
- Resp->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1424
- Resp->SetStringField(TEXT("note"), TEXT("Actor was not attached"));
1425
- SendAutomationResponse(Socket, RequestId, true,
1426
- TEXT("Actor already detached"), Resp, FString());
1427
- return true;
1428
- }
1429
-
1430
- Found->Modify();
1431
- RootComp->Modify();
1432
- RootComp->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
1433
- Found->SetOwner(nullptr);
1434
- Found->MarkPackageDirty();
1435
-
1436
- // Verify detachment
1437
- const bool bDetached = (RootComp->GetAttachParent() == nullptr);
1438
-
1439
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1440
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1441
- Data->SetBoolField(TEXT("detached"), bDetached);
1442
-
1443
- if (!bDetached) {
1444
- SendStandardErrorResponse(this, Socket, RequestId, TEXT("DETACH_FAILED"),
1445
- TEXT("Failed to detach actor"), Data);
1446
- return true;
1447
- }
1448
-
1449
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1450
- TEXT("ControlActor: Detached '%s'"), *Found->GetActorLabel());
1451
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actor detached"),
1452
- Data);
1453
- return true;
1454
- #else
1455
- return false;
1456
- #endif
1457
- }
1458
-
1459
- bool UMcpAutomationBridgeSubsystem::HandleControlActorFindByTag(
1460
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1461
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1462
- #if WITH_EDITOR
1463
- FString TagValue;
1464
- Payload->TryGetStringField(TEXT("tag"), TagValue);
1465
- if (TagValue.IsEmpty()) {
1466
- SendAutomationResponse(Socket, RequestId, false, TEXT("tag required"),
1467
- nullptr, TEXT("INVALID_ARGUMENT"));
1468
- return true;
1469
- }
1470
-
1471
- FString MatchType;
1472
- Payload->TryGetStringField(TEXT("matchType"), MatchType);
1473
- MatchType = MatchType.ToLower();
1474
- FName TagName(*TagValue);
1475
- TArray<TSharedPtr<FJsonValue>> Matches;
1476
-
1477
- UEditorActorSubsystem *ActorSS =
1478
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1479
- TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
1480
- for (AActor *Actor : AllActors) {
1481
- if (!Actor)
1482
- continue;
1483
- bool bMatches = false;
1484
- if (MatchType == TEXT("contains")) {
1485
- for (const FName &Existing : Actor->Tags) {
1486
- if (Existing.ToString().Contains(TagValue, ESearchCase::IgnoreCase)) {
1487
- bMatches = true;
1488
- break;
1489
- }
1490
- }
1491
- } else {
1492
- bMatches = Actor->ActorHasTag(TagName);
1493
- }
1494
-
1495
- if (bMatches) {
1496
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1497
- Entry->SetStringField(TEXT("name"), Actor->GetActorLabel());
1498
- Entry->SetStringField(TEXT("path"), Actor->GetPathName());
1499
- Entry->SetStringField(TEXT("class"),
1500
- Actor->GetClass() ? Actor->GetClass()->GetPathName()
1501
- : TEXT(""));
1502
- Matches.Add(MakeShared<FJsonValueObject>(Entry));
1503
- }
1504
- }
1505
-
1506
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1507
- Data->SetArrayField(TEXT("actors"), Matches);
1508
- Data->SetNumberField(TEXT("count"), Matches.Num());
1509
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actors found"),
1510
- Data);
1511
- return true;
1512
- #else
1513
- return false;
1514
- #endif
1515
- }
1516
-
1517
- bool UMcpAutomationBridgeSubsystem::HandleControlActorAddTag(
1518
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1519
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1520
- #if WITH_EDITOR
1521
- FString TargetName;
1522
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1523
- FString TagValue;
1524
- Payload->TryGetStringField(TEXT("tag"), TagValue);
1525
- if (TargetName.IsEmpty() || TagValue.IsEmpty()) {
1526
- SendAutomationResponse(Socket, RequestId, false,
1527
- TEXT("actorName and tag required"), nullptr,
1528
- TEXT("INVALID_ARGUMENT"));
1529
- return true;
1530
- }
1531
-
1532
- AActor *Found = FindActorByName(TargetName);
1533
- if (!Found) {
1534
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1535
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1536
- return true;
1537
- }
1538
-
1539
- const FName TagName(*TagValue);
1540
- const bool bAlreadyHad = Found->Tags.Contains(TagName);
1541
-
1542
- Found->Modify();
1543
- Found->Tags.AddUnique(TagName);
1544
- Found->MarkPackageDirty();
1545
-
1546
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1547
- Data->SetBoolField(TEXT("wasPresent"), bAlreadyHad);
1548
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1549
- Data->SetStringField(TEXT("tag"), TagName.ToString());
1550
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1551
- TEXT("ControlActor: Added tag '%s' to '%s'"), *TagName.ToString(),
1552
- *Found->GetActorLabel());
1553
- SendStandardSuccessResponse(this, Socket, RequestId,
1554
- TEXT("Tag applied to actor"), Data);
1555
- return true;
1556
- #else
1557
- return false;
1558
- #endif
1559
- }
1560
-
1561
- bool UMcpAutomationBridgeSubsystem::HandleControlActorFindByName(
1562
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1563
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1564
- #if WITH_EDITOR
1565
- FString Query;
1566
- Payload->TryGetStringField(TEXT("name"), Query);
1567
- if (Query.IsEmpty()) {
1568
- SendAutomationResponse(Socket, RequestId, false, TEXT("name required"),
1569
- nullptr, TEXT("INVALID_ARGUMENT"));
1570
- return true;
1571
- }
1572
-
1573
- UEditorActorSubsystem *ActorSS =
1574
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1575
- const TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
1576
- TArray<TSharedPtr<FJsonValue>> Matches;
1577
- for (AActor *Actor : AllActors) {
1578
- if (!Actor)
1579
- continue;
1580
- const FString Label = Actor->GetActorLabel();
1581
- const FString Name = Actor->GetName();
1582
- const FString Path = Actor->GetPathName();
1583
- const bool bMatches = Label.Contains(Query, ESearchCase::IgnoreCase) ||
1584
- Name.Contains(Query, ESearchCase::IgnoreCase) ||
1585
- Path.Contains(Query, ESearchCase::IgnoreCase);
1586
- if (bMatches) {
1587
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1588
- Entry->SetStringField(TEXT("label"), Label);
1589
- Entry->SetStringField(TEXT("name"), Name);
1590
- Entry->SetStringField(TEXT("path"), Path);
1591
- Entry->SetStringField(TEXT("class"),
1592
- Actor->GetClass() ? Actor->GetClass()->GetPathName()
1593
- : TEXT(""));
1594
- Matches.Add(MakeShared<FJsonValueObject>(Entry));
1595
- }
1596
- }
1597
-
1598
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1599
- Data->SetNumberField(TEXT("count"), Matches.Num());
1600
- Data->SetArrayField(TEXT("actors"), Matches);
1601
- Data->SetStringField(TEXT("query"), Query);
1602
- SendStandardSuccessResponse(this, Socket, RequestId,
1603
- TEXT("Actor query executed"), Data);
1604
- return true;
1605
- #else
1606
- return false;
1607
- #endif
1608
- }
1609
-
1610
- bool UMcpAutomationBridgeSubsystem::HandleControlActorDeleteByTag(
1611
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1612
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1613
- #if WITH_EDITOR
1614
- FString TagValue;
1615
- Payload->TryGetStringField(TEXT("tag"), TagValue);
1616
- if (TagValue.IsEmpty()) {
1617
- SendAutomationResponse(Socket, RequestId, false, TEXT("tag required"),
1618
- nullptr, TEXT("INVALID_ARGUMENT"));
1619
- return true;
1620
- }
1621
-
1622
- const FName TagName(*TagValue);
1623
- UEditorActorSubsystem *ActorSS =
1624
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
1625
- const TArray<AActor *> AllActors = ActorSS->GetAllLevelActors();
1626
- TArray<FString> Deleted;
1627
-
1628
- for (AActor *Actor : AllActors) {
1629
- if (!Actor)
1630
- continue;
1631
- if (Actor->ActorHasTag(TagName)) {
1632
- const FString Label = Actor->GetActorLabel();
1633
- if (ActorSS->DestroyActor(Actor))
1634
- Deleted.Add(Label);
1635
- }
1636
- }
1637
-
1638
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1639
- Data->SetStringField(TEXT("tag"), TagName.ToString());
1640
- Data->SetNumberField(TEXT("deletedCount"), Deleted.Num());
1641
- TArray<TSharedPtr<FJsonValue>> DeletedArray;
1642
- for (const FString &Name : Deleted)
1643
- DeletedArray.Add(MakeShared<FJsonValueString>(Name));
1644
- Data->SetArrayField(TEXT("deleted"), DeletedArray);
1645
- SendStandardSuccessResponse(this, Socket, RequestId,
1646
- TEXT("Actors deleted by tag"), Data);
1647
- return true;
1648
- #else
1649
- return false;
1650
- #endif
1651
- }
1652
-
1653
- bool UMcpAutomationBridgeSubsystem::HandleControlActorSetBlueprintVariables(
1654
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1655
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1656
- #if WITH_EDITOR
1657
- FString TargetName;
1658
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1659
- if (TargetName.IsEmpty()) {
1660
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1661
- nullptr, TEXT("INVALID_ARGUMENT"));
1662
- return true;
1663
- }
1664
-
1665
- const TSharedPtr<FJsonObject> *VariablesPtr = nullptr;
1666
- if (!(Payload->TryGetObjectField(TEXT("variables"), VariablesPtr) &&
1667
- VariablesPtr && VariablesPtr->IsValid())) {
1668
- SendAutomationResponse(Socket, RequestId, false,
1669
- TEXT("variables object required"), nullptr,
1670
- TEXT("INVALID_ARGUMENT"));
1671
- return true;
1672
- }
1673
-
1674
- AActor *Found = FindActorByName(TargetName);
1675
- if (!Found) {
1676
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1677
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1678
- return true;
1679
- }
1680
-
1681
- UClass *ActorClass = Found->GetClass();
1682
- Found->Modify();
1683
- TArray<FString> Applied;
1684
- TArray<FString> Warnings;
1685
-
1686
- for (const auto &Pair : (*VariablesPtr)->Values) {
1687
- FProperty *Property = ActorClass->FindPropertyByName(*Pair.Key);
1688
- if (!Property) {
1689
- Warnings.Add(FString::Printf(TEXT("Property not found: %s"), *Pair.Key));
1690
- continue;
1691
- }
1692
-
1693
- FString ApplyError;
1694
- if (ApplyJsonValueToProperty(Found, Property, Pair.Value, ApplyError))
1695
- Applied.Add(Pair.Key);
1696
- else
1697
- Warnings.Add(FString::Printf(TEXT("Failed to set %s: %s"), *Pair.Key,
1698
- *ApplyError));
1699
- }
1700
-
1701
- Found->MarkComponentsRenderStateDirty();
1702
- Found->MarkPackageDirty();
1703
-
1704
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1705
- if (Applied.Num() > 0) {
1706
- TArray<TSharedPtr<FJsonValue>> AppliedArray;
1707
- for (const FString &Name : Applied)
1708
- AppliedArray.Add(MakeShared<FJsonValueString>(Name));
1709
- Data->SetArrayField(TEXT("updated"), AppliedArray);
1710
- }
1711
-
1712
- SendStandardSuccessResponse(this, Socket, RequestId,
1713
- TEXT("Variables updated"), Data, Warnings);
1714
- return true;
1715
- #else
1716
- return false;
1717
- #endif
1718
- }
1719
-
1720
- bool UMcpAutomationBridgeSubsystem::HandleControlActorCreateSnapshot(
1721
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1722
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1723
- #if WITH_EDITOR
1724
- FString TargetName;
1725
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1726
- if (TargetName.IsEmpty()) {
1727
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1728
- nullptr, TEXT("INVALID_ARGUMENT"));
1729
- return true;
1730
- }
1731
-
1732
- FString SnapshotName;
1733
- Payload->TryGetStringField(TEXT("snapshotName"), SnapshotName);
1734
- if (SnapshotName.IsEmpty()) {
1735
- SendAutomationResponse(Socket, RequestId, false,
1736
- TEXT("snapshotName required"), nullptr,
1737
- TEXT("INVALID_ARGUMENT"));
1738
- return true;
1739
- }
1740
-
1741
- AActor *Found = FindActorByName(TargetName);
1742
- if (!Found) {
1743
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1744
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1745
- return true;
1746
- }
1747
-
1748
- const FString SnapshotKey =
1749
- FString::Printf(TEXT("%s::%s"), *Found->GetPathName(), *SnapshotName);
1750
- CachedActorSnapshots.Add(SnapshotKey, Found->GetActorTransform());
1751
-
1752
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1753
- Data->SetStringField(TEXT("snapshotName"), SnapshotName);
1754
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1755
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Snapshot created"),
1756
- Data);
1757
- return true;
1758
- #else
1759
- return false;
1760
- #endif
1761
- }
1762
-
1763
- bool UMcpAutomationBridgeSubsystem::HandleControlActorRestoreSnapshot(
1764
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1765
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1766
- #if WITH_EDITOR
1767
- FString TargetName;
1768
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1769
- if (TargetName.IsEmpty()) {
1770
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1771
- nullptr, TEXT("INVALID_ARGUMENT"));
1772
- return true;
1773
- }
1774
-
1775
- FString SnapshotName;
1776
- Payload->TryGetStringField(TEXT("snapshotName"), SnapshotName);
1777
- if (SnapshotName.IsEmpty()) {
1778
- SendAutomationResponse(Socket, RequestId, false,
1779
- TEXT("snapshotName required"), nullptr,
1780
- TEXT("INVALID_ARGUMENT"));
1781
- return true;
1782
- }
1783
-
1784
- AActor *Found = FindActorByName(TargetName);
1785
- if (!Found) {
1786
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1787
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1788
- return true;
1789
- }
1790
-
1791
- const FString SnapshotKey =
1792
- FString::Printf(TEXT("%s::%s"), *Found->GetPathName(), *SnapshotName);
1793
- if (!CachedActorSnapshots.Contains(SnapshotKey)) {
1794
- SendAutomationResponse(Socket, RequestId, false, TEXT("Snapshot not found"),
1795
- nullptr, TEXT("SNAPSHOT_NOT_FOUND"));
1796
- return true;
1797
- }
1798
-
1799
- const FTransform &SavedTransform = CachedActorSnapshots[SnapshotKey];
1800
- Found->Modify();
1801
- Found->SetActorTransform(SavedTransform);
1802
- Found->MarkComponentsRenderStateDirty();
1803
- Found->MarkPackageDirty();
1804
-
1805
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1806
- Data->SetStringField(TEXT("snapshotName"), SnapshotName);
1807
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1808
- SendStandardSuccessResponse(this, Socket, RequestId,
1809
- TEXT("Snapshot restored"), Data);
1810
- return true;
1811
- #else
1812
- return false;
1813
- #endif
1814
- }
1815
-
1816
- bool UMcpAutomationBridgeSubsystem::HandleControlActorExport(
1817
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1818
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1819
- #if WITH_EDITOR
1820
- FString TargetName;
1821
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1822
- if (TargetName.IsEmpty()) {
1823
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1824
- nullptr, TEXT("INVALID_ARGUMENT"));
1825
- return true;
1826
- }
1827
-
1828
- AActor *Found = FindActorByName(TargetName);
1829
- if (!Found) {
1830
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1831
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1832
- return true;
1833
- }
1834
-
1835
- FMcpOutputCapture OutputCapture;
1836
- UExporter::ExportToOutputDevice(nullptr, Found, nullptr, OutputCapture,
1837
- TEXT("T3D"), 0, 0, false);
1838
- FString OutputString = FString::Join(OutputCapture.Consume(), TEXT("\n"));
1839
-
1840
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1841
- Data->SetStringField(TEXT("t3d"), OutputString);
1842
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1843
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actor exported"),
1844
- Data);
1845
- return true;
1846
- #else
1847
- return false;
1848
- #endif
1849
- }
1850
-
1851
- bool UMcpAutomationBridgeSubsystem::HandleControlActorGetBoundingBox(
1852
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1853
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1854
- #if WITH_EDITOR
1855
- FString TargetName;
1856
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1857
- if (TargetName.IsEmpty()) {
1858
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1859
- nullptr, TEXT("INVALID_ARGUMENT"));
1860
- return true;
1861
- }
1862
-
1863
- AActor *Found = FindActorByName(TargetName);
1864
- if (!Found) {
1865
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1866
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1867
- return true;
1868
- }
1869
-
1870
- FVector Origin, BoxExtent;
1871
- Found->GetActorBounds(false, Origin, BoxExtent);
1872
-
1873
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1874
-
1875
- auto MakeArray = [](const FVector &Vec) {
1876
- TArray<TSharedPtr<FJsonValue>> Arr;
1877
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.X));
1878
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Y));
1879
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Z));
1880
- return Arr;
1881
- };
1882
-
1883
- Data->SetArrayField(TEXT("origin"), MakeArray(Origin));
1884
- Data->SetArrayField(TEXT("extent"), MakeArray(BoxExtent));
1885
- SendStandardSuccessResponse(this, Socket, RequestId,
1886
- TEXT("Bounding box retrieved"), Data);
1887
- return true;
1888
- #else
1889
- return false;
1890
- #endif
1891
- }
1892
-
1893
- bool UMcpAutomationBridgeSubsystem::HandleControlActorGetMetadata(
1894
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1895
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1896
- #if WITH_EDITOR
1897
- FString TargetName;
1898
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1899
- if (TargetName.IsEmpty()) {
1900
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
1901
- nullptr, TEXT("INVALID_ARGUMENT"));
1902
- return true;
1903
- }
1904
-
1905
- AActor *Found = FindActorByName(TargetName);
1906
- if (!Found) {
1907
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1908
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1909
- return true;
1910
- }
1911
-
1912
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1913
- Data->SetStringField(TEXT("name"), Found->GetName());
1914
- Data->SetStringField(TEXT("label"), Found->GetActorLabel());
1915
- Data->SetStringField(TEXT("path"), Found->GetPathName());
1916
- Data->SetStringField(TEXT("class"), Found->GetClass()
1917
- ? Found->GetClass()->GetPathName()
1918
- : TEXT(""));
1919
-
1920
- TArray<TSharedPtr<FJsonValue>> TagsArray;
1921
- for (const FName &Tag : Found->Tags) {
1922
- TagsArray.Add(MakeShared<FJsonValueString>(Tag.ToString()));
1923
- }
1924
- Data->SetArrayField(TEXT("tags"), TagsArray);
1925
-
1926
- const FTransform Current = Found->GetActorTransform();
1927
- auto MakeArray = [](const FVector &Vec) {
1928
- TArray<TSharedPtr<FJsonValue>> Arr;
1929
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.X));
1930
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Y));
1931
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Z));
1932
- return Arr;
1933
- };
1934
- Data->SetArrayField(TEXT("location"), MakeArray(Current.GetLocation()));
1935
-
1936
- SendStandardSuccessResponse(this, Socket, RequestId,
1937
- TEXT("Metadata retrieved"), Data);
1938
- return true;
1939
- #else
1940
- return false;
1941
- #endif
1942
- }
1943
-
1944
- bool UMcpAutomationBridgeSubsystem::HandleControlActorRemoveTag(
1945
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
1946
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
1947
- #if WITH_EDITOR
1948
- FString TargetName;
1949
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
1950
- FString TagValue;
1951
- Payload->TryGetStringField(TEXT("tag"), TagValue);
1952
- if (TargetName.IsEmpty() || TagValue.IsEmpty()) {
1953
- SendAutomationResponse(Socket, RequestId, false,
1954
- TEXT("actorName and tag required"), nullptr,
1955
- TEXT("INVALID_ARGUMENT"));
1956
- return true;
1957
- }
1958
-
1959
- AActor *Found = FindActorByName(TargetName);
1960
- if (!Found) {
1961
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
1962
- nullptr, TEXT("ACTOR_NOT_FOUND"));
1963
- return true;
1964
- }
1965
-
1966
- const FName TagName(*TagValue);
1967
- if (!Found->Tags.Contains(TagName)) {
1968
- // Idempotent success
1969
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
1970
- Resp->SetBoolField(TEXT("success"), true);
1971
- Resp->SetBoolField(TEXT("wasPresent"), false);
1972
- Resp->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1973
- Resp->SetStringField(TEXT("tag"), TagValue);
1974
- SendAutomationResponse(Socket, RequestId, true,
1975
- TEXT("Tag not present (idempotent)"), Resp,
1976
- FString());
1977
- return true;
1978
- }
1979
-
1980
- Found->Modify();
1981
- Found->Tags.Remove(TagName);
1982
- Found->MarkPackageDirty();
1983
-
1984
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
1985
- Data->SetBoolField(TEXT("wasPresent"), true);
1986
- Data->SetStringField(TEXT("actorName"), Found->GetActorLabel());
1987
- Data->SetStringField(TEXT("tag"), TagValue);
1988
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
1989
- TEXT("ControlActor: Removed tag '%s' from '%s'"), *TagValue,
1990
- *Found->GetActorLabel());
1991
- SendStandardSuccessResponse(this, Socket, RequestId,
1992
- TEXT("Tag removed from actor"), Data);
1993
- return true;
1994
- #else
1995
- return false;
1996
- #endif
1997
- }
1998
-
1999
- bool UMcpAutomationBridgeSubsystem::HandleControlActorAction(
2000
- const FString &RequestId, const FString &Action,
2001
- const TSharedPtr<FJsonObject> &Payload,
2002
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2003
- const FString Lower = Action.ToLower();
2004
- if (!Lower.Equals(TEXT("control_actor"), ESearchCase::IgnoreCase) &&
2005
- !Lower.StartsWith(TEXT("control_actor")))
2006
- return false;
2007
- if (!Payload.IsValid()) {
2008
- SendAutomationError(RequestingSocket, RequestId,
2009
- TEXT("control_actor payload missing."),
2010
- TEXT("INVALID_PAYLOAD"));
2011
- return true;
2012
- }
2013
-
2014
- FString SubAction;
2015
- Payload->TryGetStringField(TEXT("action"), SubAction);
2016
- const FString LowerSub = SubAction.ToLower();
2017
-
2018
- UE_LOG(LogMcpAutomationBridgeSubsystem, Display,
2019
- TEXT("HandleControlActorAction: %s RequestId=%s"), *LowerSub,
2020
- *RequestId);
2021
-
2022
- #if WITH_EDITOR
2023
- if (!GEditor) {
2024
- SendAutomationResponse(RequestingSocket, RequestId, false,
2025
- TEXT("Editor not available"), nullptr,
2026
- TEXT("EDITOR_NOT_AVAILABLE"));
2027
- return true;
2028
- }
2029
- if (!GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
2030
- SendAutomationResponse(RequestingSocket, RequestId, false,
2031
- TEXT("EditorActorSubsystem not available"), nullptr,
2032
- TEXT("EDITOR_ACTOR_SUBSYSTEM_MISSING"));
2033
- return true;
2034
- }
2035
-
2036
- if (LowerSub == TEXT("spawn"))
2037
- return HandleControlActorSpawn(RequestId, Payload, RequestingSocket);
2038
- if (LowerSub == TEXT("spawn_blueprint"))
2039
- return HandleControlActorSpawnBlueprint(RequestId, Payload,
2040
- RequestingSocket);
2041
- if (LowerSub == TEXT("delete") || LowerSub == TEXT("remove"))
2042
- return HandleControlActorDelete(RequestId, Payload, RequestingSocket);
2043
- if (LowerSub == TEXT("apply_force") ||
2044
- LowerSub == TEXT("apply_force_to_actor"))
2045
- return HandleControlActorApplyForce(RequestId, Payload, RequestingSocket);
2046
- if (LowerSub == TEXT("set_transform") ||
2047
- LowerSub == TEXT("set_actor_transform"))
2048
- return HandleControlActorSetTransform(RequestId, Payload, RequestingSocket);
2049
- if (LowerSub == TEXT("get_transform") ||
2050
- LowerSub == TEXT("get_actor_transform"))
2051
- return HandleControlActorGetTransform(RequestId, Payload, RequestingSocket);
2052
- if (LowerSub == TEXT("set_visibility") ||
2053
- LowerSub == TEXT("set_actor_visibility"))
2054
- return HandleControlActorSetVisibility(RequestId, Payload,
2055
- RequestingSocket);
2056
- if (LowerSub == TEXT("add_component"))
2057
- return HandleControlActorAddComponent(RequestId, Payload, RequestingSocket);
2058
- if (LowerSub == TEXT("set_component_properties"))
2059
- return HandleControlActorSetComponentProperties(RequestId, Payload,
2060
- RequestingSocket);
2061
- if (LowerSub == TEXT("get_components"))
2062
- return HandleControlActorGetComponents(RequestId, Payload,
2063
- RequestingSocket);
2064
- if (LowerSub == TEXT("duplicate"))
2065
- return HandleControlActorDuplicate(RequestId, Payload, RequestingSocket);
2066
- if (LowerSub == TEXT("attach"))
2067
- return HandleControlActorAttach(RequestId, Payload, RequestingSocket);
2068
- if (LowerSub == TEXT("detach"))
2069
- return HandleControlActorDetach(RequestId, Payload, RequestingSocket);
2070
- if (LowerSub == TEXT("find_by_tag"))
2071
- return HandleControlActorFindByTag(RequestId, Payload, RequestingSocket);
2072
- if (LowerSub == TEXT("add_tag"))
2073
- return HandleControlActorAddTag(RequestId, Payload, RequestingSocket);
2074
- if (LowerSub == TEXT("remove_tag"))
2075
- return HandleControlActorRemoveTag(RequestId, Payload, RequestingSocket);
2076
- if (LowerSub == TEXT("find_by_name"))
2077
- return HandleControlActorFindByName(RequestId, Payload, RequestingSocket);
2078
- if (LowerSub == TEXT("delete_by_tag"))
2079
- return HandleControlActorDeleteByTag(RequestId, Payload, RequestingSocket);
2080
- if (LowerSub == TEXT("set_blueprint_variables"))
2081
- return HandleControlActorSetBlueprintVariables(RequestId, Payload,
2082
- RequestingSocket);
2083
- if (LowerSub == TEXT("create_snapshot"))
2084
- return HandleControlActorCreateSnapshot(RequestId, Payload,
2085
- RequestingSocket);
2086
- if (LowerSub == TEXT("restore_snapshot"))
2087
- return HandleControlActorRestoreSnapshot(RequestId, Payload,
2088
- RequestingSocket);
2089
- if (LowerSub == TEXT("export"))
2090
- return HandleControlActorExport(RequestId, Payload, RequestingSocket);
2091
- if (LowerSub == TEXT("get_bounding_box"))
2092
- return HandleControlActorGetBoundingBox(RequestId, Payload,
2093
- RequestingSocket);
2094
- if (LowerSub == TEXT("get_metadata"))
2095
- return HandleControlActorGetMetadata(RequestId, Payload, RequestingSocket);
2096
- if (LowerSub == TEXT("list") || LowerSub == TEXT("list_actors"))
2097
- return HandleControlActorList(RequestId, Payload, RequestingSocket);
2098
- if (LowerSub == TEXT("get") || LowerSub == TEXT("get_actor") ||
2099
- LowerSub == TEXT("get_actor_by_name"))
2100
- return HandleControlActorGet(RequestId, Payload, RequestingSocket);
2101
-
2102
- SendAutomationResponse(
2103
- RequestingSocket, RequestId, false,
2104
- FString::Printf(TEXT("Unknown actor control action: %s"), *LowerSub),
2105
- nullptr, TEXT("UNKNOWN_ACTION"));
2106
- return true;
2107
- #else
2108
- SendAutomationResponse(RequestingSocket, RequestId, false,
2109
- TEXT("Actor control requires editor build."), nullptr,
2110
- TEXT("NOT_IMPLEMENTED"));
2111
- return true;
2112
- #endif
2113
- }
2114
-
2115
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorPlay(
2116
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2117
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2118
- #if WITH_EDITOR
2119
- if (GEditor->PlayWorld) {
2120
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2121
- Resp->SetBoolField(TEXT("success"), true);
2122
- Resp->SetBoolField(TEXT("alreadyPlaying"), true);
2123
- SendAutomationResponse(Socket, RequestId, true,
2124
- TEXT("Play session already active"), Resp,
2125
- FString());
2126
- return true;
2127
- }
2128
-
2129
- FRequestPlaySessionParams PlayParams;
2130
- PlayParams.WorldType = EPlaySessionWorldType::PlayInEditor;
2131
- #if MCP_HAS_LEVEL_EDITOR_PLAY_SETTINGS
2132
- PlayParams.EditorPlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
2133
- #endif
2134
- #if MCP_HAS_LEVEL_EDITOR_MODULE
2135
- if (FLevelEditorModule *LevelEditorModule =
2136
- FModuleManager::GetModulePtr<FLevelEditorModule>(
2137
- TEXT("LevelEditor"))) {
2138
- TSharedPtr<IAssetViewport> DestinationViewport =
2139
- LevelEditorModule->GetFirstActiveViewport();
2140
- if (DestinationViewport.IsValid())
2141
- PlayParams.DestinationSlateViewport = DestinationViewport;
2142
- }
2143
- #endif
2144
-
2145
- GEditor->RequestPlaySession(PlayParams);
2146
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2147
- Resp->SetBoolField(TEXT("success"), true);
2148
- SendAutomationResponse(Socket, RequestId, true,
2149
- TEXT("Play in Editor started"), Resp, FString());
2150
- return true;
2151
- #else
2152
- return false;
2153
- #endif
2154
- }
2155
-
2156
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorStop(
2157
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2158
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2159
- #if WITH_EDITOR
2160
- if (!GEditor->PlayWorld) {
2161
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2162
- Resp->SetBoolField(TEXT("success"), true);
2163
- Resp->SetBoolField(TEXT("alreadyStopped"), true);
2164
- SendAutomationResponse(Socket, RequestId, true,
2165
- TEXT("Play session not active"), Resp, FString());
2166
- return true;
2167
- }
2168
-
2169
- GEditor->RequestEndPlayMap();
2170
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2171
- Resp->SetBoolField(TEXT("success"), true);
2172
- SendAutomationResponse(Socket, RequestId, true,
2173
- TEXT("Play in Editor stopped"), Resp, FString());
2174
- return true;
2175
- #else
2176
- return false;
2177
- #endif
2178
- }
2179
-
2180
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorEject(
2181
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2182
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2183
- #if WITH_EDITOR
2184
- if (!GEditor->PlayWorld) {
2185
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2186
- Resp->SetBoolField(TEXT("success"), true);
2187
- Resp->SetBoolField(TEXT("alreadyStopped"), true);
2188
- SendAutomationResponse(Socket, RequestId, true,
2189
- TEXT("Play session not active"), Resp, FString());
2190
- return true;
2191
- }
2192
-
2193
- GEditor->RequestEndPlayMap();
2194
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2195
- Resp->SetBoolField(TEXT("success"), true);
2196
- SendAutomationResponse(Socket, RequestId, true,
2197
- TEXT("Play in Editor ejected"), Resp, FString());
2198
- return true;
2199
- #else
2200
- return false;
2201
- #endif
2202
- }
2203
-
2204
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorPossess(
2205
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2206
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2207
- #if WITH_EDITOR
2208
- FString ActorName;
2209
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
2210
-
2211
- // Also try "objectPath" as fallback since schema might use that
2212
- if (ActorName.IsEmpty())
2213
- Payload->TryGetStringField(TEXT("objectPath"), ActorName);
2214
-
2215
- if (ActorName.IsEmpty()) {
2216
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
2217
- nullptr, TEXT("INVALID_ARGUMENT"));
2218
- return true;
2219
- }
2220
-
2221
- AActor *Found = FindActorByName(ActorName);
2222
- if (!Found) {
2223
- SendAutomationResponse(
2224
- Socket, RequestId, false,
2225
- FString::Printf(TEXT("Actor not found: %s"), *ActorName), nullptr,
2226
- TEXT("ACTOR_NOT_FOUND"));
2227
- return true;
2228
- }
2229
-
2230
- if (GEditor) {
2231
- GEditor->SelectNone(true, true, false);
2232
- GEditor->SelectActor(Found, true, true, true);
2233
- // 'POSSESS' command works on selected actor in PIE
2234
- if (GEditor->PlayWorld) {
2235
- GEditor->Exec(GEditor->PlayWorld, TEXT("POSSESS"));
2236
- SendAutomationResponse(Socket, RequestId, true, TEXT("Possessed actor"),
2237
- nullptr);
2238
- } else {
2239
- // If not in PIE, we can't possess
2240
- SendAutomationResponse(Socket, RequestId, false,
2241
- TEXT("Cannot possess actor while not in PIE"),
2242
- nullptr, TEXT("NOT_IN_PIE"));
2243
- }
2244
- return true;
2245
- }
2246
-
2247
- SendAutomationResponse(Socket, RequestId, false, TEXT("Editor not available"),
2248
- nullptr, TEXT("EDITOR_NOT_AVAILABLE"));
2249
- return true;
2250
- #else
2251
- return false;
2252
- #endif
2253
- }
2254
-
2255
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorFocusActor(
2256
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2257
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2258
- #if WITH_EDITOR
2259
- FString ActorName;
2260
- Payload->TryGetStringField(TEXT("actorName"), ActorName);
2261
- if (ActorName.IsEmpty()) {
2262
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
2263
- nullptr, TEXT("INVALID_ARGUMENT"));
2264
- return true;
2265
- }
2266
-
2267
- if (UEditorActorSubsystem *ActorSS =
2268
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>()) {
2269
- TArray<AActor *> Actors = ActorSS->GetAllLevelActors();
2270
- for (AActor *Actor : Actors) {
2271
- if (!Actor)
2272
- continue;
2273
- if (Actor->GetActorLabel().Equals(ActorName, ESearchCase::IgnoreCase)) {
2274
- GEditor->SelectNone(true, true, false);
2275
- GEditor->SelectActor(Actor, true, true, true);
2276
- GEditor->Exec(nullptr, TEXT("EDITORTEMPVIEWPORT"));
2277
- GEditor->MoveViewportCamerasToActor(*Actor, false);
2278
- SendAutomationResponse(Socket, RequestId, true,
2279
- TEXT("Viewport focused on actor"), nullptr,
2280
- FString());
2281
- return true;
2282
- }
2283
- }
2284
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
2285
- nullptr, TEXT("ACTOR_NOT_FOUND"));
2286
- return true;
2287
- }
2288
- return false;
2289
- #else
2290
- return false;
2291
- #endif
2292
- }
2293
-
2294
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorSetCamera(
2295
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2296
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2297
- #if WITH_EDITOR
2298
- const TSharedPtr<FJsonObject> *Loc = nullptr;
2299
- FVector Location(0, 0, 0);
2300
- FRotator Rotation(0, 0, 0);
2301
- if (Payload->TryGetObjectField(TEXT("location"), Loc) && Loc &&
2302
- (*Loc).IsValid())
2303
- ReadVectorField(*Loc, TEXT(""), Location, Location);
2304
- if (Payload->TryGetObjectField(TEXT("rotation"), Loc) && Loc &&
2305
- (*Loc).IsValid())
2306
- ReadRotatorField(*Loc, TEXT(""), Rotation, Rotation);
2307
-
2308
- #if defined(MCP_HAS_UNREALEDITOR_SUBSYSTEM)
2309
- if (UUnrealEditorSubsystem *UES =
2310
- GEditor->GetEditorSubsystem<UUnrealEditorSubsystem>()) {
2311
- UES->SetLevelViewportCameraInfo(Location, Rotation);
2312
- #if defined(MCP_HAS_LEVELEDITOR_SUBSYSTEM)
2313
- if (ULevelEditorSubsystem *LES =
2314
- GEditor->GetEditorSubsystem<ULevelEditorSubsystem>())
2315
- LES->EditorInvalidateViewports();
2316
- #endif
2317
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2318
- Resp->SetBoolField(TEXT("success"), true);
2319
- SendAutomationResponse(Socket, RequestId, true, TEXT("Camera set"), Resp,
2320
- FString());
2321
- return true;
2322
- }
2323
- #endif
2324
- if (FEditorViewportClient *ViewportClient =
2325
- GEditor->GetActiveViewport()
2326
- ? (FEditorViewportClient *)GEditor->GetActiveViewport()
2327
- ->GetClient()
2328
- : nullptr) {
2329
- ViewportClient->SetViewLocation(Location);
2330
- ViewportClient->SetViewRotation(Rotation);
2331
- ViewportClient->Invalidate();
2332
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2333
- Resp->SetBoolField(TEXT("success"), true);
2334
- SendAutomationResponse(Socket, RequestId, true, TEXT("Camera set"), Resp,
2335
- FString());
2336
- return true;
2337
- }
2338
- return false;
2339
- #else
2340
- return false;
2341
- #endif
2342
- }
2343
-
2344
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorSetViewMode(
2345
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2346
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2347
- #if WITH_EDITOR
2348
- FString Mode;
2349
- Payload->TryGetStringField(TEXT("viewMode"), Mode);
2350
- FString LowerMode = Mode.ToLower();
2351
- FString Chosen;
2352
- if (LowerMode == TEXT("lit"))
2353
- Chosen = TEXT("Lit");
2354
- else if (LowerMode == TEXT("unlit"))
2355
- Chosen = TEXT("Unlit");
2356
- else if (LowerMode == TEXT("wireframe"))
2357
- Chosen = TEXT("Wireframe");
2358
- else if (LowerMode == TEXT("detaillighting"))
2359
- Chosen = TEXT("DetailLighting");
2360
- else if (LowerMode == TEXT("lightingonly"))
2361
- Chosen = TEXT("LightingOnly");
2362
- else if (LowerMode == TEXT("lightcomplexity"))
2363
- Chosen = TEXT("LightComplexity");
2364
- else if (LowerMode == TEXT("shadercomplexity"))
2365
- Chosen = TEXT("ShaderComplexity");
2366
- else if (LowerMode == TEXT("lightmapdensity"))
2367
- Chosen = TEXT("LightmapDensity");
2368
- else if (LowerMode == TEXT("stationarylightoverlap"))
2369
- Chosen = TEXT("StationaryLightOverlap");
2370
- else if (LowerMode == TEXT("reflectionoverride"))
2371
- Chosen = TEXT("ReflectionOverride");
2372
- else
2373
- Chosen = Mode;
2374
-
2375
- const FString Cmd = FString::Printf(TEXT("viewmode %s"), *Chosen);
2376
- if (GEditor->Exec(nullptr, *Cmd)) {
2377
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2378
- Resp->SetBoolField(TEXT("success"), true);
2379
- Resp->SetStringField(TEXT("viewMode"), Chosen);
2380
- SendAutomationResponse(Socket, RequestId, true, TEXT("View mode set"), Resp,
2381
- FString());
2382
- return true;
2383
- }
2384
- SendAutomationResponse(Socket, RequestId, false,
2385
- TEXT("View mode command failed"), nullptr,
2386
- TEXT("EXEC_FAILED"));
2387
- return true;
2388
- #else
2389
- return false;
2390
- #endif
2391
- }
2392
-
2393
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorAction(
2394
- const FString &RequestId, const FString &Action,
2395
- const TSharedPtr<FJsonObject> &Payload,
2396
- TSharedPtr<FMcpBridgeWebSocket> RequestingSocket) {
2397
- const FString Lower = Action.ToLower();
2398
- if (!Lower.Equals(TEXT("control_editor"), ESearchCase::IgnoreCase) &&
2399
- !Lower.StartsWith(TEXT("control_editor")))
2400
- return false;
2401
- if (!Payload.IsValid()) {
2402
- SendAutomationError(RequestingSocket, RequestId,
2403
- TEXT("control_editor payload missing."),
2404
- TEXT("INVALID_PAYLOAD"));
2405
- return true;
2406
- }
2407
-
2408
- FString SubAction;
2409
- Payload->TryGetStringField(TEXT("action"), SubAction);
2410
- const FString LowerSub = SubAction.ToLower();
2411
-
2412
- #if WITH_EDITOR
2413
- if (!GEditor) {
2414
- SendAutomationResponse(RequestingSocket, RequestId, false,
2415
- TEXT("Editor not available"), nullptr,
2416
- TEXT("EDITOR_NOT_AVAILABLE"));
2417
- return true;
2418
- }
2419
-
2420
- if (LowerSub == TEXT("play"))
2421
- return HandleControlEditorPlay(RequestId, Payload, RequestingSocket);
2422
- if (LowerSub == TEXT("stop"))
2423
- return HandleControlEditorStop(RequestId, Payload, RequestingSocket);
2424
- if (LowerSub == TEXT("eject"))
2425
- return HandleControlEditorEject(RequestId, Payload, RequestingSocket);
2426
- if (LowerSub == TEXT("possess"))
2427
- return HandleControlEditorPossess(RequestId, Payload, RequestingSocket);
2428
- if (LowerSub == TEXT("focus_actor"))
2429
- return HandleControlEditorFocusActor(RequestId, Payload, RequestingSocket);
2430
- if (LowerSub == TEXT("set_camera") ||
2431
- LowerSub == TEXT("set_camera_position") ||
2432
- LowerSub == TEXT("set_viewport_camera"))
2433
- return HandleControlEditorSetCamera(RequestId, Payload, RequestingSocket);
2434
- if (LowerSub == TEXT("set_view_mode"))
2435
- return HandleControlEditorSetViewMode(RequestId, Payload, RequestingSocket);
2436
- if (LowerSub == TEXT("open_asset"))
2437
- return HandleControlEditorOpenAsset(RequestId, Payload, RequestingSocket);
2438
-
2439
- SendAutomationResponse(
2440
- RequestingSocket, RequestId, false,
2441
- FString::Printf(TEXT("Unknown editor control action: %s"), *LowerSub),
2442
- nullptr, TEXT("UNKNOWN_ACTION"));
2443
- return true;
2444
- #else
2445
- SendAutomationResponse(RequestingSocket, RequestId, false,
2446
- TEXT("Editor control requires editor build."), nullptr,
2447
- TEXT("NOT_IMPLEMENTED"));
2448
- return true;
2449
- #endif
2450
- }
2451
-
2452
- bool UMcpAutomationBridgeSubsystem::HandleControlEditorOpenAsset(
2453
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2454
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2455
- #if WITH_EDITOR
2456
- FString AssetPath;
2457
- Payload->TryGetStringField(TEXT("assetPath"), AssetPath);
2458
- if (AssetPath.IsEmpty()) {
2459
- SendAutomationResponse(Socket, RequestId, false, TEXT("assetPath required"),
2460
- nullptr, TEXT("INVALID_ARGUMENT"));
2461
- return true;
2462
- }
2463
-
2464
- if (!GEditor) {
2465
- SendAutomationResponse(Socket, RequestId, false,
2466
- TEXT("Editor not available"), nullptr,
2467
- TEXT("EDITOR_NOT_AVAILABLE"));
2468
- return true;
2469
- }
2470
-
2471
- UAssetEditorSubsystem *AssetEditorSS =
2472
- GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
2473
- if (!AssetEditorSS) {
2474
- SendAutomationResponse(Socket, RequestId, false,
2475
- TEXT("AssetEditorSubsystem not available"), nullptr,
2476
- TEXT("SUBSYSTEM_MISSING"));
2477
- return true;
2478
- }
2479
-
2480
- if (!UEditorAssetLibrary::DoesAssetExist(AssetPath)) {
2481
- SendAutomationResponse(Socket, RequestId, false, TEXT("Asset not found"),
2482
- nullptr, TEXT("ASSET_NOT_FOUND"));
2483
- return true;
2484
- }
2485
-
2486
- UObject *Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
2487
- if (!Asset) {
2488
- SendAutomationResponse(Socket, RequestId, false,
2489
- TEXT("Failed to load asset"), nullptr,
2490
- TEXT("LOAD_FAILED"));
2491
- return true;
2492
- }
2493
-
2494
- const bool bOpened = AssetEditorSS->OpenEditorForAsset(Asset);
2495
-
2496
- TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2497
- Resp->SetBoolField(TEXT("success"), bOpened);
2498
- Resp->SetStringField(TEXT("assetPath"), AssetPath);
2499
-
2500
- if (bOpened) {
2501
- SendAutomationResponse(Socket, RequestId, true, TEXT("Asset opened"), Resp,
2502
- FString());
2503
- } else {
2504
- SendAutomationResponse(Socket, RequestId, false,
2505
- TEXT("Failed to open asset editor"), Resp,
2506
- TEXT("OPEN_FAILED"));
2507
- }
2508
- return true;
2509
- #else
2510
- return false;
2511
- #endif
2512
- }
2513
-
2514
- bool UMcpAutomationBridgeSubsystem::HandleControlActorList(
2515
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2516
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2517
- #if WITH_EDITOR
2518
- FString Filter;
2519
- Payload->TryGetStringField(TEXT("filter"), Filter);
2520
-
2521
- UEditorActorSubsystem *ActorSS =
2522
- GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
2523
- if (!ActorSS) {
2524
- SendAutomationResponse(Socket, RequestId, false,
2525
- TEXT("EditorActorSubsystem unavailable"), nullptr,
2526
- TEXT("SUBSYSTEM_MISSING"));
2527
- return true;
2528
- }
2529
-
2530
- const TArray<AActor *> &AllActors = ActorSS->GetAllLevelActors();
2531
- TArray<TSharedPtr<FJsonValue>> ActorsArray;
2532
-
2533
- for (AActor *Actor : AllActors) {
2534
- if (!Actor)
2535
- continue;
2536
- const FString Label = Actor->GetActorLabel();
2537
- const FString Name = Actor->GetName();
2538
- if (!Filter.IsEmpty() && !Label.Contains(Filter) && !Name.Contains(Filter))
2539
- continue;
2540
-
2541
- TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
2542
- Entry->SetStringField(TEXT("label"), Label);
2543
- Entry->SetStringField(TEXT("name"), Name);
2544
- Entry->SetStringField(TEXT("path"), Actor->GetPathName());
2545
- Entry->SetStringField(TEXT("class"), Actor->GetClass()
2546
- ? Actor->GetClass()->GetPathName()
2547
- : TEXT(""));
2548
- ActorsArray.Add(MakeShared<FJsonValueObject>(Entry));
2549
- }
2550
-
2551
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
2552
- Data->SetArrayField(TEXT("actors"), ActorsArray);
2553
- Data->SetNumberField(TEXT("count"), ActorsArray.Num());
2554
- if (!Filter.IsEmpty())
2555
- Data->SetStringField(TEXT("filter"), Filter);
2556
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actors listed"),
2557
- Data);
2558
- return true;
2559
- #else
2560
- return false;
2561
- #endif
2562
- }
2563
-
2564
- bool UMcpAutomationBridgeSubsystem::HandleControlActorGet(
2565
- const FString &RequestId, const TSharedPtr<FJsonObject> &Payload,
2566
- TSharedPtr<FMcpBridgeWebSocket> Socket) {
2567
- #if WITH_EDITOR
2568
- FString TargetName;
2569
- Payload->TryGetStringField(TEXT("actorName"), TargetName);
2570
- if (TargetName.IsEmpty()) {
2571
- SendAutomationResponse(Socket, RequestId, false, TEXT("actorName required"),
2572
- nullptr, TEXT("INVALID_ARGUMENT"));
2573
- return true;
2574
- }
2575
-
2576
- AActor *Found = FindActorByName(TargetName);
2577
- if (!Found) {
2578
- SendAutomationResponse(Socket, RequestId, false, TEXT("Actor not found"),
2579
- nullptr, TEXT("ACTOR_NOT_FOUND"));
2580
- return true;
2581
- }
2582
-
2583
- const FTransform Current = Found->GetActorTransform();
2584
- TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
2585
- Data->SetStringField(TEXT("name"), Found->GetName());
2586
- Data->SetStringField(TEXT("label"), Found->GetActorLabel());
2587
- Data->SetStringField(TEXT("path"), Found->GetPathName());
2588
- Data->SetStringField(TEXT("class"), Found->GetClass()
2589
- ? Found->GetClass()->GetPathName()
2590
- : TEXT(""));
2591
-
2592
- TArray<TSharedPtr<FJsonValue>> TagsArray;
2593
- for (const FName &Tag : Found->Tags) {
2594
- TagsArray.Add(MakeShared<FJsonValueString>(Tag.ToString()));
2595
- }
2596
- Data->SetArrayField(TEXT("tags"), TagsArray);
2597
-
2598
- auto MakeArray = [](const FVector &Vec) -> TArray<TSharedPtr<FJsonValue>> {
2599
- TArray<TSharedPtr<FJsonValue>> Arr;
2600
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.X));
2601
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Y));
2602
- Arr.Add(MakeShared<FJsonValueNumber>(Vec.Z));
2603
- return Arr;
2604
- };
2605
- Data->SetArrayField(TEXT("location"), MakeArray(Current.GetLocation()));
2606
- Data->SetArrayField(TEXT("scale"), MakeArray(Current.GetScale3D()));
2607
-
2608
- SendStandardSuccessResponse(this, Socket, RequestId, TEXT("Actor retrieved"),
2609
- Data);
2610
- return true;
2611
- #else
2612
- return false;
2613
- #endif
2614
- }