unreal-engine-mcp-server 0.4.7 → 0.5.1

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