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
@@ -1,1190 +1,323 @@
1
- // Consolidated tool handlers - maps 13 tools to all 36 operations
2
1
  import { cleanObject } from '../utils/safe-json.js';
3
2
  import { Logger } from '../utils/logger.js';
4
-
5
- const log = new Logger('ConsolidatedToolHandler');
6
-
7
- const ACTION_REQUIRED_ERROR = 'Missing required parameter: action';
8
-
9
- function ensureArgsPresent(args: any) {
10
- if (args === null || args === undefined) {
11
- throw new Error('Invalid arguments: null or undefined');
12
- }
3
+ import { ResponseFactory } from '../utils/response-factory.js';
4
+ import { ITools } from '../types/tool-interfaces.js';
5
+ import { executeAutomationRequest, requireAction } from './handlers/common-handlers.js';
6
+ import { handleAssetTools } from './handlers/asset-handlers.js';
7
+ import { handleActorTools } from './handlers/actor-handlers.js';
8
+ import { handleEditorTools } from './handlers/editor-handlers.js';
9
+ import { handleLevelTools } from './handlers/level-handlers.js';
10
+ import { handleBlueprintTools, handleBlueprintGet } from './handlers/blueprint-handlers.js';
11
+ import { handleSequenceTools } from './handlers/sequence-handlers.js';
12
+ import { handleAnimationTools } from './handlers/animation-handlers.js';
13
+ import { handleEffectTools } from './handlers/effect-handlers.js';
14
+ import { handleEnvironmentTools } from './handlers/environment-handlers.js';
15
+ import { handleSystemTools, handleConsoleCommand } from './handlers/system-handlers.js';
16
+ import { handleInspectTools } from './handlers/inspect-handlers.js';
17
+ import { handlePipelineTools } from './handlers/pipeline-handlers.js';
18
+ import { handleGraphTools } from './handlers/graph-handlers.js';
19
+ import { handleAudioTools } from './handlers/audio-handlers.js';
20
+ import { handleLightingTools } from './handlers/lighting-handlers.js';
21
+ import { handlePerformanceTools } from './handlers/performance-handlers.js';
22
+ import { handleInputTools } from './handlers/input-handlers.js';
23
+ import { getDynamicHandlerForTool } from './dynamic-handler-registry.js';
24
+ import { consolidatedToolDefinitions } from './consolidated-tool-definitions.js';
25
+
26
+ type NormalizedToolCall = {
27
+ name: string;
28
+ action: string;
29
+ args: any;
30
+ };
31
+
32
+ const MATERIAL_GRAPH_ACTION_MAP: Record<string, string> = {
33
+ add_material_node: 'add_node',
34
+ connect_material_pins: 'connect_pins',
35
+ remove_material_node: 'remove_node',
36
+ break_material_connections: 'break_connections',
37
+ get_material_node_details: 'get_node_details',
38
+ };
39
+
40
+ const BEHAVIOR_TREE_ACTION_MAP: Record<string, string> = {
41
+ add_bt_node: 'add_node',
42
+ connect_bt_nodes: 'connect_nodes',
43
+ remove_bt_node: 'remove_node',
44
+ break_bt_connections: 'break_connections',
45
+ set_bt_node_properties: 'set_node_properties'
46
+ };
47
+
48
+ const NIAGARA_GRAPH_ACTION_MAP: Record<string, string> = {
49
+ add_niagara_module: 'add_module',
50
+ connect_niagara_pins: 'connect_pins',
51
+ remove_niagara_node: 'remove_node',
52
+ set_niagara_parameter: 'set_parameter'
53
+ };
54
+
55
+ function isMaterialGraphAction(action: string): boolean {
56
+ return (
57
+ Object.prototype.hasOwnProperty.call(MATERIAL_GRAPH_ACTION_MAP, action) ||
58
+ action.includes('material_node') ||
59
+ action.includes('material_pins') ||
60
+ action.includes('material_connections')
61
+ );
13
62
  }
14
63
 
15
- function requireAction(args: any): string {
16
- ensureArgsPresent(args);
17
- const action = args.action;
18
- if (typeof action !== 'string' || action.trim() === '') {
19
- throw new Error(ACTION_REQUIRED_ERROR);
20
- }
21
- return action;
64
+ function isBehaviorTreeGraphAction(action: string): boolean {
65
+ return (
66
+ Object.prototype.hasOwnProperty.call(BEHAVIOR_TREE_ACTION_MAP, action) ||
67
+ action.includes('_bt_') ||
68
+ action.includes('behavior_tree')
69
+ );
22
70
  }
23
71
 
24
- function requireNonEmptyString(value: any, field: string, message?: string): string {
25
- if (typeof value !== 'string' || value.trim() === '') {
26
- throw new Error(message ?? `Invalid ${field}: must be a non-empty string`);
27
- }
28
- return value;
72
+ function isNiagaraGraphAction(action: string): boolean {
73
+ return (
74
+ Object.prototype.hasOwnProperty.call(NIAGARA_GRAPH_ACTION_MAP, action) ||
75
+ action.includes('niagara_module') ||
76
+ action.includes('niagara_pins') ||
77
+ action.includes('niagara_node') ||
78
+ action.includes('niagara_parameter')
79
+ );
29
80
  }
30
81
 
31
- function requirePositiveNumber(value: any, field: string, message?: string): number {
32
- if (typeof value !== 'number' || !isFinite(value) || value <= 0) {
33
- throw new Error(message ?? `Invalid ${field}: must be a positive number`);
82
+ function normalizeToolCall(
83
+ name: string,
84
+ args: any
85
+ ): NormalizedToolCall {
86
+ let normalizedName = name;
87
+ let action: string;
88
+
89
+ if (args && typeof (args as any).action === 'string') {
90
+ action = (args as any).action;
91
+ } else if (normalizedName === 'console_command') {
92
+ normalizedName = 'system_control';
93
+ action = 'console_command';
94
+ } else {
95
+ action = requireAction(args);
34
96
  }
35
- return value;
36
- }
37
97
 
38
- function requireVector3Components(
39
- vector: any,
40
- message: string
41
- ): [number, number, number] {
42
- if (
43
- !vector ||
44
- typeof vector.x !== 'number' ||
45
- typeof vector.y !== 'number' ||
46
- typeof vector.z !== 'number'
47
- ) {
48
- throw new Error(message);
98
+ if (normalizedName === 'create_effect') normalizedName = 'manage_effect';
99
+ if (normalizedName === 'console_command') {
100
+ normalizedName = 'system_control';
101
+ action = 'console_command';
49
102
  }
50
- return [vector.x, vector.y, vector.z];
51
- }
52
-
53
- function getElicitationTimeoutMs(tools: any): number | undefined {
54
- if (!tools) return undefined;
55
- const direct = tools.elicitationTimeoutMs;
56
- if (typeof direct === 'number' && Number.isFinite(direct)) {
57
- return direct;
103
+ if (normalizedName === 'manage_pipeline') {
104
+ normalizedName = 'system_control';
105
+ action = 'run_ubt';
58
106
  }
59
- if (typeof tools.getElicitationTimeoutMs === 'function') {
60
- const value = tools.getElicitationTimeoutMs();
61
- if (typeof value === 'number' && Number.isFinite(value)) {
62
- return value;
63
- }
107
+ if (normalizedName === 'manage_tests') {
108
+ normalizedName = 'system_control';
109
+ action = 'run_tests';
64
110
  }
65
- return undefined;
111
+
112
+ return {
113
+ name: normalizedName,
114
+ action,
115
+ args
116
+ };
66
117
  }
67
118
 
68
- async function elicitMissingPrimitiveArgs(
69
- tools: any,
119
+ async function invokeNamedTool(
120
+ name: string,
121
+ action: string,
70
122
  args: any,
71
- prompt: string,
72
- fieldSchemas: Record<string, { type: 'string' | 'number' | 'integer' | 'boolean'; title?: string; description?: string; enum?: string[]; enumNames?: string[]; minimum?: number; maximum?: number; minLength?: number; maxLength?: number; pattern?: string; format?: string; default?: unknown }>
123
+ tools: ITools
73
124
  ) {
74
- if (
75
- !tools ||
76
- typeof tools.supportsElicitation !== 'function' ||
77
- !tools.supportsElicitation() ||
78
- typeof tools.elicit !== 'function'
79
- ) {
80
- return;
81
- }
82
-
83
- const properties: Record<string, any> = {};
84
- const required: string[] = [];
85
-
86
- for (const [key, schema] of Object.entries(fieldSchemas)) {
87
- const value = args?.[key];
88
- const missing =
89
- value === undefined ||
90
- value === null ||
91
- (typeof value === 'string' && value.trim() === '');
92
- if (missing) {
93
- properties[key] = schema;
94
- required.push(key);
125
+ switch (name) {
126
+ // 1. ASSET MANAGER
127
+ case 'manage_asset': {
128
+ // Reroute merged functionality
129
+ if (['create_render_target', 'nanite_rebuild_mesh'].includes(action)) {
130
+ const payload = { ...args, subAction: action };
131
+ return cleanObject(await executeAutomationRequest(tools, 'manage_render', payload, `Automation bridge not available for ${action}`));
132
+ }
133
+ if (isMaterialGraphAction(action)) {
134
+ // Map new action names to legacy subActions if needed, or just pass through if plugin supports them.
135
+ // The plugin expects 'manage_material_graph' for these.
136
+ // We need to map 'add_material_node' -> 'add_node' etc if the plugin is strict,
137
+ // or we assume we updated the plugin.
138
+ // For now, let's assume we map them to the old tool 'manage_material_graph' with the old action names.
139
+ const subAction = MATERIAL_GRAPH_ACTION_MAP[action] || action;
140
+
141
+ return await handleGraphTools('manage_material_graph', subAction, args, tools);
142
+ }
143
+ if (isBehaviorTreeGraphAction(action)) {
144
+ const subAction = BEHAVIOR_TREE_ACTION_MAP[action] || action;
145
+ return await handleGraphTools('manage_behavior_tree', subAction, args, tools);
146
+ }
147
+ return await handleAssetTools(action, args, tools);
95
148
  }
96
- }
97
149
 
98
- if (required.length === 0) return;
150
+ // 2. BLUEPRINT MANAGER
99
151
 
100
- const timeoutMs = getElicitationTimeoutMs(tools);
101
- const options: any = {
102
- fallback: async () => ({ ok: false, error: 'missing-params' })
103
- };
104
- if (typeof timeoutMs === 'number') {
105
- options.timeoutMs = timeoutMs;
106
- }
107
-
108
- try {
109
- const elicited = await tools.elicit(
110
- prompt,
111
- { type: 'object', properties, required },
112
- options
113
- );
114
-
115
- if (elicited?.ok && elicited.value) {
116
- for (const key of required) {
117
- const value = elicited.value[key];
118
- if (value === undefined || value === null) continue;
119
- args[key] = typeof value === 'string' ? value.trim() : value;
152
+ case 'manage_blueprint': {
153
+ if (action === 'get_blueprint') {
154
+ return await handleBlueprintGet(args, tools);
155
+ }
156
+ // Graph actions
157
+ const graphActions = ['create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property', 'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'];
158
+ if (graphActions.includes(action)) {
159
+ return await handleGraphTools('manage_blueprint_graph', action, args, tools);
120
160
  }
161
+ return await handleBlueprintTools(action, args, tools);
121
162
  }
122
- } catch (err) {
123
- log.debug('Special elicitation fallback skipped', {
124
- prompt,
125
- err: (err as any)?.message || String(err)
126
- });
127
- }
128
- }
129
163
 
130
- export async function handleConsolidatedToolCall(
131
- name: string,
132
- args: any,
133
- tools: any
134
- ) {
135
- const startTime = Date.now();
136
- // Use scoped logger (stderr) to avoid polluting stdout JSON
137
- log.debug(`Starting execution of ${name} at ${new Date().toISOString()}`);
138
-
139
- try {
140
- ensureArgsPresent(args);
164
+ // 3. ACTOR CONTROL
165
+ case 'control_actor':
166
+ return await handleActorTools(action, args, tools);
141
167
 
142
- switch (name) {
143
- // 1. ASSET MANAGER
144
- case 'manage_asset':
145
- switch (requireAction(args)) {
146
- case 'list': {
147
- if (args.directory !== undefined && args.directory !== null && typeof args.directory !== 'string') {
148
- throw new Error('Invalid directory: must be a string');
149
- }
150
- const res = await tools.assetResources.list(args.directory || '/Game', false);
151
- return cleanObject({ success: true, ...res });
152
- }
153
- case 'import': {
154
- let sourcePath = typeof args.sourcePath === 'string' ? args.sourcePath.trim() : '';
155
- let destinationPath = typeof args.destinationPath === 'string' ? args.destinationPath.trim() : '';
168
+ // 4. EDITOR CONTROL
169
+ case 'control_editor': {
170
+ if (action === 'simulate_input') {
171
+ const payload = { ...args, subAction: action };
172
+ return cleanObject(await executeAutomationRequest(tools, 'manage_ui', payload, 'Automation bridge not available'));
173
+ }
174
+ return await handleEditorTools(action, args, tools);
175
+ }
156
176
 
157
- if ((!sourcePath || !destinationPath) && typeof tools.supportsElicitation === 'function' && tools.supportsElicitation() && typeof tools.elicit === 'function') {
158
- const schemaProps: Record<string, any> = {};
159
- const required: string[] = [];
177
+ // 5. LEVEL MANAGER
178
+ case 'manage_level': {
179
+ if (['load_cells', 'set_datalayer'].includes(action)) {
180
+ const payload = { ...args, subAction: action };
181
+ return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', payload, 'Automation bridge not available'));
182
+ }
183
+ return await handleLevelTools(action, args, tools);
184
+ }
160
185
 
161
- if (!sourcePath) {
162
- schemaProps.sourcePath = {
163
- type: 'string',
164
- title: 'Source File Path',
165
- description: 'Full path to the asset file on disk to import'
166
- };
167
- required.push('sourcePath');
168
- }
186
+ // 6. ANIMATION & PHYSICS
187
+ case 'animation_physics':
188
+ return await handleAnimationTools(action, args, tools);
189
+
190
+ // 7. EFFECTS MANAGER (Renamed from create_effect)
191
+ case 'manage_effect': {
192
+ if (isNiagaraGraphAction(action)) {
193
+ // Special case: set_niagara_parameter can be for actor (Effect Tool) or asset (Graph Tool)
194
+ // If actorName is present, or systemName is present but no path, it's an instance operation -> handleEffectTools
195
+ const isInstanceOp = action === 'set_niagara_parameter' && (args.actorName || (args.systemName && !args.assetPath && !args.systemPath));
196
+ if (isInstanceOp) {
197
+ return await handleEffectTools(action, args, tools);
198
+ }
169
199
 
170
- if (!destinationPath) {
171
- schemaProps.destinationPath = {
172
- type: 'string',
173
- title: 'Destination Path',
174
- description: 'Unreal content path where the asset should be imported (e.g., /Game/MCP/Assets)'
175
- };
176
- required.push('destinationPath');
177
- }
200
+ const subAction = NIAGARA_GRAPH_ACTION_MAP[action] || action;
201
+ return await handleGraphTools('manage_niagara_graph', subAction, args, tools);
202
+ }
203
+ return await handleEffectTools(action, args, tools);
204
+ }
178
205
 
179
- if (required.length > 0) {
180
- const timeoutMs = getElicitationTimeoutMs(tools);
181
- const options: any = { fallback: async () => ({ ok: false, error: 'missing-import-params' }) };
182
- if (typeof timeoutMs === 'number') {
183
- options.timeoutMs = timeoutMs;
184
- }
185
- const elicited = await tools.elicit(
186
- 'Provide the missing import parameters for manage_asset.import',
187
- { type: 'object', properties: schemaProps, required },
188
- options
189
- );
206
+ // 8. ENVIRONMENT BUILDER
207
+ case 'build_environment':
208
+ return await handleEnvironmentTools(action, args, tools);
190
209
 
191
- if (elicited?.ok && elicited.value) {
192
- if (typeof elicited.value.sourcePath === 'string') {
193
- sourcePath = elicited.value.sourcePath.trim();
194
- }
195
- if (typeof elicited.value.destinationPath === 'string') {
196
- destinationPath = elicited.value.destinationPath.trim();
197
- }
198
- }
199
- }
200
- }
210
+ // 9. SYSTEM CONTROL
211
+ case 'system_control': {
212
+ if (action === 'console_command') return await handleConsoleCommand(args, tools);
213
+ if (action === 'run_ubt') return await handlePipelineTools(action, args, tools);
201
214
 
202
- const sourcePathValidated = requireNonEmptyString(sourcePath || args.sourcePath, 'sourcePath', 'Invalid sourcePath');
203
- const destinationPathValidated = requireNonEmptyString(destinationPath || args.destinationPath, 'destinationPath', 'Invalid destinationPath');
204
- const res = await tools.assetTools.importAsset(sourcePathValidated, destinationPathValidated);
205
- return cleanObject(res);
206
- }
207
- case 'create_material': {
208
- await elicitMissingPrimitiveArgs(
209
- tools,
210
- args,
211
- 'Provide the material details for manage_asset.create_material',
212
- {
213
- name: {
214
- type: 'string',
215
- title: 'Material Name',
216
- description: 'Name for the new material asset'
217
- },
218
- path: {
219
- type: 'string',
220
- title: 'Save Path',
221
- description: 'Optional Unreal content path where the material should be saved'
222
- }
223
- }
224
- );
225
- const sanitizedName = typeof args.name === 'string' ? args.name.trim() : args.name;
226
- const sanitizedPath = typeof args.path === 'string' ? args.path.trim() : args.path;
227
- const name = requireNonEmptyString(sanitizedName, 'name', 'Invalid name: must be a non-empty string');
228
- const res = await tools.materialTools.createMaterial(name, sanitizedPath || '/Game/Materials');
229
- return cleanObject(res);
230
- }
231
- default:
232
- throw new Error(`Unknown asset action: ${args.action}`);
233
- }
215
+ // Bridge forwards
216
+ if (action === 'run_tests') return cleanObject(await executeAutomationRequest(tools, 'manage_tests', { ...args, subAction: action }, 'Bridge unavailable'));
217
+ if (action === 'subscribe' || action === 'unsubscribe') return cleanObject(await executeAutomationRequest(tools, 'manage_logs', { ...args, subAction: action }, 'Bridge unavailable'));
218
+ if (action === 'spawn_category') return cleanObject(await executeAutomationRequest(tools, 'manage_debug', { ...args, subAction: action }, 'Bridge unavailable'));
219
+ if (action === 'start_session') return cleanObject(await executeAutomationRequest(tools, 'manage_insights', { ...args, subAction: action }, 'Bridge unavailable'));
220
+ if (action === 'lumen_update_scene') return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
234
221
 
235
- // 2. ACTOR CONTROL
236
- case 'control_actor':
237
- switch (requireAction(args)) {
238
- case 'spawn': {
239
- await elicitMissingPrimitiveArgs(
240
- tools,
241
- args,
242
- 'Provide the spawn parameters for control_actor.spawn',
243
- {
244
- classPath: {
245
- type: 'string',
246
- title: 'Actor Class or Asset Path',
247
- description: 'Class name (e.g., StaticMeshActor) or asset path (e.g., /Engine/BasicShapes/Cube) to spawn'
248
- }
249
- }
250
- );
251
- const classPathInput = typeof args.classPath === 'string' ? args.classPath.trim() : args.classPath;
252
- const classPath = requireNonEmptyString(classPathInput, 'classPath', 'Invalid classPath: must be a non-empty string');
253
- const actorNameInput = typeof args.actorName === 'string' && args.actorName.trim() !== ''
254
- ? args.actorName
255
- : (typeof args.name === 'string' ? args.name : undefined);
256
- const res = await tools.actorTools.spawn({
257
- classPath,
258
- location: args.location,
259
- rotation: args.rotation,
260
- actorName: actorNameInput
261
- });
262
- return cleanObject(res);
263
- }
264
- case 'delete': {
265
- await elicitMissingPrimitiveArgs(
266
- tools,
267
- args,
268
- 'Which actor should control_actor.delete remove?',
269
- {
270
- actorName: {
271
- type: 'string',
272
- title: 'Actor Name',
273
- description: 'Exact label of the actor to delete'
274
- }
275
- }
276
- );
277
- const actorNameArg = typeof args.actorName === 'string' && args.actorName.trim() !== ''
278
- ? args.actorName
279
- : (typeof args.name === 'string' ? args.name : undefined);
280
- const actorName = requireNonEmptyString(actorNameArg, 'actorName', 'Invalid actorName');
281
- const res = await tools.bridge.executeEditorFunction('DELETE_ACTOR', { actor_name: actorName });
282
- return cleanObject(res);
283
- }
284
- case 'apply_force': {
285
- await elicitMissingPrimitiveArgs(
286
- tools,
287
- args,
288
- 'Provide the target actor for control_actor.apply_force',
289
- {
290
- actorName: {
291
- type: 'string',
292
- title: 'Actor Name',
293
- description: 'Physics-enabled actor that should receive the force'
294
- }
295
- }
296
- );
297
- const actorName = requireNonEmptyString(args.actorName, 'actorName', 'Invalid actorName');
298
- const vector = requireVector3Components(args.force, 'Invalid force: must have numeric x,y,z');
299
- const res = await tools.physicsTools.applyForce({
300
- actorName,
301
- forceType: 'Force',
302
- vector
303
- });
304
- return cleanObject(res);
305
- }
306
- default:
307
- throw new Error(`Unknown actor action: ${args.action}`);
308
- }
222
+ return await handleSystemTools(action, args, tools);
223
+ }
309
224
 
310
- // 3. EDITOR CONTROL
311
- case 'control_editor':
312
- switch (requireAction(args)) {
313
- case 'play': {
314
- const res = await tools.editorTools.playInEditor();
315
- return cleanObject(res);
316
- }
317
- case 'stop': {
318
- const res = await tools.editorTools.stopPlayInEditor();
319
- return cleanObject(res);
320
- }
321
- case 'pause': {
322
- const res = await tools.editorTools.pausePlayInEditor();
323
- return cleanObject(res);
324
- }
325
- case 'set_game_speed': {
326
- const speed = requirePositiveNumber(args.speed, 'speed', 'Invalid speed: must be a positive number');
327
- // Use console command via bridge
328
- const res = await tools.bridge.executeConsoleCommand(`slomo ${speed}`);
329
- return cleanObject(res);
330
- }
331
- case 'eject': {
332
- const res = await tools.bridge.executeConsoleCommand('eject');
333
- return cleanObject(res);
334
- }
335
- case 'possess': {
336
- const res = await tools.bridge.executeConsoleCommand('viewself');
337
- return cleanObject(res);
338
- }
339
- case 'set_camera': {
340
- const res = await tools.editorTools.setViewportCamera(args.location, args.rotation);
341
- return cleanObject(res);
342
- }
343
- case 'set_view_mode': {
344
- await elicitMissingPrimitiveArgs(
345
- tools,
346
- args,
347
- 'Provide the view mode for control_editor.set_view_mode',
348
- {
349
- viewMode: {
350
- type: 'string',
351
- title: 'View Mode',
352
- description: 'Viewport view mode (e.g., Lit, Unlit, Wireframe)'
353
- }
354
- }
355
- );
356
- const viewMode = requireNonEmptyString(args.viewMode, 'viewMode', 'Missing required parameter: viewMode');
357
- const res = await tools.bridge.setSafeViewMode(viewMode);
358
- return cleanObject(res);
359
- }
360
- default:
361
- throw new Error(`Unknown editor action: ${args.action}`);
362
- }
225
+ // 10. SEQUENCER
226
+ case 'manage_sequence':
227
+ return await handleSequenceTools(action, args, tools);
363
228
 
364
- // 4. LEVEL MANAGER
365
- case 'manage_level':
366
- switch (requireAction(args)) {
367
- case 'load': {
368
- await elicitMissingPrimitiveArgs(
369
- tools,
370
- args,
371
- 'Select the level to load for manage_level.load',
372
- {
373
- levelPath: {
374
- type: 'string',
375
- title: 'Level Path',
376
- description: 'Content path of the level asset to load (e.g., /Game/Maps/MyLevel)'
377
- }
378
- }
379
- );
380
- const levelPath = requireNonEmptyString(args.levelPath, 'levelPath', 'Missing required parameter: levelPath');
381
- const res = await tools.levelTools.loadLevel({ levelPath, streaming: !!args.streaming });
382
- return cleanObject(res);
383
- }
384
- case 'save': {
385
- const res = await tools.levelTools.saveLevel({ levelName: args.levelName, savePath: args.savePath });
386
- return cleanObject(res);
387
- }
388
- case 'stream': {
389
- await elicitMissingPrimitiveArgs(
390
- tools,
391
- args,
392
- 'Provide the streaming level name for manage_level.stream',
393
- {
394
- levelName: {
395
- type: 'string',
396
- title: 'Level Name',
397
- description: 'Streaming level name to toggle'
398
- }
399
- }
400
- );
401
- const levelName = requireNonEmptyString(args.levelName, 'levelName', 'Missing required parameter: levelName');
402
- const res = await tools.levelTools.streamLevel({ levelName, shouldBeLoaded: !!args.shouldBeLoaded, shouldBeVisible: !!args.shouldBeVisible });
403
- return cleanObject(res);
404
- }
405
- case 'create_light': {
406
- await elicitMissingPrimitiveArgs(
407
- tools,
408
- args,
409
- 'Provide the light details for manage_level.create_light',
410
- {
411
- lightType: {
412
- type: 'string',
413
- title: 'Light Type',
414
- description: 'Directional, Point, Spot, Rect, or Sky'
415
- },
416
- name: {
417
- type: 'string',
418
- title: 'Light Name',
419
- description: 'Name for the new light actor'
420
- }
421
- }
422
- );
423
- const lightType = requireNonEmptyString(args.lightType, 'lightType', 'Missing required parameter: lightType');
424
- const name = requireNonEmptyString(args.name, 'name', 'Invalid name');
425
- const typeKey = lightType.toLowerCase();
426
- const toVector = (value: any, fallback: [number, number, number]): [number, number, number] => {
427
- if (Array.isArray(value) && value.length === 3) {
428
- return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
429
- }
430
- if (value && typeof value === 'object') {
431
- return [Number(value.x) || 0, Number(value.y) || 0, Number(value.z) || 0];
432
- }
433
- return fallback;
434
- };
435
- const toRotator = (value: any, fallback: [number, number, number]): [number, number, number] => {
436
- if (Array.isArray(value) && value.length === 3) {
437
- return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
438
- }
439
- if (value && typeof value === 'object') {
440
- return [Number(value.pitch) || 0, Number(value.yaw) || 0, Number(value.roll) || 0];
441
- }
442
- return fallback;
443
- };
444
- const toColor = (value: any): [number, number, number] | undefined => {
445
- if (Array.isArray(value) && value.length === 3) {
446
- return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
447
- }
448
- if (value && typeof value === 'object') {
449
- return [Number(value.r) || 0, Number(value.g) || 0, Number(value.b) || 0];
450
- }
451
- return undefined;
452
- };
229
+ // 11. INTROSPECTION
230
+ case 'inspect':
231
+ return await handleInspectTools(action, args, tools);
453
232
 
454
- const location = toVector(args.location, [0, 0, typeKey === 'directional' ? 500 : 0]);
455
- const rotation = toRotator(args.rotation, [0, 0, 0]);
456
- const color = toColor(args.color);
457
- const castShadows = typeof args.castShadows === 'boolean' ? args.castShadows : undefined;
233
+ // 12. AUDIO
234
+ case 'manage_audio':
235
+ return await handleAudioTools(action, args, tools);
458
236
 
459
- if (typeKey === 'directional') {
460
- return cleanObject(await tools.lightingTools.createDirectionalLight({
461
- name,
462
- intensity: args.intensity,
463
- color,
464
- rotation,
465
- castShadows,
466
- temperature: args.temperature
467
- }));
468
- }
469
- if (typeKey === 'point') {
470
- return cleanObject(await tools.lightingTools.createPointLight({
471
- name,
472
- location,
473
- intensity: args.intensity,
474
- radius: args.radius,
475
- color,
476
- falloffExponent: args.falloffExponent,
477
- castShadows
478
- }));
479
- }
480
- if (typeKey === 'spot') {
481
- const innerCone = typeof args.innerCone === 'number' ? args.innerCone : undefined;
482
- const outerCone = typeof args.outerCone === 'number' ? args.outerCone : undefined;
483
- if (innerCone !== undefined && outerCone !== undefined && innerCone >= outerCone) {
484
- throw new Error('innerCone must be less than outerCone');
485
- }
486
- return cleanObject(await tools.lightingTools.createSpotLight({
487
- name,
488
- location,
489
- rotation,
490
- intensity: args.intensity,
491
- innerCone: args.innerCone,
492
- outerCone: args.outerCone,
493
- radius: args.radius,
494
- color,
495
- castShadows
496
- }));
497
- }
498
- if (typeKey === 'rect') {
499
- return cleanObject(await tools.lightingTools.createRectLight({
500
- name,
501
- location,
502
- rotation,
503
- intensity: args.intensity,
504
- width: args.width,
505
- height: args.height,
506
- color
507
- }));
508
- }
509
- if (typeKey === 'sky' || typeKey === 'skylight') {
510
- return cleanObject(await tools.lightingTools.createSkyLight({
511
- name,
512
- sourceType: args.sourceType,
513
- cubemapPath: args.cubemapPath,
514
- intensity: args.intensity,
515
- recapture: args.recapture
516
- }));
517
- }
518
- throw new Error(`Unknown light type: ${lightType}`);
519
- }
520
- case 'build_lighting': {
521
- const res = await tools.lightingTools.buildLighting({ quality: args.quality || 'High', buildReflectionCaptures: true });
522
- return cleanObject(res);
523
- }
524
- default:
525
- throw new Error(`Unknown level action: ${args.action}`);
526
- }
237
+ // 13. BEHAVIOR TREE
238
+ case 'manage_behavior_tree':
239
+ return await handleGraphTools('manage_behavior_tree', action, args, tools);
527
240
 
528
- // 5. ANIMATION & PHYSICS
529
- case 'animation_physics':
530
- switch (requireAction(args)) {
531
- case 'create_animation_bp': {
532
- await elicitMissingPrimitiveArgs(
533
- tools,
534
- args,
535
- 'Provide details for animation_physics.create_animation_bp',
536
- {
537
- name: {
538
- type: 'string',
539
- title: 'Blueprint Name',
540
- description: 'Name of the Animation Blueprint to create'
541
- },
542
- skeletonPath: {
543
- type: 'string',
544
- title: 'Skeleton Path',
545
- description: 'Content path of the skeleton asset to bind'
546
- }
547
- }
548
- );
549
- const name = requireNonEmptyString(args.name, 'name', 'Invalid name');
550
- const skeletonPath = requireNonEmptyString(args.skeletonPath, 'skeletonPath', 'Invalid skeletonPath');
551
- const res = await tools.animationTools.createAnimationBlueprint({ name, skeletonPath, savePath: args.savePath });
552
- return cleanObject(res);
553
- }
554
- case 'play_montage': {
555
- await elicitMissingPrimitiveArgs(
556
- tools,
557
- args,
558
- 'Provide playback details for animation_physics.play_montage',
559
- {
560
- actorName: {
561
- type: 'string',
562
- title: 'Actor Name',
563
- description: 'Actor that should play the montage'
564
- },
565
- montagePath: {
566
- type: 'string',
567
- title: 'Montage Path',
568
- description: 'Montage or animation asset path to play'
569
- }
570
- }
571
- );
572
- const actorName = requireNonEmptyString(args.actorName, 'actorName', 'Invalid actorName');
573
- const montagePath = args.montagePath || args.animationPath;
574
- const validatedMontage = requireNonEmptyString(montagePath, 'montagePath', 'Invalid montagePath');
575
- const res = await tools.animationTools.playAnimation({ actorName, animationType: 'Montage', animationPath: validatedMontage, playRate: args.playRate });
576
- return cleanObject(res);
577
- }
578
- case 'setup_ragdoll': {
579
- await elicitMissingPrimitiveArgs(
580
- tools,
581
- args,
582
- 'Provide setup details for animation_physics.setup_ragdoll',
583
- {
584
- skeletonPath: {
585
- type: 'string',
586
- title: 'Skeleton Path',
587
- description: 'Content path for the skeleton asset'
588
- },
589
- physicsAssetName: {
590
- type: 'string',
591
- title: 'Physics Asset Name',
592
- description: 'Name of the physics asset to apply'
593
- }
594
- }
595
- );
596
- const skeletonPath = requireNonEmptyString(args.skeletonPath, 'skeletonPath', 'Invalid skeletonPath');
597
- const physicsAssetName = requireNonEmptyString(args.physicsAssetName, 'physicsAssetName', 'Invalid physicsAssetName');
598
- const res = await tools.physicsTools.setupRagdoll({ skeletonPath, physicsAssetName, blendWeight: args.blendWeight, savePath: args.savePath });
599
- return cleanObject(res);
600
- }
601
- default:
602
- throw new Error(`Unknown animation/physics action: ${args.action}`);
603
- }
241
+ // 14. BLUEPRINT GRAPH DIRECT
242
+ case 'manage_blueprint_graph':
243
+ return await handleGraphTools('manage_blueprint_graph', action, args, tools);
604
244
 
605
- // 6. EFFECTS SYSTEM
606
- case 'create_effect':
607
- switch (requireAction(args)) {
608
- case 'particle': {
609
- await elicitMissingPrimitiveArgs(
610
- tools,
611
- args,
612
- 'Provide the particle effect details for create_effect.particle',
613
- {
614
- effectType: {
615
- type: 'string',
616
- title: 'Effect Type',
617
- description: 'Preset effect type to spawn (e.g., Fire, Smoke)'
618
- }
619
- }
620
- );
621
- const res = await tools.niagaraTools.createEffect({ effectType: args.effectType, name: args.name, location: args.location, scale: args.scale, customParameters: args.customParameters });
622
- return cleanObject(res);
623
- }
624
- case 'niagara': {
625
- await elicitMissingPrimitiveArgs(
626
- tools,
627
- args,
628
- 'Provide the Niagara system path for create_effect.niagara',
629
- {
630
- systemPath: {
631
- type: 'string',
632
- title: 'Niagara System Path',
633
- description: 'Asset path of the Niagara system to spawn'
634
- }
635
- }
636
- );
637
- const systemPath = requireNonEmptyString(args.systemPath, 'systemPath', 'Invalid systemPath');
638
- const verifyResult = await tools.bridge.executePythonWithResult(`
639
- import unreal, json
640
- path = r"${systemPath}"
641
- exists = unreal.EditorAssetLibrary.does_asset_exist(path)
642
- print('RESULT:' + json.dumps({'success': exists, 'exists': exists, 'path': path}))
643
- `.trim());
644
- if (!verifyResult?.exists) {
645
- return cleanObject({ success: false, error: `Niagara system not found at ${systemPath}` });
646
- }
647
- const loc = Array.isArray(args.location)
648
- ? { x: args.location[0], y: args.location[1], z: args.location[2] }
649
- : args.location || { x: 0, y: 0, z: 0 };
650
- const res = await tools.niagaraTools.spawnEffect({
651
- systemPath,
652
- location: [loc.x ?? 0, loc.y ?? 0, loc.z ?? 0],
653
- rotation: Array.isArray(args.rotation) ? args.rotation : undefined,
654
- scale: args.scale
655
- });
656
- return cleanObject(res);
657
- }
658
- case 'debug_shape': {
659
- const shapeInput = args.shape ?? 'Sphere';
660
- const shape = String(shapeInput).trim().toLowerCase();
661
- const originalShapeLabel = String(shapeInput).trim() || 'shape';
662
- const loc = args.location || { x: 0, y: 0, z: 0 };
663
- const size = args.size || 100;
664
- const color = args.color || [255, 0, 0, 255];
665
- const duration = args.duration || 5;
666
- if (shape === 'line') {
667
- const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
668
- return cleanObject(await tools.debugTools.drawDebugLine({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
669
- } else if (shape === 'box') {
670
- const extent = [size, size, size];
671
- return cleanObject(await tools.debugTools.drawDebugBox({ center: [loc.x, loc.y, loc.z], extent, color, duration }));
672
- } else if (shape === 'sphere') {
673
- return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
674
- } else if (shape === 'capsule') {
675
- return cleanObject(await tools.debugTools.drawDebugCapsule({ center: [loc.x, loc.y, loc.z], halfHeight: size, radius: Math.max(10, size/3), color, duration }));
676
- } else if (shape === 'cone') {
677
- return cleanObject(await tools.debugTools.drawDebugCone({ origin: [loc.x, loc.y, loc.z], direction: [0,0,1], length: size, angleWidth: 0.5, angleHeight: 0.5, color, duration }));
678
- } else if (shape === 'arrow') {
679
- const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
680
- return cleanObject(await tools.debugTools.drawDebugArrow({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
681
- } else if (shape === 'point') {
682
- return cleanObject(await tools.debugTools.drawDebugPoint({ location: [loc.x, loc.y, loc.z], size, color, duration }));
683
- } else if (shape === 'text' || shape === 'string') {
684
- const text = args.text || 'Debug';
685
- return cleanObject(await tools.debugTools.drawDebugString({ location: [loc.x, loc.y, loc.z], text, color, duration }));
686
- }
687
- // Default fallback
688
- return cleanObject({ success: false, error: `Unsupported debug shape: ${originalShapeLabel}` });
689
- }
690
- default:
691
- throw new Error(`Unknown effect action: ${args.action}`);
692
- }
245
+ // 15. RENDER TOOLS
246
+ case 'manage_render':
247
+ return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
693
248
 
694
- // 7. BLUEPRINT MANAGER
695
- case 'manage_blueprint':
696
- switch (requireAction(args)) {
697
- case 'create': {
698
- await elicitMissingPrimitiveArgs(
699
- tools,
700
- args,
701
- 'Provide details for manage_blueprint.create',
702
- {
703
- name: {
704
- type: 'string',
705
- title: 'Blueprint Name',
706
- description: 'Name for the new Blueprint asset'
707
- },
708
- blueprintType: {
709
- type: 'string',
710
- title: 'Blueprint Type',
711
- description: 'Base type such as Actor, Pawn, Character, etc.'
712
- }
713
- }
714
- );
715
- const res = await tools.blueprintTools.createBlueprint({
716
- name: args.name,
717
- blueprintType: args.blueprintType || 'Actor',
718
- savePath: args.savePath,
719
- parentClass: args.parentClass
720
- });
721
- return cleanObject(res);
722
- }
723
- case 'add_component': {
724
- await elicitMissingPrimitiveArgs(
725
- tools,
726
- args,
727
- 'Provide details for manage_blueprint.add_component',
728
- {
729
- name: {
730
- type: 'string',
731
- title: 'Blueprint Name',
732
- description: 'Blueprint asset to modify'
733
- },
734
- componentType: {
735
- type: 'string',
736
- title: 'Component Type',
737
- description: 'Component class to add (e.g., StaticMeshComponent)'
738
- },
739
- componentName: {
740
- type: 'string',
741
- title: 'Component Name',
742
- description: 'Name for the new component'
743
- }
744
- }
745
- );
746
- const res = await tools.blueprintTools.addComponent({ blueprintName: args.name, componentType: args.componentType, componentName: args.componentName });
747
- return cleanObject(res);
748
- }
749
- default:
750
- throw new Error(`Unknown blueprint action: ${args.action}`);
751
- }
249
+ // 16. WORLD PARTITION
250
+ case 'manage_world_partition':
251
+ return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', { ...args, subAction: action }, 'Bridge unavailable'));
752
252
 
753
- // 8. ENVIRONMENT BUILDER
754
- case 'build_environment':
755
- switch (requireAction(args)) {
756
- case 'create_landscape': {
757
- const res = await tools.landscapeTools.createLandscape({ name: args.name, sizeX: args.sizeX, sizeY: args.sizeY, materialPath: args.materialPath });
758
- return cleanObject(res);
759
- }
760
- case 'sculpt': {
761
- const res = await tools.landscapeTools.sculptLandscape({ landscapeName: args.name, tool: args.tool, brushSize: args.brushSize, strength: args.strength });
762
- return cleanObject(res);
763
- }
764
- case 'add_foliage': {
765
- const res = await tools.foliageTools.addFoliageType({ name: args.name, meshPath: args.meshPath, density: args.density });
766
- return cleanObject(res);
767
- }
768
- case 'paint_foliage': {
769
- const pos = args.position ? [args.position.x || 0, args.position.y || 0, args.position.z || 0] : [0,0,0];
770
- const res = await tools.foliageTools.paintFoliage({ foliageType: args.foliageType, position: pos, brushSize: args.brushSize, paintDensity: args.paintDensity, eraseMode: args.eraseMode });
771
- return cleanObject(res);
772
- }
773
- case 'create_procedural_terrain': {
774
- const loc = args.location ? [args.location.x||0, args.location.y||0, args.location.z||0] : [0,0,0];
775
- const res = await tools.buildEnvAdvanced.createProceduralTerrain({
776
- name: args.name || 'ProceduralTerrain',
777
- location: loc as [number,number,number],
778
- sizeX: args.sizeX,
779
- sizeY: args.sizeY,
780
- subdivisions: args.subdivisions,
781
- heightFunction: args.heightFunction,
782
- material: args.materialPath
783
- });
784
- return cleanObject(res);
785
- }
786
- case 'create_procedural_foliage': {
787
- if (!args.bounds || !args.bounds.location || !args.bounds.size) throw new Error('bounds.location and bounds.size are required');
788
- const bounds = {
789
- location: [args.bounds.location.x||0, args.bounds.location.y||0, args.bounds.location.z||0] as [number,number,number],
790
- size: [args.bounds.size.x||1000, args.bounds.size.y||1000, args.bounds.size.z||100] as [number,number,number]
791
- };
792
- const res = await tools.buildEnvAdvanced.createProceduralFoliage({
793
- name: args.name || 'ProceduralFoliage',
794
- bounds,
795
- foliageTypes: args.foliageTypes || [],
796
- seed: args.seed
797
- });
798
- return cleanObject(res);
799
- }
800
- case 'add_foliage_instances': {
801
- if (!args.foliageType) throw new Error('foliageType is required');
802
- if (!Array.isArray(args.transforms)) throw new Error('transforms array is required');
803
- const transforms = (args.transforms as any[]).map(t => ({
804
- location: [t.location?.x||0, t.location?.y||0, t.location?.z||0] as [number,number,number],
805
- rotation: t.rotation ? [t.rotation.pitch||0, t.rotation.yaw||0, t.rotation.roll||0] as [number,number,number] : undefined,
806
- scale: t.scale ? [t.scale.x||1, t.scale.y||1, t.scale.z||1] as [number,number,number] : undefined
807
- }));
808
- const res = await tools.buildEnvAdvanced.addFoliageInstances({ foliageType: args.foliageType, transforms });
809
- return cleanObject(res);
810
- }
811
- case 'create_landscape_grass_type': {
812
- const res = await tools.buildEnvAdvanced.createLandscapeGrassType({
813
- name: args.name || 'GrassType',
814
- meshPath: args.meshPath,
815
- density: args.density,
816
- minScale: args.minScale,
817
- maxScale: args.maxScale
818
- });
819
- return cleanObject(res);
820
- }
821
- default:
822
- throw new Error(`Unknown environment action: ${args.action}`);
823
- }
253
+ // 17. LIGHTING
254
+ case 'manage_lighting':
255
+ return await handleLightingTools(action, args, tools);
824
256
 
825
- // 9. SYSTEM CONTROL
826
- case 'system_control':
827
- switch (requireAction(args)) {
828
- case 'read_log': {
829
- const filterCategoryRaw = args.filter_category;
830
- const filterCategory = Array.isArray(filterCategoryRaw)
831
- ? filterCategoryRaw
832
- : typeof filterCategoryRaw === 'string' && filterCategoryRaw.trim() !== ''
833
- ? filterCategoryRaw.split(',').map((s: string) => s.trim()).filter(Boolean)
834
- : undefined;
835
- const res = await tools.logTools.readOutputLog({
836
- filterCategory,
837
- filterLevel: args.filter_level,
838
- lines: typeof args.lines === 'number' ? args.lines : undefined,
839
- logPath: typeof args.log_path === 'string' ? args.log_path : undefined,
840
- includePrefixes: Array.isArray(args.include_prefixes) ? args.include_prefixes : undefined,
841
- excludeCategories: Array.isArray(args.exclude_categories) ? args.exclude_categories : undefined
842
- });
843
- return cleanObject(res);
844
- }
845
- case 'profile': {
846
- const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
847
- return cleanObject(res);
848
- }
849
- case 'show_fps': {
850
- const res = await tools.performanceTools.showFPS({ enabled: !!args.enabled, verbose: !!args.verbose });
851
- return cleanObject(res);
852
- }
853
- case 'set_quality': {
854
- const res = await tools.performanceTools.setScalability({ category: args.category, level: args.level });
855
- return cleanObject(res);
856
- }
857
- case 'play_sound': {
858
- await elicitMissingPrimitiveArgs(
859
- tools,
860
- args,
861
- 'Provide the audio asset for system_control.play_sound',
862
- {
863
- soundPath: {
864
- type: 'string',
865
- title: 'Sound Asset Path',
866
- description: 'Asset path of the sound to play'
867
- }
868
- }
869
- );
870
- const soundPath = requireNonEmptyString(args.soundPath, 'soundPath', 'Missing required parameter: soundPath');
871
- if (args.location && typeof args.location === 'object') {
872
- const loc = [args.location.x || 0, args.location.y || 0, args.location.z || 0];
873
- const res = await tools.audioTools.playSoundAtLocation({ soundPath, location: loc as [number, number, number], volume: args.volume, pitch: args.pitch, startTime: args.startTime });
874
- return cleanObject(res);
875
- }
876
- const res = await tools.audioTools.playSound2D({ soundPath, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
877
- return cleanObject(res);
878
- }
879
- case 'create_widget': {
880
- await elicitMissingPrimitiveArgs(
881
- tools,
882
- args,
883
- 'Provide details for system_control.create_widget',
884
- {
885
- widgetName: {
886
- type: 'string',
887
- title: 'Widget Name',
888
- description: 'Name for the new UI widget asset'
889
- },
890
- widgetType: {
891
- type: 'string',
892
- title: 'Widget Type',
893
- description: 'Widget type such as HUD, Menu, Overlay, etc.'
894
- }
895
- }
896
- );
897
- const widgetName = requireNonEmptyString(args.widgetName ?? args.name, 'widgetName', 'Missing required parameter: widgetName');
898
- const widgetType = requireNonEmptyString(args.widgetType, 'widgetType', 'Missing required parameter: widgetType');
899
- const res = await tools.uiTools.createWidget({ name: widgetName, type: widgetType as any, savePath: args.savePath });
900
- return cleanObject(res);
901
- }
902
- case 'show_widget': {
903
- const res = await tools.uiTools.setWidgetVisibility({ widgetName: args.widgetName, visible: args.visible !== false });
904
- return cleanObject(res);
905
- }
906
- case 'screenshot': {
907
- const res = await tools.visualTools.takeScreenshot({ resolution: args.resolution });
908
- return cleanObject(res);
909
- }
910
- case 'engine_start': {
911
- const res = await tools.engineTools.launchEditor({ editorExe: args.editorExe, projectPath: args.projectPath });
912
- return cleanObject(res);
913
- }
914
- case 'engine_quit': {
915
- const res = await tools.engineTools.quitEditor();
916
- return cleanObject(res);
917
- }
918
- default:
919
- throw new Error(`Unknown system action: ${args.action}`);
920
- }
257
+ // 18. PERFORMANCE
258
+ case 'manage_performance':
259
+ return await handlePerformanceTools(action, args, tools);
921
260
 
922
- // 10. CONSOLE COMMAND - handle validation here
923
- case 'console_command':
924
- if (!args.command || typeof args.command !== 'string' || args.command.trim() === '') {
925
- return { success: true, message: 'Empty command' } as any;
926
- }
927
- // Basic safety filter
928
- const cmd = String(args.command).trim();
929
- const blocked = [/\bquit\b/i, /\bexit\b/i, /debugcrash/i];
930
- if (blocked.some(r => r.test(cmd))) {
931
- return { success: false, error: 'Command blocked for safety' } as any;
932
- }
933
- try {
934
- const raw = await tools.bridge.executeConsoleCommand(cmd);
935
- const summary = tools.bridge.summarizeConsoleCommand(cmd, raw);
936
- const output = summary.output || '';
937
- const looksInvalid = /unknown|invalid/i.test(output);
938
- return cleanObject({
939
- success: summary.returnValue !== false && !looksInvalid,
940
- command: summary.command,
941
- output: output || undefined,
942
- logLines: summary.logLines?.length ? summary.logLines : undefined,
943
- returnValue: summary.returnValue,
944
- message: !looksInvalid
945
- ? (output || 'Command executed')
946
- : undefined,
947
- error: looksInvalid ? output : undefined,
948
- raw: summary.raw
949
- });
950
- } catch (e: any) {
951
- return cleanObject({ success: false, command: cmd, error: e?.message || String(e) });
952
- }
953
-
261
+ // 19. INPUT
262
+ case 'manage_input':
263
+ return await handleInputTools(action, args, tools);
954
264
 
955
- // 11. REMOTE CONTROL PRESETS - Direct implementation
956
- case 'manage_rc':
957
- // Handle RC operations directly through RcTools
958
- let rcResult: any;
959
- const rcAction = requireAction(args);
960
- switch (rcAction) {
961
- // Support both 'create_preset' and 'create' for compatibility
962
- case 'create_preset':
963
- case 'create':
964
- // Support both 'name' and 'presetName' parameter names
965
- const presetName = args.name || args.presetName;
966
- if (!presetName) throw new Error('Missing required parameter: name or presetName');
967
- rcResult = await tools.rcTools.createPreset({
968
- name: presetName,
969
- path: args.path
970
- });
971
- // Return consistent output with presetId for tests
972
- if (rcResult.success) {
973
- rcResult.message = `Remote Control preset created: ${presetName}`;
974
- // Ensure presetId is set (for test compatibility)
975
- if (rcResult.presetPath && !rcResult.presetId) {
976
- rcResult.presetId = rcResult.presetPath;
977
- }
978
- }
979
- break;
980
-
981
- case 'list':
982
- // List all presets - implement via RcTools
983
- rcResult = await tools.rcTools.listPresets();
984
- break;
985
-
986
- case 'delete':
987
- case 'delete_preset':
988
- const presetIdentifier = args.presetId || args.presetPath;
989
- if (!presetIdentifier) throw new Error('Missing required parameter: presetId');
990
- rcResult = await tools.rcTools.deletePreset(presetIdentifier);
991
- if (rcResult.success) {
992
- rcResult.message = 'Preset deleted successfully';
993
- }
994
- break;
995
-
996
- case 'expose_actor':
997
- if (!args.presetPath) throw new Error('Missing required parameter: presetPath');
998
- if (!args.actorName) throw new Error('Missing required parameter: actorName');
999
-
1000
- rcResult = await tools.rcTools.exposeActor({
1001
- presetPath: args.presetPath,
1002
- actorName: args.actorName
1003
- });
1004
- if (rcResult.success) {
1005
- rcResult.message = `Actor '${args.actorName}' exposed to preset`;
1006
- }
1007
- break;
1008
-
1009
- case 'expose_property':
1010
- case 'expose': // Support simplified name from tests
1011
- // Support both presetPath and presetId
1012
- const presetPathExp = args.presetPath || args.presetId;
1013
- if (!presetPathExp) throw new Error('Missing required parameter: presetPath or presetId');
1014
- if (!args.objectPath) throw new Error('Missing required parameter: objectPath');
1015
- if (!args.propertyName) throw new Error('Missing required parameter: propertyName');
1016
-
1017
- rcResult = await tools.rcTools.exposeProperty({
1018
- presetPath: presetPathExp,
1019
- objectPath: args.objectPath,
1020
- propertyName: args.propertyName
1021
- });
1022
- if (rcResult.success) {
1023
- rcResult.message = `Property '${args.propertyName}' exposed to preset`;
1024
- }
1025
- break;
1026
-
1027
- case 'list_fields':
1028
- case 'get_exposed': // Support test naming
1029
- const presetPathList = args.presetPath || args.presetId;
1030
- if (!presetPathList) throw new Error('Missing required parameter: presetPath or presetId');
1031
-
1032
- rcResult = await tools.rcTools.listFields({
1033
- presetPath: presetPathList
1034
- });
1035
- // Map 'fields' to 'exposedProperties' for test compatibility
1036
- if (rcResult.success && rcResult.fields) {
1037
- rcResult.exposedProperties = rcResult.fields;
1038
- }
1039
- break;
1040
-
1041
- case 'set_property':
1042
- case 'set_value': // Support test naming
1043
- // Support both patterns
1044
- const objPathSet = args.objectPath || args.presetId;
1045
- const propNameSet = args.propertyName || args.propertyLabel;
1046
-
1047
- if (!objPathSet) throw new Error('Missing required parameter: objectPath or presetId');
1048
- if (!propNameSet) throw new Error('Missing required parameter: propertyName or propertyLabel');
1049
- if (args.value === undefined) throw new Error('Missing required parameter: value');
1050
-
1051
- rcResult = await tools.rcTools.setProperty({
1052
- objectPath: objPathSet,
1053
- propertyName: propNameSet,
1054
- value: args.value
1055
- });
1056
- if (rcResult.success) {
1057
- rcResult.message = `Property '${propNameSet}' value updated`;
1058
- }
1059
- break;
1060
-
1061
- case 'get_property':
1062
- case 'get_value': // Support test naming
1063
- const objPathGet = args.objectPath || args.presetId;
1064
- const propNameGet = args.propertyName || args.propertyLabel;
1065
-
1066
- if (!objPathGet) throw new Error('Missing required parameter: objectPath or presetId');
1067
- if (!propNameGet) throw new Error('Missing required parameter: propertyName or propertyLabel');
1068
-
1069
- rcResult = await tools.rcTools.getProperty({
1070
- objectPath: objPathGet,
1071
- propertyName: propNameGet
1072
- });
1073
- break;
1074
-
1075
- case 'call_function':
1076
- if (!args.presetId) throw new Error('Missing required parameter: presetId');
1077
- if (!args.functionLabel) throw new Error('Missing required parameter: functionLabel');
1078
-
1079
- // For now, return not implemented
1080
- rcResult = {
1081
- success: false,
1082
- error: 'Function calls not yet implemented'
1083
- };
1084
- break;
1085
-
1086
- default:
1087
- throw new Error(`Unknown RC action: ${rcAction}. Valid actions are: create_preset, expose_actor, expose_property, list_fields, set_property, get_property, or their simplified versions: create, list, delete, expose, get_exposed, set_value, get_value, call_function`);
1088
- }
1089
-
1090
- // Return result directly - MCP formatting will be handled by response validator
1091
- // Clean to prevent circular references
1092
- return cleanObject(rcResult);
265
+ default:
266
+ return cleanObject({ success: false, error: 'UNKNOWN_TOOL', message: `Unknown consolidated tool: ${name}` });
267
+ }
268
+ }
1093
269
 
1094
- // 12. SEQUENCER / CINEMATICS
1095
- case 'manage_sequence':
1096
- // Direct handling for sequence operations
1097
- const seqResult = await (async () => {
1098
- const sequenceTools = tools.sequenceTools;
1099
- if (!sequenceTools) throw new Error('Sequence tools not available');
1100
- const action = requireAction(args);
1101
-
1102
- switch (action) {
1103
- case 'create':
1104
- return await sequenceTools.create({ name: args.name, path: args.path });
1105
- case 'open':
1106
- return await sequenceTools.open({ path: args.path });
1107
- case 'add_camera':
1108
- return await sequenceTools.addCamera({ spawnable: args.spawnable !== false });
1109
- case 'add_actor':
1110
- return await sequenceTools.addActor({ actorName: args.actorName });
1111
- case 'add_actors':
1112
- if (!args.actorNames) throw new Error('Missing required parameter: actorNames');
1113
- return await sequenceTools.addActors({ actorNames: args.actorNames });
1114
- case 'remove_actors':
1115
- if (!args.actorNames) throw new Error('Missing required parameter: actorNames');
1116
- return await sequenceTools.removeActors({ actorNames: args.actorNames });
1117
- case 'get_bindings':
1118
- return await sequenceTools.getBindings({ path: args.path });
1119
- case 'add_spawnable_from_class':
1120
- if (!args.className) throw new Error('Missing required parameter: className');
1121
- return await sequenceTools.addSpawnableFromClass({ className: args.className, path: args.path });
1122
- case 'play':
1123
- return await sequenceTools.play({ loopMode: args.loopMode });
1124
- case 'pause':
1125
- return await sequenceTools.pause();
1126
- case 'stop':
1127
- return await sequenceTools.stop();
1128
- case 'set_properties':
1129
- return await sequenceTools.setSequenceProperties({
1130
- path: args.path,
1131
- frameRate: args.frameRate,
1132
- lengthInFrames: args.lengthInFrames,
1133
- playbackStart: args.playbackStart,
1134
- playbackEnd: args.playbackEnd
1135
- });
1136
- case 'get_properties':
1137
- return await sequenceTools.getSequenceProperties({ path: args.path });
1138
- case 'set_playback_speed':
1139
- if (args.speed === undefined) throw new Error('Missing required parameter: speed');
1140
- return await sequenceTools.setPlaybackSpeed({ speed: args.speed });
1141
- default:
1142
- throw new Error(`Unknown sequence action: ${action}`);
1143
- }
1144
- })();
1145
-
1146
- // Return result directly - MCP formatting will be handled by response validator
1147
- // Clean to prevent circular references
1148
- return cleanObject(seqResult);
1149
- // 13. INTROSPECTION
1150
- case 'inspect':
1151
- const inspectAction = requireAction(args);
1152
- switch (inspectAction) {
1153
- case 'inspect_object': {
1154
- const res = await tools.introspectionTools.inspectObject({ objectPath: args.objectPath, detailed: args.detailed });
1155
- return cleanObject(res);
1156
- }
1157
- case 'set_property': {
1158
- const res = await tools.introspectionTools.setProperty({ objectPath: args.objectPath, propertyName: args.propertyName, value: args.value });
1159
- return cleanObject(res);
1160
- }
1161
- default:
1162
- throw new Error(`Unknown inspect action: ${inspectAction}`);
1163
- }
1164
-
270
+ // Export the main consolidated tool call handler
271
+ export async function handleConsolidatedToolCall(
272
+ name: string,
273
+ args: any,
274
+ tools: ITools
275
+ ) {
276
+ const logger = new Logger('ConsolidatedToolHandler');
277
+ const startTime = Date.now();
278
+ logger.info(`Starting execution of ${name} at ${new Date().toISOString()}`);
1165
279
 
1166
- default:
1167
- throw new Error(`Unknown consolidated tool: ${name}`);
280
+ try {
281
+ const normalized = normalizeToolCall(name, args);
282
+ const normalizedName = normalized.name;
283
+ const action = normalized.action;
284
+ const normalizedArgs = normalized.args;
285
+
286
+ const toolDef: any = (consolidatedToolDefinitions as any[]).find(t => t.name === normalizedName);
287
+ const dynamicHandler = await getDynamicHandlerForTool(normalizedName, action);
288
+
289
+ if (!dynamicHandler && toolDef && toolDef.inputSchema && toolDef.inputSchema.properties && toolDef.inputSchema.properties.action) {
290
+ const actionProp: any = toolDef.inputSchema.properties.action;
291
+ const allowed: string[] | undefined = Array.isArray(actionProp.enum) ? actionProp.enum as string[] : undefined;
292
+ if (allowed && !allowed.includes(action)) {
293
+ return cleanObject({
294
+ success: false,
295
+ error: 'UNKNOWN_ACTION',
296
+ message: `Unknown action '${action}' for consolidated tool '${normalizedName}'.`
297
+ });
298
+ }
1168
299
  }
300
+ const defaultHandler = async () => invokeNamedTool(normalizedName, action, normalizedArgs, tools);
1169
301
 
1170
- // All cases return (or throw) above; this is a type guard for exhaustiveness.
302
+ if (dynamicHandler) {
303
+ return await dynamicHandler({ name: normalizedName, action, args: normalizedArgs, tools, defaultHandler });
304
+ }
1171
305
 
306
+ return await defaultHandler();
1172
307
  } catch (err: any) {
1173
308
  const duration = Date.now() - startTime;
1174
- console.log(`[ConsolidatedToolHandler] Failed execution of ${name} after ${duration}ms: ${err?.message || String(err)}`);
1175
-
1176
- // Return consistent error structure matching regular tool handlers
309
+ logger.error(`Failed execution of ${name} after ${duration}ms: ${err?.message || String(err)}`);
1177
310
  const errorMessage = err?.message || String(err);
1178
- const isTimeout = errorMessage.includes('timeout');
1179
-
1180
- return {
1181
- content: [{
1182
- type: 'text',
1183
- text: isTimeout
1184
- ? `Tool ${name} timed out. Please check Unreal Engine connection.`
1185
- : `Failed to execute ${name}: ${errorMessage}`
1186
- }],
1187
- isError: true
1188
- };
311
+ const lowerError = errorMessage.toString().toLowerCase();
312
+ const isTimeout = lowerError.includes('timeout');
313
+
314
+ let text: string;
315
+ if (isTimeout) {
316
+ text = `Tool ${name} timed out. Please check Unreal Engine connection.`;
317
+ } else {
318
+ text = `Failed to execute ${name}: ${errorMessage}`;
319
+ }
320
+
321
+ return ResponseFactory.error(text);
1189
322
  }
1190
- }
323
+ }