unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -0,0 +1,450 @@
1
+ import { cleanObject } from '../../utils/safe-json.js';
2
+ import { ITools } from '../../types/tool-interfaces.js';
3
+ import { executeAutomationRequest } from './common-handlers.js';
4
+ import { normalizeArgs, resolveObjectPath } from './argument-helper.js';
5
+
6
+ async function resolveComponentObjectPathFromArgs(args: any, tools: ITools): Promise<string> {
7
+ const componentName = typeof args.componentName === 'string' ? args.componentName.trim() : '';
8
+ const componentPath = typeof args.componentPath === 'string' ? args.componentPath.trim() : '';
9
+
10
+ // Direct path provided
11
+ const direct = componentPath || (
12
+ (componentName.includes(':') || componentName.includes('.')) &&
13
+ (componentName.startsWith('/Game') || componentName.startsWith('/Script') || componentName.startsWith('/Engine'))
14
+ ? componentName
15
+ : ''
16
+ );
17
+ if (direct) return direct;
18
+
19
+ const actorName = await resolveObjectPath(args, tools, { pathKeys: [], actorKeys: ['actorName', 'name', 'objectPath'] });
20
+ if (!actorName) {
21
+ throw new Error('Invalid actorName: required to resolve componentName');
22
+ }
23
+ if (!componentName) {
24
+ throw new Error('Invalid componentName: must be a non-empty string');
25
+ }
26
+
27
+ // Use inspect:get_components to find the exact component path
28
+ const compsRes: any = await executeAutomationRequest(
29
+ tools,
30
+ 'inspect',
31
+ {
32
+ action: 'get_components',
33
+ actorName: actorName,
34
+ objectPath: actorName
35
+ },
36
+ 'Failed to get components'
37
+ );
38
+
39
+ let components: any[] = [];
40
+ if (compsRes.success) {
41
+ components = Array.isArray(compsRes?.components) ? compsRes.components : [];
42
+ }
43
+
44
+ const needle = componentName.toLowerCase();
45
+
46
+ if (components.length > 0) {
47
+ // 1. Exact Name/Path Match
48
+ let match = components.find((c) => String(c?.name || '').toLowerCase() === needle)
49
+ ?? components.find((c) => String(c?.path || '').toLowerCase() === needle)
50
+ ?? components.find((c) => String(c?.path || '').toLowerCase().endsWith(`:${needle}`))
51
+ ?? components.find((c) => String(c?.path || '').toLowerCase().endsWith(`.${needle}`));
52
+
53
+ // 2. Fuzzy/StartsWith Match (e.g. "StaticMeshComponent" -> "StaticMeshComponent0")
54
+ if (!match) {
55
+ match = components.find((c) => String(c?.name || '').toLowerCase().startsWith(needle));
56
+ }
57
+
58
+ // RESOLUTION LOGIC FIX:
59
+ // If we have a match, we MUST use its path OR its name.
60
+ // We cannot fall back to 'needle' or 'args.componentName' if we found a better specific match.
61
+ if (match) {
62
+ if (typeof match.path === 'string' && match.path.trim().length > 0) {
63
+ return match.path.trim();
64
+ }
65
+ if (typeof match.name === 'string' && match.name.trim().length > 0) {
66
+ // Construct path from the MATCHED name, not the requested name
67
+ return `${actorName}.${match.name}`;
68
+ }
69
+ }
70
+ }
71
+
72
+ // Fallback: Construct path manually using original request
73
+ // Use dot notation for subobjects
74
+ return `${actorName}.${componentName}`;
75
+ }
76
+
77
+
78
+ export async function handleInspectTools(action: string, args: any, tools: ITools) {
79
+ switch (action) {
80
+ case 'inspect_object': {
81
+ const objectPath = await resolveObjectPath(args, tools);
82
+ if (!objectPath) {
83
+ throw new Error('Invalid objectPath: must be a non-empty string');
84
+ }
85
+
86
+ const payload = {
87
+ ...args,
88
+ objectPath,
89
+ action: 'inspect_object',
90
+ detailed: true
91
+ };
92
+
93
+ const res: any = await executeAutomationRequest(
94
+ tools,
95
+ 'inspect',
96
+ payload,
97
+ 'Automation bridge not available for inspect operations'
98
+ );
99
+
100
+ if (res && res.success === false) {
101
+ const errorCode = String(res.error || '').toUpperCase();
102
+ const msg = String(res.message || '');
103
+ if (errorCode === 'OBJECT_NOT_FOUND' || msg.toLowerCase().includes('object not found')) {
104
+ return cleanObject({
105
+ success: false,
106
+ handled: true,
107
+ notFound: true,
108
+ error: res.error,
109
+ message: res.message || 'Object not found'
110
+ });
111
+ }
112
+ }
113
+
114
+ return cleanObject(res);
115
+ }
116
+ case 'get_property': {
117
+ const objectPath = await resolveObjectPath(args, tools);
118
+ const propertyName = normalizeArgs(args, [{ key: 'propertyName', aliases: ['propertyPath'], required: true }]).propertyName;
119
+
120
+ if (!objectPath) {
121
+ throw new Error('Invalid objectPath: must be a non-empty string');
122
+ }
123
+
124
+ const res = await tools.introspectionTools.getProperty({
125
+ objectPath,
126
+ propertyName
127
+ });
128
+
129
+ // Smart Lookup: If property not found on the Actor, try to find it on components
130
+ if (!res.success && (res.error === 'PROPERTY_NOT_FOUND' || String(res.error).includes('not found'))) {
131
+ const actorName = await resolveObjectPath(args, tools, { pathKeys: [], actorKeys: ['actorName', 'name', 'objectPath'] });
132
+ if (actorName) {
133
+ const triedPaths: string[] = [];
134
+
135
+ // Strategy 1: Check RootComponent (Most common for transform/mobility)
136
+ try {
137
+ const rootRes: any = await tools.introspectionTools.getProperty({
138
+ objectPath: actorName,
139
+ propertyName: 'RootComponent'
140
+ });
141
+
142
+ // Check if we got a valid object path string or object with path
143
+ const rootPath = typeof rootRes.value === 'string' ? rootRes.value : (rootRes.value?.path || rootRes.value?.objectPath);
144
+
145
+ if (rootRes.success && rootPath && typeof rootPath === 'string' && rootPath.length > 0 && rootPath !== 'None') {
146
+ triedPaths.push(rootPath);
147
+ const propRes: any = await tools.introspectionTools.getProperty({
148
+ objectPath: rootPath,
149
+ propertyName
150
+ });
151
+ if (propRes.success) {
152
+ return cleanObject({
153
+ ...propRes,
154
+ message: `Resolved property '${propertyName}' on RootComponent (Smart Lookup)`,
155
+ foundOnComponent: 'RootComponent'
156
+ });
157
+ }
158
+ }
159
+ } catch (_e) { /* Ignore RootComponent lookup errors */ }
160
+
161
+ try {
162
+ // Strategy 2: Iterate all components
163
+ // Use ActorTools directly with the input/original name (args.objectPath)
164
+ const shortName = String(args.objectPath || '').trim();
165
+ const compsRes: any = await tools.actorTools.getComponents(shortName);
166
+
167
+ if (compsRes.success && (Array.isArray(compsRes.components) || Array.isArray(compsRes))) {
168
+ const list = Array.isArray(compsRes.components) ? compsRes.components : (Array.isArray(compsRes) ? compsRes : []);
169
+ const triedPaths: string[] = [];
170
+ for (const comp of list) {
171
+ // Use path if available, otherwise construct it (ActorPath.ComponentName)
172
+ // Note: C++ Inspect handler might miss 'path', so we fallback.
173
+ const compName = comp.name;
174
+ const compPath = comp.path || (compName ? `${actorName}.${compName}` : undefined);
175
+
176
+ if (!compPath) continue;
177
+ triedPaths.push(compPath);
178
+
179
+ // Quick check: Try to get property on component
180
+ const compRes: any = await tools.introspectionTools.getProperty({
181
+ objectPath: compPath,
182
+ propertyName
183
+ });
184
+
185
+ if (compRes.success) {
186
+ return cleanObject({
187
+ ...compRes,
188
+ message: `Resolved property '${propertyName}' on component '${comp.name}' (Smart Lookup)`,
189
+ foundOnComponent: comp.name
190
+ });
191
+ }
192
+ }
193
+ // End of loop - if we're here, nothing found
194
+ return cleanObject({
195
+ ...res,
196
+ message: res.message + ` (Smart Lookup failed. Tried: ${triedPaths.length} paths. First: ${triedPaths[0]}. Components: ${list.map((c: any) => c.name).join(',')})`,
197
+ smartLookupTriedPaths: triedPaths
198
+ });
199
+ } else {
200
+ return cleanObject({
201
+ ...res,
202
+ message: res.message + ' (Smart Lookup failed: get_components returned ' + (compsRes.success ? 'success but no list' : 'failure: ' + compsRes.error) + ' | Name: ' + shortName + ' Path: ' + actorName + ')',
203
+ smartLookupGetComponentsError: compsRes
204
+ });
205
+ }
206
+ } catch (_e: any) {
207
+ return cleanObject({
208
+ ...res,
209
+ message: res.message + ' (Smart Lookup exception: ' + _e.message + ')',
210
+ error: _e
211
+ });
212
+ }
213
+ }
214
+ }
215
+ return cleanObject(res);
216
+ }
217
+ case 'set_property': {
218
+ const objectPath = await resolveObjectPath(args, tools);
219
+ const params = normalizeArgs(args, [
220
+ { key: 'propertyName', aliases: ['propertyPath'], required: true },
221
+ { key: 'value' }
222
+ ]);
223
+
224
+ if (!objectPath) {
225
+ throw new Error('Invalid objectPath: must be a non-empty string');
226
+ }
227
+
228
+ const res: any = await tools.introspectionTools.setProperty({
229
+ objectPath,
230
+ propertyName: params.propertyName,
231
+ value: params.value
232
+ });
233
+
234
+ if (res && res.success === false) {
235
+ const errorCode = String(res.error || '').toUpperCase();
236
+ if (errorCode === 'PROPERTY_NOT_FOUND') {
237
+ return cleanObject({
238
+ ...res,
239
+ error: 'UNKNOWN_PROPERTY'
240
+ });
241
+ }
242
+ }
243
+
244
+ return cleanObject(res);
245
+ }
246
+
247
+ case 'get_components': {
248
+ const actorName = await resolveObjectPath(args, tools, { pathKeys: [], actorKeys: ['actorName', 'name', 'objectPath'] });
249
+ if (!actorName) {
250
+ throw new Error('Invalid actorName');
251
+ }
252
+
253
+ const res: any = await executeAutomationRequest(
254
+ tools,
255
+ 'inspect',
256
+ {
257
+ action: 'get_components',
258
+ actorName: actorName,
259
+ objectPath: actorName
260
+ },
261
+ 'Failed to get components'
262
+ );
263
+
264
+ return cleanObject(res);
265
+ }
266
+ case 'get_component_property': {
267
+ const componentObjectPath = await resolveComponentObjectPathFromArgs(args, tools);
268
+ const params = normalizeArgs(args, [
269
+ { key: 'propertyName', aliases: ['propertyPath'], required: true }
270
+ ]);
271
+
272
+ const res = await tools.introspectionTools.getProperty({
273
+ objectPath: componentObjectPath,
274
+ propertyName: params.propertyName
275
+ });
276
+ return cleanObject(res);
277
+ }
278
+ case 'set_component_property': {
279
+ const componentObjectPath = await resolveComponentObjectPathFromArgs(args, tools);
280
+ const params = normalizeArgs(args, [
281
+ { key: 'propertyName', aliases: ['propertyPath'], required: true },
282
+ { key: 'value' }
283
+ ]);
284
+
285
+ const res = await tools.introspectionTools.setProperty({
286
+ objectPath: componentObjectPath,
287
+ propertyName: params.propertyName,
288
+ value: params.value
289
+ });
290
+ return cleanObject(res);
291
+ }
292
+ case 'get_metadata': {
293
+ const actorName = await resolveObjectPath(args, tools);
294
+ if (!actorName) throw new Error('Invalid actorName');
295
+ return cleanObject(await tools.actorTools.getMetadata(actorName));
296
+ }
297
+ case 'add_tag': {
298
+ const actorName = await resolveObjectPath(args, tools);
299
+ const params = normalizeArgs(args, [
300
+ { key: 'tag', required: true }
301
+ ]);
302
+
303
+ if (!actorName) throw new Error('Invalid actorName');
304
+ return cleanObject(await tools.actorTools.addTag({
305
+ actorName,
306
+ tag: params.tag
307
+ }));
308
+ }
309
+ case 'find_by_tag':
310
+ const params = normalizeArgs(args, [{ key: 'tag' }]);
311
+ return cleanObject(await tools.actorTools.findByTag({
312
+ tag: params.tag
313
+ }));
314
+ case 'create_snapshot': {
315
+ const actorName = await resolveObjectPath(args, tools);
316
+ if (!actorName) throw new Error('actorName is required for create_snapshot');
317
+ return cleanObject(await tools.actorTools.createSnapshot({
318
+ actorName,
319
+ snapshotName: args.snapshotName
320
+ }));
321
+ }
322
+ case 'restore_snapshot': {
323
+ const actorName = await resolveObjectPath(args, tools);
324
+ if (!actorName) throw new Error('actorName is required for restore_snapshot');
325
+ return cleanObject(await tools.actorTools.restoreSnapshot({
326
+ actorName,
327
+ snapshotName: args.snapshotName
328
+ }));
329
+ }
330
+ case 'export': {
331
+ const actorName = await resolveObjectPath(args, tools);
332
+ if (!actorName) throw new Error('actorName may be required for export depending on context (exporting actor requires it)');
333
+ const params = normalizeArgs(args, [
334
+ { key: 'destinationPath', aliases: ['outputPath'] }
335
+ ]);
336
+ return cleanObject(await tools.actorTools.exportActor({
337
+ actorName: actorName || '',
338
+ destinationPath: params.destinationPath
339
+ }));
340
+ }
341
+ case 'delete_object': {
342
+ const actorName = await resolveObjectPath(args, tools);
343
+ try {
344
+ if (!actorName) throw new Error('actorName is required for delete_object');
345
+ const res = await tools.actorTools.delete({
346
+ actorName
347
+ });
348
+ return cleanObject(res);
349
+ } catch (err: any) {
350
+ const msg = String(err?.message || err || '');
351
+ const lower = msg.toLowerCase();
352
+ if (lower.includes('actor not found')) {
353
+ return cleanObject({
354
+ success: false,
355
+ error: 'NOT_FOUND',
356
+ handled: true,
357
+ message: msg,
358
+ deleted: actorName,
359
+ notFound: true
360
+ });
361
+ }
362
+ throw err;
363
+ }
364
+ }
365
+ case 'list_objects':
366
+ return cleanObject(await tools.actorTools.listActors(args));
367
+ case 'find_by_class': {
368
+ const params = normalizeArgs(args, [
369
+ { key: 'className', aliases: ['classPath'], required: true }
370
+ ]);
371
+ const res: any = await tools.introspectionTools.findObjectsByClass(params.className);
372
+ if (!res || res.success === false) {
373
+ // Return proper failure state
374
+ return cleanObject({
375
+ success: false,
376
+ error: res?.error || 'OPERATION_FAILED',
377
+ message: res?.message || 'find_by_class failed',
378
+ className: params.className,
379
+ objects: [],
380
+ count: 0
381
+ });
382
+ }
383
+ return cleanObject(res);
384
+ }
385
+ case 'get_bounding_box': {
386
+ const actorName = await resolveObjectPath(args, tools);
387
+ try {
388
+ if (!actorName) throw new Error('actorName is required for get_bounding_box');
389
+ const res = await tools.actorTools.getBoundingBox(actorName);
390
+ return cleanObject(res);
391
+ } catch (err: any) {
392
+ const msg = String(err?.message || err || '');
393
+ const lower = msg.toLowerCase();
394
+ if (lower.includes('actor not found')) {
395
+ return cleanObject({
396
+ success: false,
397
+ error: 'NOT_FOUND',
398
+ handled: true,
399
+ message: msg,
400
+ actorName,
401
+ notFound: true
402
+ });
403
+ }
404
+ throw err;
405
+ }
406
+ }
407
+ case 'inspect_class': {
408
+ const params = normalizeArgs(args, [
409
+ { key: 'className', aliases: ['classPath'], required: true }
410
+ ]);
411
+ let className = params.className;
412
+
413
+ // Basic smart resolution for common classes if path is incomplete
414
+ // E.g. "Landscape" -> "/Script/Landscape.Landscape" or "/Script/Engine.Landscape"
415
+ if (className && !className.includes('/') && !className.includes('.')) {
416
+ if (className === 'Landscape') {
417
+ className = '/Script/Landscape.Landscape';
418
+ } else if (['Actor', 'Pawn', 'Character', 'StaticMeshActor'].includes(className)) {
419
+ className = `/Script/Engine.${className}`;
420
+ }
421
+ }
422
+
423
+ const res: any = await tools.introspectionTools.getCDO(className);
424
+ if (!res || res.success === false) {
425
+ // If first try failed and it looked like a short name, maybe try standard engine path?
426
+ if (args.className && !args.className.includes('/') && !className.startsWith('/Script/')) {
427
+ const retryName = `/Script/Engine.${args.className}`;
428
+ const resRetry: any = await tools.introspectionTools.getCDO(retryName);
429
+ if (resRetry && resRetry.success) {
430
+ return cleanObject(resRetry);
431
+ }
432
+ }
433
+
434
+ // Return proper failure state
435
+ return cleanObject({
436
+ success: false,
437
+ error: res?.error || 'OPERATION_FAILED',
438
+ message: res?.message || `inspect_class failed for '${className}'`,
439
+ className,
440
+ cdo: res?.cdo ?? null
441
+ });
442
+ }
443
+ return cleanObject(res);
444
+ }
445
+ default:
446
+ // Fallback to generic automation request if action not explicitly handled
447
+ const res = await executeAutomationRequest(tools, 'inspect', args, 'Automation bridge not available for inspect operations');
448
+ return cleanObject(res);
449
+ }
450
+ }
@@ -0,0 +1,252 @@
1
+ import { cleanObject } from '../../utils/safe-json.js';
2
+ import { ITools } from '../../types/tool-interfaces.js';
3
+ import { executeAutomationRequest, requireNonEmptyString } from './common-handlers.js';
4
+
5
+ export async function handleLevelTools(action: string, args: any, tools: ITools) {
6
+ switch (action) {
7
+ case 'load':
8
+ case 'load_level': {
9
+ const levelPath = requireNonEmptyString(args.levelPath, 'levelPath', 'Missing required parameter: levelPath');
10
+ const res = await tools.levelTools.loadLevel({ levelPath, streaming: !!args.streaming });
11
+ return cleanObject(res);
12
+ }
13
+ case 'save': {
14
+ const targetPath = args.levelPath || args.savePath;
15
+ if (targetPath) {
16
+ const res = await tools.levelTools.saveLevelAs({ targetPath });
17
+ return cleanObject(res);
18
+ }
19
+ const res = await tools.levelTools.saveLevel({ levelName: args.levelName });
20
+ return cleanObject(res);
21
+ }
22
+ case 'save_as':
23
+ case 'save_level_as': {
24
+ // Accept savePath, destinationPath, or levelPath as the target
25
+ const targetPath = args.savePath || args.destinationPath || args.levelPath;
26
+ if (!targetPath) {
27
+ return {
28
+ success: false,
29
+ error: 'INVALID_ARGUMENT',
30
+ message: 'savePath is required for save_as action',
31
+ action
32
+ };
33
+ }
34
+ const res = await tools.levelTools.saveLevelAs({ targetPath });
35
+ return cleanObject(res);
36
+ }
37
+ case 'create_level': {
38
+ const levelName = requireNonEmptyString(args.levelName || (args.levelPath ? args.levelPath.split('/').pop() : ''), 'levelName', 'Missing required parameter: levelName');
39
+ const res = await tools.levelTools.createLevel({ levelName, savePath: args.savePath || args.levelPath });
40
+ return cleanObject(res);
41
+ }
42
+ case 'add_sublevel': {
43
+ const subLevelPath = requireNonEmptyString(args.subLevelPath || args.levelPath, 'subLevelPath', 'Missing required parameter: subLevelPath');
44
+ const res = await tools.levelTools.addSubLevel({
45
+ subLevelPath,
46
+ parentLevel: args.parentLevel || args.parentPath,
47
+ streamingMethod: args.streamingMethod
48
+ });
49
+ return cleanObject(res);
50
+ }
51
+ case 'stream': {
52
+ const levelPath = typeof args.levelPath === 'string' ? args.levelPath : undefined;
53
+ const levelName = typeof args.levelName === 'string' ? args.levelName : undefined;
54
+ if (!levelPath && !levelName) {
55
+ return cleanObject({
56
+ success: false,
57
+ error: 'INVALID_ARGUMENT',
58
+ message: 'Missing required parameter: levelPath (or levelName)',
59
+ action
60
+ });
61
+ }
62
+ if (typeof args.shouldBeLoaded !== 'boolean') {
63
+ return cleanObject({
64
+ success: false,
65
+ error: 'INVALID_ARGUMENT',
66
+ message: 'Missing required parameter: shouldBeLoaded (boolean)',
67
+ action,
68
+ levelPath,
69
+ levelName
70
+ });
71
+ }
72
+
73
+ const res = await tools.levelTools.streamLevel({
74
+ levelPath,
75
+ levelName,
76
+ shouldBeLoaded: args.shouldBeLoaded,
77
+ shouldBeVisible: args.shouldBeVisible
78
+ });
79
+ return cleanObject(res);
80
+ }
81
+ case 'create_light': {
82
+ // Delegate directly to the plugin's manage_level.create_light handler.
83
+ const res = await executeAutomationRequest(tools, 'manage_level', args);
84
+ return cleanObject(res);
85
+ }
86
+ case 'spawn_light': {
87
+ // Fallback to control_actor spawn if manage_level spawn_light is not supported
88
+ const lightClassMap: Record<string, string> = {
89
+ 'Point': '/Script/Engine.PointLight',
90
+ 'Directional': '/Script/Engine.DirectionalLight',
91
+ 'Spot': '/Script/Engine.SpotLight',
92
+ 'Sky': '/Script/Engine.SkyLight',
93
+ 'Rect': '/Script/Engine.RectLight'
94
+ };
95
+
96
+ const lightType = args.lightType || 'Point';
97
+ const classPath = lightClassMap[lightType] || '/Script/Engine.PointLight';
98
+
99
+ try {
100
+ const res = await tools.actorTools.spawn({
101
+ classPath,
102
+ actorName: args.name,
103
+ location: args.location,
104
+ rotation: args.rotation
105
+ });
106
+ return { ...cleanObject(res), action: 'spawn_light' };
107
+ } catch (_e) {
108
+ return await executeAutomationRequest(tools, 'manage_level', args);
109
+ }
110
+ }
111
+ case 'build_lighting': {
112
+ return cleanObject(await tools.lightingTools.buildLighting({
113
+ quality: (args.quality as any) || 'Preview',
114
+ buildOnlySelected: false,
115
+ buildReflectionCaptures: false
116
+ }));
117
+ }
118
+ case 'export_level': {
119
+ const res = await tools.levelTools.exportLevel({
120
+ levelPath: args.levelPath,
121
+ exportPath: args.exportPath || args.destinationPath,
122
+ timeoutMs: typeof args.timeoutMs === 'number' ? args.timeoutMs : undefined
123
+ });
124
+ return cleanObject(res);
125
+ }
126
+ case 'import_level': {
127
+ const res = await tools.levelTools.importLevel({
128
+ packagePath: args.packagePath || args.sourcePath, // Allow sourcePath as fallback for backward compat
129
+ destinationPath: args.destinationPath,
130
+ timeoutMs: typeof args.timeoutMs === 'number' ? args.timeoutMs : undefined
131
+ });
132
+ return cleanObject(res);
133
+ }
134
+ case 'list_levels': {
135
+ const res = await tools.levelTools.listLevels();
136
+ return cleanObject(res);
137
+ }
138
+ case 'get_summary': {
139
+ const res = await tools.levelTools.getLevelSummary(args.levelPath);
140
+ return cleanObject(res);
141
+ }
142
+ case 'delete': {
143
+ const levelPaths = Array.isArray(args.levelPaths) ? args.levelPaths : [args.levelPath];
144
+ const res = await tools.levelTools.deleteLevels({ levelPaths });
145
+ return cleanObject(res);
146
+ }
147
+ case 'set_metadata': {
148
+ const levelPath = requireNonEmptyString(args.levelPath, 'levelPath', 'Missing required parameter: levelPath');
149
+ const metadata = (args.metadata && typeof args.metadata === 'object') ? args.metadata : {};
150
+ const res = await executeAutomationRequest(tools, 'set_metadata', { assetPath: levelPath, metadata });
151
+ return cleanObject(res);
152
+ }
153
+ case 'load_cells': {
154
+ // Calculate origin/extent if min/max provided for C++ handler compatibility
155
+ let origin = args.origin;
156
+ let extent = args.extent;
157
+
158
+ if (!origin && args.min && args.max) {
159
+ const min = args.min;
160
+ const max = args.max;
161
+ origin = [(min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2];
162
+ extent = [(max[0] - min[0]) / 2, (max[1] - min[1]) / 2, (max[2] - min[2]) / 2];
163
+ }
164
+
165
+ const payload = {
166
+ subAction: 'load_cells',
167
+ origin: origin,
168
+ extent: extent,
169
+ ...args // Allow other args to override if explicit
170
+ };
171
+
172
+ const res = await executeAutomationRequest(tools, 'manage_world_partition', payload);
173
+ return cleanObject(res);
174
+ }
175
+ case 'set_datalayer': {
176
+ const dataLayerName = args.dataLayerName || args.dataLayerLabel;
177
+ if (!dataLayerName || typeof dataLayerName !== 'string' || dataLayerName.trim().length === 0) {
178
+ return cleanObject({
179
+ success: false,
180
+ error: 'INVALID_ARGUMENT',
181
+ message: 'Missing required parameter: dataLayerLabel (or dataLayerName)',
182
+ action
183
+ });
184
+ }
185
+ if (!args.dataLayerState || typeof args.dataLayerState !== 'string') {
186
+ return cleanObject({
187
+ success: false,
188
+ error: 'INVALID_ARGUMENT',
189
+ message: 'Missing required parameter: dataLayerState',
190
+ action,
191
+ dataLayerName
192
+ });
193
+ }
194
+
195
+ const res = await executeAutomationRequest(tools, 'manage_world_partition', {
196
+ subAction: 'set_datalayer',
197
+ actorPath: args.actorPath,
198
+ dataLayerName, // Map label to name
199
+ ...args
200
+ });
201
+ return cleanObject(res);
202
+ }
203
+ case 'cleanup_invalid_datalayers': {
204
+ // Route to manage_world_partition
205
+ const res = await executeAutomationRequest(tools, 'manage_world_partition', {
206
+ subAction: 'cleanup_invalid_datalayers'
207
+ }, 'World Partition support not available');
208
+ return cleanObject(res);
209
+ }
210
+ case 'validate_level': {
211
+ const levelPath = requireNonEmptyString(args.levelPath, 'levelPath', 'Missing required parameter: levelPath');
212
+
213
+ // Prefer an editor-side existence check when the automation bridge is available.
214
+ const automationBridge = tools.automationBridge;
215
+ if (!automationBridge || typeof automationBridge.sendAutomationRequest !== 'function' || !automationBridge.isConnected()) {
216
+ return cleanObject({
217
+ success: false,
218
+ error: 'BRIDGE_UNAVAILABLE',
219
+ message: 'Automation bridge not available; cannot validate level asset',
220
+ levelPath
221
+ });
222
+ }
223
+
224
+ try {
225
+ const resp: any = await automationBridge.sendAutomationRequest('execute_editor_function', {
226
+ functionName: 'ASSET_EXISTS_SIMPLE',
227
+ path: levelPath
228
+ });
229
+ const result = resp?.result ?? resp ?? {};
230
+ const exists = Boolean(result.exists);
231
+
232
+ return cleanObject({
233
+ success: true,
234
+ exists,
235
+ levelPath: result.path ?? levelPath,
236
+ classPath: result.class,
237
+ error: exists ? undefined : 'NOT_FOUND',
238
+ message: exists ? 'Level asset exists' : 'Level asset not found'
239
+ });
240
+ } catch (err) {
241
+ return cleanObject({
242
+ success: false,
243
+ error: 'VALIDATION_FAILED',
244
+ message: `Level validation failed: ${err instanceof Error ? err.message : String(err)}`,
245
+ levelPath
246
+ });
247
+ }
248
+ }
249
+ default:
250
+ return await executeAutomationRequest(tools, 'manage_level', args);
251
+ }
252
+ }