unreal-engine-mcp-server 0.4.6 → 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 +269 -22
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -72
  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 -604
  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 +5475 -1627
  97. package/dist/tools/consolidated-tool-definitions.js +829 -482
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1009
  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 +45 -0
  161. package/dist/tools/logs.js +210 -0
  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 +195 -11
  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 -649
  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 -500
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1122
  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 +219 -0
  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 +250 -13
  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 -572
  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,17 +1,33 @@
1
1
  import { Logger } from '../utils/logger.js';
2
- import { bestEffortInterpretedText, interpretStandardResult } from '../utils/result-helpers.js';
2
+ import { lookupPropertyMetadata, normalizeDictionaryKey } from './property-dictionary.js';
3
3
  export class IntrospectionTools {
4
4
  bridge;
5
+ automationBridge;
5
6
  log = new Logger('IntrospectionTools');
6
7
  objectCache = new Map();
7
8
  retryAttempts = 3;
8
9
  retryDelay = 1000;
9
- constructor(bridge) {
10
+ propertyFilterPatterns = [
11
+ /internal/i,
12
+ /transient/i,
13
+ /^temp/i,
14
+ /^bhidden/i,
15
+ /renderstate/i,
16
+ /previewonly/i,
17
+ /deprecated/i
18
+ ];
19
+ ignoredPropertyKeys = new Set([
20
+ 'assetimportdata',
21
+ 'blueprintcreatedcomponents',
22
+ 'componentreplicator',
23
+ 'componentoverrides',
24
+ 'actorcomponenttags'
25
+ ]);
26
+ constructor(bridge, automationBridge) {
10
27
  this.bridge = bridge;
28
+ this.automationBridge = automationBridge;
11
29
  }
12
- /**
13
- * Execute with retry logic for transient failures
14
- */
30
+ setAutomationBridge(automationBridge) { this.automationBridge = automationBridge; }
15
31
  async executeWithRetry(operation, operationName) {
16
32
  let lastError;
17
33
  for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
@@ -28,40 +44,7 @@ export class IntrospectionTools {
28
44
  }
29
45
  throw lastError;
30
46
  }
31
- /**
32
- * Parse Python execution result with better error handling
33
- */
34
- parsePythonResult(resp, operationName) {
35
- const interpreted = interpretStandardResult(resp, {
36
- successMessage: `${operationName} succeeded`,
37
- failureMessage: `${operationName} failed`
38
- });
39
- if (interpreted.success) {
40
- return {
41
- ...interpreted.payload,
42
- success: true
43
- };
44
- }
45
- const output = bestEffortInterpretedText(interpreted) ?? '';
46
- if (output) {
47
- this.log.error(`Failed to parse ${operationName} result: ${output}`);
48
- }
49
- if (output.includes('ModuleNotFoundError')) {
50
- return { success: false, error: 'Reflection module not available.' };
51
- }
52
- if (output.includes('AttributeError')) {
53
- return { success: false, error: 'Reflection API method not found. Check Unreal Engine version compatibility.' };
54
- }
55
- return {
56
- success: false,
57
- error: `${interpreted.error ?? `${operationName} did not return a valid result`}: ${output.substring(0, 200)}`
58
- };
59
- }
60
- /**
61
- * Convert Unreal property value to JavaScript-friendly format
62
- */
63
47
  convertPropertyValue(value, typeName) {
64
- // Handle vectors, rotators, transforms
65
48
  if (typeName.includes('Vector')) {
66
49
  if (typeof value === 'object' && value !== null) {
67
50
  return { x: value.X || 0, y: value.Y || 0, z: value.Z || 0 };
@@ -83,258 +66,246 @@ export class IntrospectionTools {
83
66
  }
84
67
  return value;
85
68
  }
69
+ isPlainObject(value) {
70
+ return !!value && typeof value === 'object' && !Array.isArray(value);
71
+ }
72
+ isLikelyPropertyDescriptor(value) {
73
+ if (!this.isPlainObject(value))
74
+ return false;
75
+ if (typeof value.name === 'string' && ('value' in value || 'type' in value))
76
+ return true;
77
+ if ('propertyName' in value && ('currentValue' in value || 'defaultValue' in value))
78
+ return true;
79
+ return false;
80
+ }
81
+ shouldFilterProperty(name, value, flags, detailed = false) {
82
+ if (detailed)
83
+ return false;
84
+ if (!name)
85
+ return true;
86
+ const normalized = normalizeDictionaryKey(name);
87
+ if (this.ignoredPropertyKeys.has(normalized))
88
+ return true;
89
+ for (const pattern of this.propertyFilterPatterns) {
90
+ if (pattern.test(name))
91
+ return true;
92
+ }
93
+ if (Array.isArray(value) && value.length === 0)
94
+ return true;
95
+ if (this.isPlainObject(value) && Object.keys(value).length === 0)
96
+ return true;
97
+ if (flags?.some((f) => /deprecated/i.test(f)))
98
+ return true;
99
+ return false;
100
+ }
101
+ formatDisplayValue(value, type) {
102
+ if (value === null || value === undefined)
103
+ return 'None';
104
+ if (typeof value === 'string')
105
+ return value.length > 120 ? `${value.slice(0, 117)}...` : value;
106
+ if (typeof value === 'number' || typeof value === 'boolean')
107
+ return `${value}`;
108
+ if (Array.isArray(value)) {
109
+ const preview = value.slice(0, 5).map((entry) => this.formatDisplayValue(entry, typeof entry));
110
+ const suffix = value.length > 5 ? `, … (+${value.length - 5})` : '';
111
+ return `[${preview.join(', ')}${suffix}]`;
112
+ }
113
+ if (this.isPlainObject(value)) {
114
+ const keys = Object.keys(value);
115
+ if (['vector', 'rotator', 'transform'].some((token) => type.toLowerCase().includes(token))) {
116
+ const printable = Object.entries(value)
117
+ .map(([k, v]) => `${k}: ${this.formatDisplayValue(v, typeof v)}`)
118
+ .join(', ');
119
+ return `{ ${printable} }`;
120
+ }
121
+ const preview = keys.slice(0, 5).map((k) => `${k}: ${this.formatDisplayValue(value[k], typeof value[k])}`);
122
+ const suffix = keys.length > 5 ? `, … (+${keys.length - 5} keys)` : '';
123
+ return `{ ${preview.join(', ')}${suffix} }`;
124
+ }
125
+ return String(value);
126
+ }
127
+ normalizePropertyEntry(entry, detailed = false) {
128
+ if (!entry)
129
+ return null;
130
+ const name = entry.name ?? entry.propertyName ?? entry.key ?? '';
131
+ if (!name)
132
+ return null;
133
+ const candidateType = entry.type ?? entry.propertyType ?? (entry.value !== undefined ? typeof entry.value : undefined);
134
+ const type = typeof candidateType === 'string' && candidateType.length > 0 ? candidateType : 'unknown';
135
+ const rawValue = entry.value ?? entry.currentValue ?? entry.defaultValue ?? entry.data ?? entry;
136
+ const value = this.convertPropertyValue(rawValue, type);
137
+ const flags = entry.flags ?? entry.attributes;
138
+ const metadata = entry.metadata ?? entry.annotations;
139
+ const filtered = this.shouldFilterProperty(name, value, flags, detailed);
140
+ const dictionaryEntry = lookupPropertyMetadata(name);
141
+ const propertyInfo = {
142
+ name,
143
+ type,
144
+ value,
145
+ flags,
146
+ metadata,
147
+ category: dictionaryEntry?.category ?? entry.category,
148
+ tooltip: entry.tooltip ?? entry.helpText,
149
+ description: dictionaryEntry?.description ?? entry.description,
150
+ displayValue: this.formatDisplayValue(value, type),
151
+ dictionaryEntry,
152
+ isReadOnly: Boolean(entry.isReadOnly || entry.readOnly || flags?.some((f) => f.toLowerCase().includes('readonly')))
153
+ };
154
+ propertyInfo.__filtered = filtered;
155
+ return propertyInfo;
156
+ }
157
+ flattenPropertyMap(source, prefix = '', detailed = false) {
158
+ const properties = [];
159
+ for (const [rawKey, rawValue] of Object.entries(source)) {
160
+ const name = prefix ? `${prefix}.${rawKey}` : rawKey;
161
+ if (this.isLikelyPropertyDescriptor(rawValue)) {
162
+ const normalized = this.normalizePropertyEntry({ ...rawValue, name }, detailed);
163
+ if (normalized)
164
+ properties.push(normalized);
165
+ continue;
166
+ }
167
+ if (this.isPlainObject(rawValue)) {
168
+ const nestedKeys = Object.keys(rawValue);
169
+ const hasPrimitiveChildren = nestedKeys.some((key) => {
170
+ const child = rawValue[key];
171
+ return child === null || typeof child !== 'object' || Array.isArray(child) || this.isLikelyPropertyDescriptor(child);
172
+ });
173
+ if (hasPrimitiveChildren) {
174
+ const normalized = this.normalizePropertyEntry({ name, value: rawValue }, detailed);
175
+ if (normalized)
176
+ properties.push(normalized);
177
+ }
178
+ else {
179
+ properties.push(...this.flattenPropertyMap(rawValue, name, detailed));
180
+ }
181
+ continue;
182
+ }
183
+ const normalized = this.normalizePropertyEntry({ name, value: rawValue }, detailed);
184
+ if (normalized)
185
+ properties.push(normalized);
186
+ }
187
+ return properties;
188
+ }
189
+ extractRawProperties(rawInfo, detailed = false) {
190
+ if (!rawInfo)
191
+ return [];
192
+ if (Array.isArray(rawInfo.properties)) {
193
+ const entries = rawInfo.properties;
194
+ return entries
195
+ .map((entry) => this.normalizePropertyEntry(entry, detailed))
196
+ .filter((entry) => Boolean(entry));
197
+ }
198
+ if (this.isPlainObject(rawInfo.properties)) {
199
+ return this.flattenPropertyMap(rawInfo.properties, '', detailed);
200
+ }
201
+ if (Array.isArray(rawInfo)) {
202
+ const entries = rawInfo;
203
+ return entries
204
+ .map((entry) => this.normalizePropertyEntry(entry, detailed))
205
+ .filter((entry) => Boolean(entry));
206
+ }
207
+ if (this.isPlainObject(rawInfo)) {
208
+ const shallow = { ...rawInfo };
209
+ delete shallow.properties;
210
+ delete shallow.functions;
211
+ delete shallow.summary;
212
+ return this.flattenPropertyMap(shallow, '', detailed);
213
+ }
214
+ return [];
215
+ }
216
+ curateObjectInfo(rawInfo, objectPath, detailed = false) {
217
+ const properties = this.extractRawProperties(rawInfo, detailed);
218
+ const filteredProperties = [];
219
+ const curatedProperties = properties.filter((prop) => {
220
+ const shouldFilter = prop.__filtered;
221
+ delete prop.__filtered;
222
+ if (shouldFilter) {
223
+ filteredProperties.push(prop.name);
224
+ }
225
+ return !shouldFilter;
226
+ });
227
+ const categories = {};
228
+ const finalList = (detailed ? properties : curatedProperties).map((prop) => {
229
+ const category = (prop.category ?? prop.dictionaryEntry?.category ?? 'General');
230
+ categories[category] = (categories[category] ?? 0) + 1;
231
+ return prop;
232
+ });
233
+ const summary = {
234
+ name: rawInfo?.name ?? rawInfo?.objectName ?? rawInfo?.displayName,
235
+ class: rawInfo?.class ?? rawInfo?.className ?? rawInfo?.type ?? rawInfo?.objectClass,
236
+ path: rawInfo?.path ?? rawInfo?.objectPath ?? objectPath,
237
+ parent: rawInfo?.outer ?? rawInfo?.parent,
238
+ tags: Array.isArray(rawInfo?.tags) ? rawInfo.tags : undefined,
239
+ propertyCount: properties.length,
240
+ curatedPropertyCount: curatedProperties.length,
241
+ filteredPropertyCount: filteredProperties.length,
242
+ categories
243
+ };
244
+ const info = {
245
+ class: summary.class,
246
+ name: summary.name,
247
+ path: summary.path,
248
+ parent: summary.parent,
249
+ tags: summary.tags,
250
+ properties: finalList,
251
+ functions: Array.isArray(rawInfo?.functions) ? rawInfo.functions : undefined,
252
+ interfaces: Array.isArray(rawInfo?.interfaces) ? rawInfo.interfaces : undefined,
253
+ flags: Array.isArray(rawInfo?.flags) ? rawInfo.flags : undefined,
254
+ summary,
255
+ filteredProperties: filteredProperties.length ? filteredProperties : undefined,
256
+ original: rawInfo
257
+ };
258
+ return info;
259
+ }
86
260
  async inspectObject(params) {
87
- // Check cache first if not requesting detailed info
88
261
  if (!params.detailed && this.objectCache.has(params.objectPath)) {
89
262
  const cached = this.objectCache.get(params.objectPath);
90
263
  if (cached) {
91
264
  return { success: true, info: cached };
92
265
  }
93
266
  }
94
- const py = `
95
- import unreal, json, inspect
96
- path = r"${params.objectPath}"
97
- detailed = ${params.detailed ? 'True' : 'False'}
98
-
99
- def get_property_info(prop, obj=None):
100
- """Extract detailed property information"""
101
- try:
102
- info = {
103
- 'name': prop.get_name(),
104
- 'type': prop.get_property_class_name() if hasattr(prop, 'get_property_class_name') else 'Unknown'
105
- }
106
-
107
- # Try to get property flags
108
- flags = []
109
- if hasattr(prop, 'has_any_property_flags'):
110
- if prop.has_any_property_flags(unreal.PropertyFlags.CPF_EDIT_CONST):
111
- flags.append('ReadOnly')
112
- if prop.has_any_property_flags(unreal.PropertyFlags.CPF_BLUEPRINT_READ_ONLY):
113
- flags.append('BlueprintReadOnly')
114
- if prop.has_any_property_flags(unreal.PropertyFlags.CPF_TRANSIENT):
115
- flags.append('Transient')
116
- info['flags'] = flags
117
-
118
- # Try to get metadata
119
- if hasattr(prop, 'get_metadata'):
120
- try:
121
- info['category'] = prop.get_metadata('Category')
122
- info['tooltip'] = prop.get_metadata('ToolTip')
123
- except Exception:
124
- pass
125
-
126
- # Try to get current value if object provided
127
- if obj and detailed:
128
- try:
129
- value = getattr(obj, prop.get_name())
130
- # Convert complex types to serializable format
131
- if hasattr(value, '__dict__'):
132
- value = str(value)
133
- info['value'] = value
134
- except Exception:
135
- pass
136
-
137
- return info
138
- except Exception as e:
139
- return {'name': str(prop) if prop else 'Unknown', 'type': 'Unknown', 'error': str(e)}
140
-
141
- try:
142
- obj = unreal.load_object(None, path)
143
- if not obj:
144
- # Try as class if object load fails
145
- try:
146
- obj = unreal.load_class(None, path)
147
- if not obj:
148
- print('RESULT:' + json.dumps({'success': False, 'error': 'Object or class not found'}))
149
- raise SystemExit(0)
150
- except Exception:
151
- print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
152
- raise SystemExit(0)
153
-
154
- info = {
155
- 'class': obj.get_class().get_name() if hasattr(obj, 'get_class') else str(type(obj)),
156
- 'name': obj.get_name() if hasattr(obj, 'get_name') else '',
157
- 'path': path,
158
- 'properties': [],
159
- 'functions': [],
160
- 'flags': []
161
- }
162
-
163
- # Get parent class
164
- try:
165
- if hasattr(obj, 'get_class'):
166
- cls = obj.get_class()
167
- if hasattr(cls, 'get_super_class'):
168
- super_cls = cls.get_super_class()
169
- if super_cls:
170
- info['parent'] = super_cls.get_name()
171
- except Exception:
172
- pass
173
-
174
- # Get object flags
175
- try:
176
- if hasattr(obj, 'has_any_flags'):
177
- flags = []
178
- if obj.has_any_flags(unreal.ObjectFlags.RF_PUBLIC):
179
- flags.append('Public')
180
- if obj.has_any_flags(unreal.ObjectFlags.RF_TRANSIENT):
181
- flags.append('Transient')
182
- if obj.has_any_flags(unreal.ObjectFlags.RF_DEFAULT_SUB_OBJECT):
183
- flags.append('DefaultSubObject')
184
- info['flags'] = flags
185
- except Exception:
186
- pass
187
-
188
- # Get properties - AVOID deprecated properties completely
189
- props = []
190
-
191
- # List of deprecated properties to completely skip
192
- deprecated_props = [
193
- 'life_span', 'on_actor_touch', 'on_actor_un_touch',
194
- 'get_touching_actors', 'get_touching_components',
195
- 'controller_class', 'look_up_scale', 'sound_wave_param',
196
- 'material_substitute', 'texture_object'
197
- ]
198
-
199
- try:
200
- cls = obj.get_class() if hasattr(obj, 'get_class') else obj
201
-
202
- # Try UE5 reflection API with safe property access
203
- if hasattr(cls, 'get_properties'):
204
- for prop in cls.get_properties():
205
- prop_name = None
206
- try:
207
- # Safe property name extraction
208
- if hasattr(prop, 'get_name'):
209
- prop_name = prop.get_name()
210
- elif hasattr(prop, 'name'):
211
- prop_name = prop.name
212
-
213
- # Skip if deprecated
214
- if prop_name and prop_name not in deprecated_props:
215
- prop_info = get_property_info(prop, obj if detailed else None)
216
- if prop_info.get('name') not in deprecated_props:
217
- props.append(prop_info)
218
- except Exception:
219
- pass
220
-
221
- # If reflection API didn't work, use a safe property list
222
- if not props:
223
- # Only access known safe properties
224
- safe_properties = [
225
- 'actor_guid', 'actor_instance_guid', 'always_relevant',
226
- 'auto_destroy_when_finished', 'can_be_damaged',
227
- 'content_bundle_guid', 'custom_time_dilation',
228
- 'enable_auto_lod_generation', 'find_camera_component_when_view_target',
229
- 'generate_overlap_events_during_level_streaming', 'hidden',
230
- 'initial_life_span', # Use new name instead of life_span
231
- 'instigator', 'is_spatially_loaded', 'min_net_update_frequency',
232
- 'net_cull_distance_squared', 'net_dormancy', 'net_priority',
233
- 'net_update_frequency', 'net_use_owner_relevancy',
234
- 'only_relevant_to_owner', 'pivot_offset',
235
- 'replicate_using_registered_sub_object_list', 'replicates',
236
- 'root_component', 'runtime_grid', 'spawn_collision_handling_method',
237
- 'sprite_scale', 'tags', 'location', 'rotation', 'scale'
238
- ]
239
-
240
- for prop_name in safe_properties:
241
- try:
242
- # Use get_editor_property for safer access
243
- if hasattr(obj, 'get_editor_property'):
244
- val = obj.get_editor_property(prop_name)
245
- props.append({
246
- 'name': prop_name,
247
- 'type': type(val).__name__ if val is not None else 'None',
248
- 'value': str(val)[:100] if detailed and val is not None else None
249
- })
250
- elif hasattr(obj, prop_name):
251
- # Direct access only for safe properties
252
- val = getattr(obj, prop_name)
253
- if not callable(val):
254
- props.append({
255
- 'name': prop_name,
256
- 'type': type(val).__name__,
257
- 'value': str(val)[:100] if detailed else None
258
- })
259
- except Exception:
260
- pass
261
- except Exception as e:
262
- # Minimal fallback with only essential safe properties
263
- pass
264
-
265
- info['properties'] = props
266
-
267
- # Get functions/methods if detailed
268
- if detailed:
269
- funcs = []
270
- try:
271
- cls = obj.get_class() if hasattr(obj, 'get_class') else obj
272
-
273
- # Try to get UFunctions
274
- if hasattr(cls, 'get_functions'):
275
- for func in cls.get_functions():
276
- func_info = {
277
- 'name': func.get_name(),
278
- 'parameters': [],
279
- 'flags': []
280
- }
281
- # Get parameters if possible
282
- if hasattr(func, 'get_params'):
283
- for param in func.get_params():
284
- func_info['parameters'].append({
285
- 'name': param.get_name() if hasattr(param, 'get_name') else str(param),
286
- 'type': 'Unknown'
287
- })
288
- funcs.append(func_info)
289
- else:
290
- # Fallback: use known safe function names
291
- safe_functions = [
292
- 'get_actor_location', 'set_actor_location',
293
- 'get_actor_rotation', 'set_actor_rotation',
294
- 'get_actor_scale', 'set_actor_scale',
295
- 'destroy_actor', 'destroy_component',
296
- 'get_components', 'get_component_by_class',
297
- 'add_actor_component', 'add_component',
298
- 'get_world', 'get_name', 'get_path_name',
299
- 'is_valid', 'is_a', 'has_authority'
300
- ]
301
- for func_name in safe_functions:
302
- if hasattr(obj, func_name):
303
- try:
304
- attr_value = getattr(obj, func_name)
305
- if callable(attr_value):
306
- funcs.append({
307
- 'name': func_name,
308
- 'parameters': [],
309
- 'flags': []
310
- })
311
- except Exception:
312
- pass
313
- except Exception:
314
- pass
315
-
316
- info['functions'] = funcs
317
-
318
- print('RESULT:' + json.dumps({'success': True, 'info': info}))
319
- except Exception as e:
320
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
321
- `.trim();
322
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'inspectObject');
323
- const result = this.parsePythonResult(resp, 'inspectObject');
324
- // Cache the result if successful and not detailed
325
- if (result.success && result.info && !params.detailed) {
326
- this.objectCache.set(params.objectPath, result.info);
267
+ if (!this.automationBridge) {
268
+ throw new Error('Automation Bridge not available. Introspection operations require plugin support.');
327
269
  }
328
- return result;
270
+ const automationBridge = this.automationBridge;
271
+ return this.executeWithRetry(async () => {
272
+ try {
273
+ const response = await automationBridge.sendAutomationRequest('inspect_object', {
274
+ objectPath: params.objectPath,
275
+ detailed: params.detailed ?? false
276
+ }, {
277
+ timeoutMs: 60000
278
+ });
279
+ if (response.success === false) {
280
+ return {
281
+ success: false,
282
+ error: response.error || response.message || 'Failed to inspect object'
283
+ };
284
+ }
285
+ const rawInfo = response.info ?? response.result ?? response.data ?? response;
286
+ const curatedInfo = this.curateObjectInfo(rawInfo, params.objectPath, params.detailed ?? false);
287
+ const result = {
288
+ success: true,
289
+ info: curatedInfo
290
+ };
291
+ if (result.success && result.info && !params.detailed) {
292
+ this.objectCache.set(params.objectPath, result.info);
293
+ }
294
+ return result;
295
+ }
296
+ catch (error) {
297
+ return {
298
+ success: false,
299
+ error: `Failed to inspect object: ${error instanceof Error ? error.message : String(error)}`
300
+ };
301
+ }
302
+ }, 'inspectObject');
329
303
  }
330
304
  async setProperty(params) {
331
305
  return this.executeWithRetry(async () => {
332
306
  try {
333
- // Validate and convert value type if needed
334
307
  let processedValue = params.value;
335
- // Handle special Unreal types
336
308
  if (typeof params.value === 'object' && params.value !== null) {
337
- // Vector conversion
338
309
  if ('x' in params.value || 'X' in params.value) {
339
310
  processedValue = {
340
311
  X: params.value.x || params.value.X || 0,
@@ -342,7 +313,6 @@ except Exception as e:
342
313
  Z: params.value.z || params.value.Z || 0
343
314
  };
344
315
  }
345
- // Rotator conversion
346
316
  else if ('pitch' in params.value || 'Pitch' in params.value) {
347
317
  processedValue = {
348
318
  Pitch: params.value.pitch || params.value.Pitch || 0,
@@ -350,7 +320,6 @@ except Exception as e:
350
320
  Roll: params.value.roll || params.value.Roll || 0
351
321
  };
352
322
  }
353
- // Transform conversion
354
323
  else if ('location' in params.value || 'Location' in params.value) {
355
324
  processedValue = {
356
325
  Translation: this.convertPropertyValue(params.value.location || params.value.Location, 'Vector'),
@@ -359,14 +328,15 @@ except Exception as e:
359
328
  };
360
329
  }
361
330
  }
362
- const res = await this.bridge.httpCall('/remote/object/property', 'PUT', {
331
+ const res = await this.bridge.setObjectProperty({
363
332
  objectPath: params.objectPath,
364
333
  propertyName: params.propertyName,
365
- propertyValue: processedValue
334
+ value: processedValue
366
335
  });
367
- // Clear cache for this object
368
- this.objectCache.delete(params.objectPath);
369
- return { success: true, result: res };
336
+ if (res.success) {
337
+ this.objectCache.delete(params.objectPath);
338
+ }
339
+ return res;
370
340
  }
371
341
  catch (err) {
372
342
  const errorMsg = err?.message || String(err);
@@ -380,300 +350,187 @@ except Exception as e:
380
350
  }
381
351
  }, 'setProperty');
382
352
  }
383
- /**
384
- * Get property value of an object
385
- */
386
353
  async getProperty(params) {
387
- const py = `
388
- import unreal, json
389
- path = r"${params.objectPath}"
390
- prop_name = r"${params.propertyName}"
391
- try:
392
- obj = unreal.load_object(None, path)
393
- if not obj:
394
- print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
395
- else:
396
- # Try different methods to get property
397
- value = None
398
- found = False
399
-
400
- # Method 1: Direct attribute access
401
- if hasattr(obj, prop_name):
402
- try:
403
- value = getattr(obj, prop_name)
404
- found = True
405
- except Exception:
406
- pass
407
-
408
- # Method 2: get_editor_property (UE4/5)
409
- if not found and hasattr(obj, 'get_editor_property'):
410
- try:
411
- value = obj.get_editor_property(prop_name)
412
- found = True
413
- except Exception:
414
- pass
415
-
416
- # Method 3: Try with common property name variations
417
- if not found:
418
- # Try common property name variations
419
- variations = [
420
- prop_name,
421
- prop_name.lower(),
422
- prop_name.upper(),
423
- prop_name.capitalize(),
424
- # Convert snake_case to CamelCase
425
- ''.join(word.capitalize() for word in prop_name.split('_')),
426
- # Convert CamelCase to snake_case
427
- ''.join(['_' + c.lower() if c.isupper() else c for c in prop_name]).lstrip('_')
428
- ]
429
- for variant in variations:
430
- if hasattr(obj, variant):
431
- try:
432
- value = getattr(obj, variant)
433
- found = True
434
- break
435
- except Exception:
436
- pass
437
-
438
- if found:
439
- # Convert complex types to string
440
- if hasattr(value, '__dict__'):
441
- value = str(value)
442
- elif isinstance(value, (list, tuple, dict)):
443
- value = json.dumps(value)
444
-
445
- print('RESULT:' + json.dumps({'success': True, 'value': value}))
446
- else:
447
- print('RESULT:' + json.dumps({'success': False, 'error': f'Property {prop_name} not found'}))
448
- except Exception as e:
449
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
450
- `.trim();
451
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getProperty');
452
- return this.parsePythonResult(resp, 'getProperty');
354
+ return this.executeWithRetry(async () => {
355
+ const result = await this.bridge.getObjectProperty({
356
+ objectPath: params.objectPath,
357
+ propertyName: params.propertyName
358
+ });
359
+ return result;
360
+ }, 'getProperty');
453
361
  }
454
- /**
455
- * Call a function on an object
456
- */
457
362
  async callFunction(params) {
458
- const py = `
459
- import unreal, json
460
- path = r"${params.objectPath}"
461
- func_name = r"${params.functionName}"
462
- params = ${JSON.stringify(params.parameters || [])}
463
- try:
464
- obj = unreal.load_object(None, path)
465
- if not obj:
466
- # Try loading as class if object fails
467
- try:
468
- obj = unreal.load_class(None, path)
469
- except:
470
- pass
471
-
472
- if not obj:
473
- print('RESULT:' + json.dumps({'success': False, 'error': 'Object not found'}))
474
- else:
475
- # For KismetMathLibrary or similar utility classes, use static method call
476
- if 'KismetMathLibrary' in path or 'MathLibrary' in path or 'GameplayStatics' in path:
477
- try:
478
- # Use Unreal's MathLibrary (KismetMathLibrary is exposed as MathLibrary in Python)
479
- if func_name.lower() == 'abs':
480
- # Use Unreal's MathLibrary.abs function
481
- result = unreal.MathLibrary.abs(float(params[0])) if params else 0
482
- print('RESULT:' + json.dumps({'success': True, 'result': result}))
483
- elif func_name.lower() == 'sqrt':
484
- # Use Unreal's MathLibrary.sqrt function
485
- result = unreal.MathLibrary.sqrt(float(params[0])) if params else 0
486
- print('RESULT:' + json.dumps({'success': True, 'result': result}))
487
- else:
488
- # Try to call as static method
489
- if hasattr(obj, func_name):
490
- func = getattr(obj, func_name)
491
- if callable(func):
492
- result = func(*params) if params else func()
493
- if hasattr(result, '__dict__'):
494
- result = str(result)
495
- print('RESULT:' + json.dumps({'success': True, 'result': result}))
496
- else:
497
- print('RESULT:' + json.dumps({'success': False, 'error': f'{func_name} is not callable'}))
498
- else:
499
- # Try snake_case version
500
- snake_case_name = ''.join(['_' + c.lower() if c.isupper() else c for c in func_name]).lstrip('_')
501
- if hasattr(obj, snake_case_name):
502
- func = getattr(obj, snake_case_name)
503
- result = func(*params) if params else func()
504
- print('RESULT:' + json.dumps({'success': True, 'result': result}))
505
- else:
506
- print('RESULT:' + json.dumps({'success': False, 'error': f'Function {func_name} not found'}))
507
- except Exception as e:
508
- print('RESULT:' + json.dumps({'success': False, 'error': f'Function call failed: {str(e)}'}))
509
- else:
510
- # Regular object method call
511
- if hasattr(obj, func_name):
512
- func = getattr(obj, func_name)
513
- if callable(func):
514
- try:
515
- result = func(*params) if params else func()
516
- # Convert result to serializable format
517
- if hasattr(result, '__dict__'):
518
- result = str(result)
519
- print('RESULT:' + json.dumps({'success': True, 'result': result}))
520
- except Exception as e:
521
- print('RESULT:' + json.dumps({'success': False, 'error': f'Function call failed: {str(e)}'}))
522
- else:
523
- print('RESULT:' + json.dumps({'success': False, 'error': f'{func_name} is not callable'}))
524
- else:
525
- print('RESULT:' + json.dumps({'success': False, 'error': f'Function {func_name} not found'}))
526
- except Exception as e:
527
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
528
- `.trim();
529
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'callFunction');
530
- return this.parsePythonResult(resp, 'callFunction');
363
+ if (!this.automationBridge) {
364
+ throw new Error('Automation Bridge not available. Function call operations require plugin support.');
365
+ }
366
+ const automationBridge = this.automationBridge;
367
+ return this.executeWithRetry(async () => {
368
+ try {
369
+ const response = await automationBridge.sendAutomationRequest('call_object_function', {
370
+ objectPath: params.objectPath,
371
+ functionName: params.functionName,
372
+ parameters: params.parameters || []
373
+ }, {
374
+ timeoutMs: 60000
375
+ });
376
+ if (response.success === false) {
377
+ return {
378
+ success: false,
379
+ error: response.error || response.message || 'Failed to call function'
380
+ };
381
+ }
382
+ return {
383
+ success: true,
384
+ result: response.result
385
+ };
386
+ }
387
+ catch (error) {
388
+ return {
389
+ success: false,
390
+ error: `Failed to call function: ${error instanceof Error ? error.message : String(error)}`
391
+ };
392
+ }
393
+ }, 'callFunction');
531
394
  }
532
- /**
533
- * Get Class Default Object (CDO) for a class
534
- */
535
395
  async getCDO(className) {
536
- const py = `
537
- import unreal, json
538
- class_name = r"${className}"
539
- try:
540
- # Try to find the class
541
- cls = None
542
-
543
- # Method 1: Direct class load
544
- try:
545
- cls = unreal.load_class(None, class_name)
546
- except Exception:
547
- pass
548
-
549
- # Method 2: Find class by name
550
- if not cls:
551
- try:
552
- cls = unreal.find_class(class_name)
553
- except Exception:
554
- pass
555
-
556
- # Method 3: Search in loaded classes
557
- if not cls:
558
- for obj in unreal.ObjectLibrary.get_all_objects():
559
- if hasattr(obj, 'get_class'):
560
- obj_cls = obj.get_class()
561
- if obj_cls.get_name() == class_name:
562
- cls = obj_cls
563
- break
564
-
565
- if not cls:
566
- print('RESULT:' + json.dumps({'success': False, 'error': 'Class not found'}))
567
- else:
568
- # Get CDO
569
- cdo = cls.get_default_object() if hasattr(cls, 'get_default_object') else None
570
-
571
- if cdo:
572
- info = {
573
- 'className': cls.get_name(),
574
- 'cdoPath': cdo.get_path_name() if hasattr(cdo, 'get_path_name') else '',
575
- 'properties': []
396
+ if (!this.automationBridge) {
397
+ throw new Error('Automation Bridge not available. CDO operations require plugin support.');
398
+ }
399
+ const automationBridge = this.automationBridge;
400
+ return this.executeWithRetry(async () => {
401
+ try {
402
+ const response = await automationBridge.sendAutomationRequest('inspect', {
403
+ action: 'inspect_class',
404
+ classPath: className
405
+ }, {
406
+ timeoutMs: 60000
407
+ });
408
+ if (response?.success === false) {
409
+ return {
410
+ success: false,
411
+ error: response.error || response.message || 'Failed to get CDO'
412
+ };
413
+ }
414
+ return {
415
+ success: true,
416
+ cdo: response?.data ?? response?.result ?? response
417
+ };
576
418
  }
577
-
578
- # Get default property values using safe property list
579
- safe_cdo_properties = [
580
- 'initial_life_span', 'hidden', 'can_be_damaged', 'replicates',
581
- 'always_relevant', 'net_dormancy', 'net_priority',
582
- 'net_update_frequency', 'replicate_movement',
583
- 'actor_guid', 'tags', 'root_component',
584
- 'auto_destroy_when_finished', 'enable_auto_lod_generation'
585
- ]
586
- for prop_name in safe_cdo_properties:
587
- try:
588
- if hasattr(cdo, 'get_editor_property'):
589
- value = cdo.get_editor_property(prop_name)
590
- info['properties'].append({
591
- 'name': prop_name,
592
- 'defaultValue': str(value)[:100]
593
- })
594
- elif hasattr(cdo, prop_name):
595
- value = getattr(cdo, prop_name)
596
- if not callable(value):
597
- info['properties'].append({
598
- 'name': prop_name,
599
- 'defaultValue': str(value)[:100]
600
- })
601
- except Exception:
602
- pass
603
-
604
- print('RESULT:' + json.dumps({'success': True, 'cdo': info}))
605
- else:
606
- print('RESULT:' + json.dumps({'success': False, 'error': 'Could not get CDO'}))
607
- except Exception as e:
608
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
609
- `.trim();
610
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getCDO');
611
- return this.parsePythonResult(resp, 'getCDO');
419
+ catch (error) {
420
+ return {
421
+ success: false,
422
+ error: `Failed to get CDO: ${error instanceof Error ? error.message : String(error)}`
423
+ };
424
+ }
425
+ }, 'getCDO');
612
426
  }
613
- /**
614
- * Search for objects by class
615
- */
616
427
  async findObjectsByClass(className, limit = 100) {
617
- const py = `
618
- import unreal, json
619
- class_name = r"${className}"
620
- limit = ${limit}
621
- try:
622
- objects = []
623
- count = 0
624
-
625
- # Use EditorAssetLibrary to find assets
626
- try:
627
- all_assets = unreal.EditorAssetLibrary.list_assets("/Game", recursive=True)
628
- for asset_path in all_assets:
629
- if count >= limit:
630
- break
631
- try:
632
- asset = unreal.EditorAssetLibrary.load_asset(asset_path)
633
- if asset:
634
- asset_class = asset.get_class() if hasattr(asset, 'get_class') else None
635
- if asset_class and class_name in asset_class.get_name():
636
- objects.append({
637
- 'path': asset_path,
638
- 'name': asset.get_name() if hasattr(asset, 'get_name') else '',
639
- 'class': asset_class.get_name()
640
- })
641
- count += 1
642
- except Exception:
643
- pass
644
- except Exception as e:
645
- print('RESULT:' + json.dumps({'success': False, 'error': f'Asset search failed: {str(e)}'}))
646
- raise SystemExit(0)
647
-
648
- # Also search in level actors
649
- try:
650
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
651
- if actor_sub:
652
- for actor in actor_sub.get_all_level_actors():
653
- if count >= limit:
654
- break
655
- if actor:
656
- actor_class = actor.get_class() if hasattr(actor, 'get_class') else None
657
- if actor_class and class_name in actor_class.get_name():
658
- objects.append({
659
- 'path': actor.get_path_name() if hasattr(actor, 'get_path_name') else '',
660
- 'name': actor.get_actor_label() if hasattr(actor, 'get_actor_label') else '',
661
- 'class': actor_class.get_name()
662
- })
663
- count += 1
664
- except Exception:
665
- pass
666
-
667
- print('RESULT:' + json.dumps({'success': True, 'objects': objects, 'count': len(objects)}))
668
- except Exception as e:
669
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
670
- `.trim();
671
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'findObjectsByClass');
672
- return this.parsePythonResult(resp, 'findObjectsByClass');
428
+ if (!this.automationBridge) {
429
+ throw new Error('Automation Bridge not available. Object search operations require plugin support.');
430
+ }
431
+ const automationBridge = this.automationBridge;
432
+ return this.executeWithRetry(async () => {
433
+ try {
434
+ const response = await automationBridge.sendAutomationRequest('inspect', {
435
+ action: 'find_by_class',
436
+ className,
437
+ limit
438
+ }, {
439
+ timeoutMs: 60000
440
+ });
441
+ if (response?.success === false) {
442
+ return {
443
+ success: false,
444
+ error: response.error || response.message || 'Failed to find objects'
445
+ };
446
+ }
447
+ const data = response?.data ?? response?.result ?? response;
448
+ const validObjects = Array.isArray(data?.actors)
449
+ ? data.actors
450
+ : (Array.isArray(data?.objects) ? data.objects : (Array.isArray(data) ? data : []));
451
+ return {
452
+ success: true,
453
+ message: `Found ${validObjects.length} objects`,
454
+ objects: validObjects,
455
+ count: validObjects.length
456
+ };
457
+ }
458
+ catch (error) {
459
+ return {
460
+ success: false,
461
+ error: `Failed to find objects: ${error instanceof Error ? error.message : String(error)}`
462
+ };
463
+ }
464
+ }, 'findObjectsByClass');
465
+ }
466
+ async getComponentProperty(params) {
467
+ if (!this.automationBridge) {
468
+ throw new Error('Automation Bridge not available. Component property operations require plugin support.');
469
+ }
470
+ const automationBridge = this.automationBridge;
471
+ return this.executeWithRetry(async () => {
472
+ try {
473
+ const response = await automationBridge.sendAutomationRequest('get_component_property', {
474
+ objectPath: params.objectPath,
475
+ componentName: params.componentName,
476
+ propertyName: params.propertyName
477
+ }, {
478
+ timeoutMs: 15000
479
+ });
480
+ if (response.success === false) {
481
+ return {
482
+ success: false,
483
+ error: response.error || response.message || 'Failed to get component property'
484
+ };
485
+ }
486
+ return {
487
+ success: true,
488
+ value: response.value,
489
+ type: response.type
490
+ };
491
+ }
492
+ catch (error) {
493
+ return {
494
+ success: false,
495
+ error: `Failed to get component property: ${error instanceof Error ? error.message : String(error)}`
496
+ };
497
+ }
498
+ }, 'getComponentProperty');
499
+ }
500
+ async setComponentProperty(params) {
501
+ if (!this.automationBridge) {
502
+ throw new Error('Automation Bridge not available. Component property operations require plugin support.');
503
+ }
504
+ const automationBridge = this.automationBridge;
505
+ return this.executeWithRetry(async () => {
506
+ try {
507
+ const response = await automationBridge.sendAutomationRequest('set_component_property', {
508
+ objectPath: params.objectPath,
509
+ componentName: params.componentName,
510
+ propertyName: params.propertyName,
511
+ value: params.value
512
+ }, {
513
+ timeoutMs: 15000
514
+ });
515
+ if (response.success === false) {
516
+ return {
517
+ success: false,
518
+ error: response.error || response.message || 'Failed to set component property'
519
+ };
520
+ }
521
+ return {
522
+ success: true,
523
+ message: response.message || 'Property set successfully'
524
+ };
525
+ }
526
+ catch (error) {
527
+ return {
528
+ success: false,
529
+ error: `Failed to set component property: ${error instanceof Error ? error.message : String(error)}`
530
+ };
531
+ }
532
+ }, 'setComponentProperty');
673
533
  }
674
- /**
675
- * Clear object cache
676
- */
677
534
  clearCache() {
678
535
  this.objectCache.clear();
679
536
  }