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
@@ -0,0 +1,452 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { WebSocket } from 'ws';
3
+ import { Logger } from '../utils/logger.js';
4
+ import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT, DEFAULT_NEGOTIATED_PROTOCOLS, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_MAX_PENDING_REQUESTS } from '../constants.js';
5
+ import { createRequire } from 'node:module';
6
+ import { ConnectionManager } from './connection-manager.js';
7
+ import { RequestTracker } from './request-tracker.js';
8
+ import { HandshakeHandler } from './handshake.js';
9
+ import { MessageHandler } from './message-handler.js';
10
+ const require = createRequire(import.meta.url);
11
+ const packageInfo = (() => {
12
+ try {
13
+ return require('../../package.json');
14
+ }
15
+ catch (error) {
16
+ const log = new Logger('AutomationBridge');
17
+ log.debug('Unable to read package.json for version info', error);
18
+ return {};
19
+ }
20
+ })();
21
+ export class AutomationBridge extends EventEmitter {
22
+ host;
23
+ port;
24
+ ports;
25
+ negotiatedProtocols;
26
+ capabilityToken;
27
+ enabled;
28
+ serverName;
29
+ serverVersion;
30
+ clientHost;
31
+ clientPort;
32
+ serverLegacyEnabled;
33
+ maxConcurrentConnections;
34
+ connectionManager;
35
+ requestTracker;
36
+ handshakeHandler;
37
+ messageHandler;
38
+ log = new Logger('AutomationBridge');
39
+ lastHandshakeAt;
40
+ lastHandshakeMetadata;
41
+ lastHandshakeAck;
42
+ lastHandshakeFailure;
43
+ lastDisconnect;
44
+ lastError;
45
+ requestQueue = [];
46
+ queuedRequestItems = [];
47
+ connectionPromise;
48
+ constructor(options = {}) {
49
+ super();
50
+ this.host = options.host ?? process.env.MCP_AUTOMATION_WS_HOST ?? DEFAULT_AUTOMATION_HOST;
51
+ const sanitizePort = (value) => {
52
+ if (typeof value === 'number' && Number.isInteger(value)) {
53
+ return value > 0 && value <= 65535 ? value : null;
54
+ }
55
+ if (typeof value === 'string' && value.trim().length > 0) {
56
+ const parsed = Number.parseInt(value.trim(), 10);
57
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null;
58
+ }
59
+ return null;
60
+ };
61
+ const defaultPort = sanitizePort(options.port ?? process.env.MCP_AUTOMATION_WS_PORT) ?? DEFAULT_AUTOMATION_PORT;
62
+ const configuredPortValues = options.ports
63
+ ? options.ports
64
+ : process.env.MCP_AUTOMATION_WS_PORTS
65
+ ?.split(',')
66
+ .map((token) => token.trim())
67
+ .filter((token) => token.length > 0);
68
+ const sanitizedPorts = Array.isArray(configuredPortValues)
69
+ ? configuredPortValues
70
+ .map((value) => sanitizePort(value))
71
+ .filter((port) => port !== null)
72
+ : [];
73
+ if (!sanitizedPorts.includes(defaultPort)) {
74
+ sanitizedPorts.unshift(defaultPort);
75
+ }
76
+ if (sanitizedPorts.length === 0) {
77
+ sanitizedPorts.push(DEFAULT_AUTOMATION_PORT);
78
+ }
79
+ this.ports = Array.from(new Set(sanitizedPorts));
80
+ const defaultProtocols = DEFAULT_NEGOTIATED_PROTOCOLS;
81
+ const userProtocols = Array.isArray(options.protocols)
82
+ ? options.protocols.filter((proto) => typeof proto === 'string' && proto.trim().length > 0)
83
+ : [];
84
+ const envProtocols = process.env.MCP_AUTOMATION_WS_PROTOCOLS
85
+ ? process.env.MCP_AUTOMATION_WS_PROTOCOLS.split(',')
86
+ .map((token) => token.trim())
87
+ .filter((token) => token.length > 0)
88
+ : [];
89
+ this.negotiatedProtocols = Array.from(new Set([...userProtocols, ...envProtocols, ...defaultProtocols]));
90
+ this.port = this.ports[0];
91
+ this.serverLegacyEnabled =
92
+ options.serverLegacyEnabled ?? process.env.MCP_AUTOMATION_SERVER_LEGACY !== 'false';
93
+ this.capabilityToken =
94
+ options.capabilityToken ?? process.env.MCP_AUTOMATION_CAPABILITY_TOKEN ?? undefined;
95
+ this.enabled = options.enabled ?? process.env.MCP_AUTOMATION_BRIDGE_ENABLED !== 'false';
96
+ this.serverName = options.serverName
97
+ ?? process.env.MCP_SERVER_NAME
98
+ ?? packageInfo.name
99
+ ?? 'unreal-engine-mcp';
100
+ this.serverVersion = options.serverVersion
101
+ ?? process.env.MCP_SERVER_VERSION
102
+ ?? packageInfo.version
103
+ ?? process.env.npm_package_version
104
+ ?? '0.0.0';
105
+ const heartbeatIntervalMs = (options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS) > 0
106
+ ? (options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS)
107
+ : 0;
108
+ const maxPendingRequests = Math.max(1, options.maxPendingRequests ?? DEFAULT_MAX_PENDING_REQUESTS);
109
+ const maxConcurrentConnections = Math.max(1, options.maxConcurrentConnections ?? 10);
110
+ this.clientHost = options.clientHost ?? process.env.MCP_AUTOMATION_CLIENT_HOST ?? DEFAULT_AUTOMATION_HOST;
111
+ this.clientPort = options.clientPort ?? sanitizePort(process.env.MCP_AUTOMATION_CLIENT_PORT) ?? DEFAULT_AUTOMATION_PORT;
112
+ this.maxConcurrentConnections = maxConcurrentConnections;
113
+ this.connectionManager = new ConnectionManager(heartbeatIntervalMs);
114
+ this.requestTracker = new RequestTracker(maxPendingRequests);
115
+ this.handshakeHandler = new HandshakeHandler(this.capabilityToken);
116
+ this.messageHandler = new MessageHandler(this.requestTracker);
117
+ }
118
+ on(event, listener) {
119
+ return super.on(event, listener);
120
+ }
121
+ once(event, listener) {
122
+ return super.once(event, listener);
123
+ }
124
+ off(event, listener) {
125
+ return super.off(event, listener);
126
+ }
127
+ start() {
128
+ if (!this.enabled) {
129
+ this.log.info('Automation bridge disabled by configuration.');
130
+ return;
131
+ }
132
+ this.log.info(`Automation bridge connecting to Unreal server at ws://${this.clientHost}:${this.clientPort}`);
133
+ this.startClient();
134
+ }
135
+ startClient() {
136
+ try {
137
+ const url = `ws://${this.clientHost}:${this.clientPort}`;
138
+ this.log.info(`Connecting to Unreal Engine automation server at ${url}`);
139
+ this.log.debug(`Negotiated protocols: ${JSON.stringify(this.negotiatedProtocols)}`);
140
+ const protocols = 'mcp-automation';
141
+ this.log.debug(`Using WebSocket protocols arg: ${JSON.stringify(protocols)}`);
142
+ const socket = new WebSocket(url, protocols, {
143
+ headers: this.capabilityToken ? { 'X-MCP-Capability': this.capabilityToken } : undefined,
144
+ perMessageDeflate: false
145
+ });
146
+ this.handleClientConnection(socket);
147
+ }
148
+ catch (error) {
149
+ const errorObj = error instanceof Error ? error : new Error(String(error));
150
+ this.lastError = { message: errorObj.message, at: new Date() };
151
+ this.log.error('Failed to create WebSocket client connection', errorObj);
152
+ const errorWithPort = Object.assign(errorObj, { port: this.clientPort });
153
+ this.emitAutomation('error', errorWithPort);
154
+ }
155
+ }
156
+ async handleClientConnection(socket) {
157
+ socket.on('open', async () => {
158
+ this.log.info('Automation bridge client connected, starting handshake');
159
+ try {
160
+ const metadata = await this.handshakeHandler.initiateHandshake(socket);
161
+ this.lastHandshakeAt = new Date();
162
+ this.lastHandshakeMetadata = metadata;
163
+ this.lastHandshakeFailure = undefined;
164
+ this.connectionManager.updateLastMessageTime();
165
+ const underlying = socket._socket || socket.socket;
166
+ const remoteAddr = underlying?.remoteAddress ?? undefined;
167
+ const remotePort = underlying?.remotePort ?? undefined;
168
+ this.connectionManager.registerSocket(socket, this.clientPort, metadata, remoteAddr, remotePort);
169
+ this.connectionManager.startHeartbeat();
170
+ this.flushQueue();
171
+ this.emitAutomation('connected', {
172
+ socket,
173
+ metadata,
174
+ port: this.clientPort,
175
+ protocol: socket.protocol || null
176
+ });
177
+ socket.on('message', (data) => {
178
+ try {
179
+ const text = typeof data === 'string' ? data : data.toString('utf8');
180
+ this.log.debug(`[AutomationBridge Client] Received message: ${text.substring(0, 1000)}`);
181
+ const parsed = JSON.parse(text);
182
+ this.connectionManager.updateLastMessageTime();
183
+ this.messageHandler.handleMessage(parsed);
184
+ this.emitAutomation('message', parsed);
185
+ }
186
+ catch (error) {
187
+ this.log.error('Error handling message', error);
188
+ }
189
+ });
190
+ }
191
+ catch (error) {
192
+ const err = error instanceof Error ? error : new Error(String(error));
193
+ this.lastHandshakeFailure = { reason: err.message, at: new Date() };
194
+ this.emitAutomation('handshakeFailed', { reason: err.message, port: this.clientPort });
195
+ }
196
+ });
197
+ socket.on('error', (error) => {
198
+ this.log.error('Automation bridge client socket error', error);
199
+ const errObj = error instanceof Error ? error : new Error(String(error));
200
+ this.lastError = { message: errObj.message, at: new Date() };
201
+ const errWithPort = Object.assign(errObj, { port: this.clientPort });
202
+ this.emitAutomation('error', errWithPort);
203
+ });
204
+ socket.on('close', (code, reasonBuffer) => {
205
+ const reason = reasonBuffer.toString('utf8');
206
+ const socketInfo = this.connectionManager.removeSocket(socket);
207
+ if (socketInfo) {
208
+ this.lastDisconnect = { code, reason, at: new Date() };
209
+ this.emitAutomation('disconnected', {
210
+ code,
211
+ reason,
212
+ port: socketInfo.port,
213
+ protocol: socketInfo.protocol || null
214
+ });
215
+ this.log.info(`Automation bridge client socket closed (code=${code}, reason=${reason})`);
216
+ if (!this.connectionManager.isConnected()) {
217
+ this.requestTracker.rejectAll(new Error(reason || 'Connection lost'));
218
+ }
219
+ }
220
+ });
221
+ }
222
+ stop() {
223
+ if (this.isConnected()) {
224
+ this.broadcast({
225
+ type: 'bridge_shutdown',
226
+ timestamp: new Date().toISOString(),
227
+ reason: 'Server shutting down'
228
+ });
229
+ }
230
+ this.connectionManager.closeAll(1001, 'Server shutdown');
231
+ this.lastHandshakeAck = undefined;
232
+ this.requestTracker.rejectAll(new Error('Automation bridge server stopped'));
233
+ }
234
+ isConnected() {
235
+ return this.connectionManager.isConnected();
236
+ }
237
+ getStatus() {
238
+ const connectionInfos = Array.from(this.connectionManager.getActiveSockets().entries()).map(([socket, info]) => ({
239
+ connectionId: info.connectionId,
240
+ sessionId: info.sessionId ?? null,
241
+ remoteAddress: info.remoteAddress ?? null,
242
+ remotePort: info.remotePort ?? null,
243
+ port: info.port,
244
+ connectedAt: info.connectedAt.toISOString(),
245
+ protocol: info.protocol || null,
246
+ readyState: socket.readyState,
247
+ isPrimary: socket === this.connectionManager.getPrimarySocket()
248
+ }));
249
+ return {
250
+ enabled: this.enabled,
251
+ host: this.host,
252
+ port: this.port,
253
+ configuredPorts: [...this.ports],
254
+ listeningPorts: [],
255
+ connected: this.isConnected(),
256
+ connectedAt: connectionInfos.length > 0 ? connectionInfos[0].connectedAt : null,
257
+ activePort: connectionInfos.length > 0 ? connectionInfos[0].port : null,
258
+ negotiatedProtocol: connectionInfos.length > 0 ? connectionInfos[0].protocol : null,
259
+ supportedProtocols: [...this.negotiatedProtocols],
260
+ supportedOpcodes: ['automation_request'],
261
+ expectedResponseOpcodes: ['automation_response'],
262
+ capabilityTokenRequired: Boolean(this.capabilityToken),
263
+ lastHandshakeAt: this.lastHandshakeAt?.toISOString() ?? null,
264
+ lastHandshakeMetadata: this.lastHandshakeMetadata ?? null,
265
+ lastHandshakeAck: this.lastHandshakeAck ?? null,
266
+ lastHandshakeFailure: this.lastHandshakeFailure
267
+ ? { reason: this.lastHandshakeFailure.reason, at: this.lastHandshakeFailure.at.toISOString() }
268
+ : null,
269
+ lastDisconnect: this.lastDisconnect
270
+ ? { code: this.lastDisconnect.code, reason: this.lastDisconnect.reason, at: this.lastDisconnect.at.toISOString() }
271
+ : null,
272
+ lastError: this.lastError
273
+ ? { message: this.lastError.message, at: this.lastError.at.toISOString() }
274
+ : null,
275
+ lastMessageAt: this.connectionManager.getLastMessageTime()?.toISOString() ?? null,
276
+ lastRequestSentAt: null,
277
+ pendingRequests: this.requestTracker.getPendingCount(),
278
+ pendingRequestDetails: this.requestTracker.getPendingDetails(),
279
+ connections: connectionInfos,
280
+ webSocketListening: false,
281
+ serverLegacyEnabled: this.serverLegacyEnabled,
282
+ serverName: this.serverName,
283
+ serverVersion: this.serverVersion,
284
+ maxConcurrentConnections: this.maxConcurrentConnections,
285
+ maxPendingRequests: 100,
286
+ heartbeatIntervalMs: 30000
287
+ };
288
+ }
289
+ async sendAutomationRequest(action, payload = {}, options = {}) {
290
+ if (!this.isConnected()) {
291
+ if (this.enabled) {
292
+ this.log.info('Automation bridge not connected, attempting lazy connection...');
293
+ if (!this.connectionPromise) {
294
+ this.connectionPromise = new Promise((resolve, reject) => {
295
+ const onConnect = () => {
296
+ cleanup();
297
+ resolve();
298
+ };
299
+ const onError = (err) => {
300
+ cleanup();
301
+ reject(err);
302
+ };
303
+ const onHandshakeFail = (err) => {
304
+ cleanup();
305
+ reject(new Error(`Handshake failed: ${err.reason}`));
306
+ };
307
+ const cleanup = () => {
308
+ this.off('connected', onConnect);
309
+ this.off('error', onError);
310
+ this.off('handshakeFailed', onHandshakeFail);
311
+ if (this.connectionPromise)
312
+ this.connectionPromise = undefined;
313
+ };
314
+ this.once('connected', onConnect);
315
+ this.once('error', onError);
316
+ this.once('handshakeFailed', onHandshakeFail);
317
+ try {
318
+ this.startClient();
319
+ }
320
+ catch (e) {
321
+ onError(e);
322
+ }
323
+ });
324
+ }
325
+ try {
326
+ const connectTimeout = 5000;
327
+ await Promise.race([
328
+ this.connectionPromise,
329
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Lazy connection timeout')), connectTimeout))
330
+ ]);
331
+ }
332
+ catch (err) {
333
+ this.log.error('Lazy connection failed', err);
334
+ throw new Error(`Failed to establish connection to Unreal Engine: ${err.message}`);
335
+ }
336
+ }
337
+ else {
338
+ throw new Error('Automation bridge disabled');
339
+ }
340
+ }
341
+ if (!this.isConnected()) {
342
+ throw new Error('Automation bridge not connected');
343
+ }
344
+ if (this.requestTracker.getPendingCount() >= this.requestTracker.maxPendingRequests) {
345
+ return new Promise((resolve, reject) => {
346
+ this.queuedRequestItems.push({
347
+ resolve,
348
+ reject,
349
+ action,
350
+ payload,
351
+ options
352
+ });
353
+ });
354
+ }
355
+ return this.sendRequestInternal(action, payload, options);
356
+ }
357
+ async sendRequestInternal(action, payload, options) {
358
+ const timeoutMs = options.timeoutMs ?? 60000;
359
+ const coalesceKey = this.requestTracker.createCoalesceKey(action, payload);
360
+ if (coalesceKey) {
361
+ const existing = this.requestTracker.getCoalescedRequest(coalesceKey);
362
+ if (existing) {
363
+ return existing;
364
+ }
365
+ }
366
+ const { requestId, promise } = this.requestTracker.createRequest(action, payload, timeoutMs);
367
+ if (coalesceKey) {
368
+ this.requestTracker.setCoalescedRequest(coalesceKey, promise);
369
+ }
370
+ const message = {
371
+ type: 'automation_request',
372
+ requestId,
373
+ action,
374
+ payload
375
+ };
376
+ const resultPromise = promise;
377
+ resultPromise.finally(() => {
378
+ this.processRequestQueue();
379
+ }).catch(() => { });
380
+ if (this.send(message)) {
381
+ return resultPromise;
382
+ }
383
+ else {
384
+ this.requestTracker.rejectRequest(requestId, new Error('Failed to send request'));
385
+ throw new Error('Failed to send request');
386
+ }
387
+ }
388
+ processRequestQueue() {
389
+ if (this.queuedRequestItems.length === 0)
390
+ return;
391
+ while (this.queuedRequestItems.length > 0 &&
392
+ this.requestTracker.getPendingCount() < this.requestTracker.maxPendingRequests) {
393
+ const item = this.queuedRequestItems.shift();
394
+ if (item) {
395
+ this.sendRequestInternal(item.action, item.payload, item.options)
396
+ .then(item.resolve)
397
+ .catch(item.reject);
398
+ }
399
+ }
400
+ }
401
+ send(payload) {
402
+ const primarySocket = this.connectionManager.getPrimarySocket();
403
+ if (!primarySocket || primarySocket.readyState !== WebSocket.OPEN) {
404
+ this.log.warn('Attempted to send automation message without an active primary connection');
405
+ return false;
406
+ }
407
+ try {
408
+ primarySocket.send(JSON.stringify(payload));
409
+ return true;
410
+ }
411
+ catch (error) {
412
+ this.log.error('Failed to send automation message', error);
413
+ const errObj = error instanceof Error ? error : new Error(String(error));
414
+ const primaryInfo = this.connectionManager.getActiveSockets().get(primarySocket);
415
+ const errorWithPort = Object.assign(errObj, { port: primaryInfo?.port });
416
+ this.emitAutomation('error', errorWithPort);
417
+ return false;
418
+ }
419
+ }
420
+ broadcast(payload) {
421
+ const sockets = this.connectionManager.getActiveSockets();
422
+ if (sockets.size === 0) {
423
+ this.log.warn('Attempted to broadcast automation message without any active connections');
424
+ return false;
425
+ }
426
+ let sentCount = 0;
427
+ for (const [socket] of sockets) {
428
+ if (socket.readyState === WebSocket.OPEN) {
429
+ try {
430
+ socket.send(JSON.stringify(payload));
431
+ sentCount++;
432
+ }
433
+ catch (error) {
434
+ this.log.error('Failed to broadcast automation message to socket', error);
435
+ }
436
+ }
437
+ }
438
+ return sentCount > 0;
439
+ }
440
+ flushQueue() {
441
+ if (this.requestQueue.length === 0)
442
+ return;
443
+ this.log.info(`Flushing ${this.requestQueue.length} queued automation requests`);
444
+ const queue = [...this.requestQueue];
445
+ this.requestQueue = [];
446
+ queue.forEach(fn => fn());
447
+ }
448
+ emitAutomation(event, ...args) {
449
+ this.emit(event, ...args);
450
+ }
451
+ }
452
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1,23 @@
1
+ import { WebSocket } from 'ws';
2
+ import { SocketInfo } from './types.js';
3
+ import { EventEmitter } from 'node:events';
4
+ export declare class ConnectionManager extends EventEmitter {
5
+ private heartbeatIntervalMs;
6
+ private activeSockets;
7
+ private primarySocket?;
8
+ private heartbeatTimer?;
9
+ private lastMessageAt?;
10
+ private log;
11
+ constructor(heartbeatIntervalMs: number);
12
+ registerSocket(socket: WebSocket, port: number, metadata?: Record<string, unknown>, remoteAddress?: string, remotePort?: number): void;
13
+ removeSocket(socket: WebSocket): SocketInfo | undefined;
14
+ getActiveSockets(): Map<WebSocket, SocketInfo>;
15
+ getPrimarySocket(): WebSocket | undefined;
16
+ isConnected(): boolean;
17
+ startHeartbeat(): void;
18
+ stopHeartbeat(): void;
19
+ updateLastMessageTime(): void;
20
+ getLastMessageTime(): Date | undefined;
21
+ closeAll(code?: number, reason?: string): void;
22
+ }
23
+ //# sourceMappingURL=connection-manager.d.ts.map
@@ -0,0 +1,107 @@
1
+ import { WebSocket } from 'ws';
2
+ import { Logger } from '../utils/logger.js';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { EventEmitter } from 'node:events';
5
+ export class ConnectionManager extends EventEmitter {
6
+ heartbeatIntervalMs;
7
+ activeSockets = new Map();
8
+ primarySocket;
9
+ heartbeatTimer;
10
+ lastMessageAt;
11
+ log = new Logger('ConnectionManager');
12
+ constructor(heartbeatIntervalMs) {
13
+ super();
14
+ this.heartbeatIntervalMs = heartbeatIntervalMs;
15
+ }
16
+ registerSocket(socket, port, metadata, remoteAddress, remotePort) {
17
+ const connectionId = randomUUID();
18
+ const sessionId = metadata && typeof metadata.sessionId === 'string' ? metadata.sessionId : undefined;
19
+ const socketInfo = {
20
+ connectionId,
21
+ port,
22
+ connectedAt: new Date(),
23
+ protocol: socket.protocol || undefined,
24
+ sessionId,
25
+ remoteAddress: remoteAddress ?? undefined,
26
+ remotePort: typeof remotePort === 'number' ? remotePort : undefined
27
+ };
28
+ this.activeSockets.set(socket, socketInfo);
29
+ if (!this.primarySocket) {
30
+ this.primarySocket = socket;
31
+ }
32
+ socket.on('pong', () => {
33
+ this.lastMessageAt = new Date();
34
+ });
35
+ }
36
+ removeSocket(socket) {
37
+ const info = this.activeSockets.get(socket);
38
+ if (info) {
39
+ this.activeSockets.delete(socket);
40
+ if (socket === this.primarySocket) {
41
+ this.primarySocket = this.activeSockets.size > 0 ? this.activeSockets.keys().next().value : undefined;
42
+ if (this.activeSockets.size === 0) {
43
+ this.stopHeartbeat();
44
+ }
45
+ }
46
+ }
47
+ return info;
48
+ }
49
+ getActiveSockets() {
50
+ return this.activeSockets;
51
+ }
52
+ getPrimarySocket() {
53
+ return this.primarySocket;
54
+ }
55
+ isConnected() {
56
+ return this.activeSockets.size > 0;
57
+ }
58
+ startHeartbeat() {
59
+ if (this.heartbeatIntervalMs <= 0)
60
+ return;
61
+ if (this.heartbeatTimer)
62
+ clearInterval(this.heartbeatTimer);
63
+ this.heartbeatTimer = setInterval(() => {
64
+ if (this.activeSockets.size === 0) {
65
+ this.stopHeartbeat();
66
+ return;
67
+ }
68
+ const pingPayload = JSON.stringify({
69
+ type: 'bridge_ping',
70
+ timestamp: new Date().toISOString()
71
+ });
72
+ for (const [socket] of this.activeSockets) {
73
+ if (socket.readyState === WebSocket.OPEN) {
74
+ try {
75
+ socket.ping();
76
+ socket.send(pingPayload);
77
+ }
78
+ catch (error) {
79
+ this.log.error('Failed to send heartbeat', error);
80
+ }
81
+ }
82
+ }
83
+ }, this.heartbeatIntervalMs);
84
+ }
85
+ stopHeartbeat() {
86
+ if (this.heartbeatTimer) {
87
+ clearInterval(this.heartbeatTimer);
88
+ this.heartbeatTimer = undefined;
89
+ }
90
+ }
91
+ updateLastMessageTime() {
92
+ this.lastMessageAt = new Date();
93
+ }
94
+ getLastMessageTime() {
95
+ return this.lastMessageAt;
96
+ }
97
+ closeAll(code, reason) {
98
+ this.stopHeartbeat();
99
+ for (const [socket] of this.activeSockets) {
100
+ socket.removeAllListeners();
101
+ socket.close(code, reason);
102
+ }
103
+ this.activeSockets.clear();
104
+ this.primarySocket = undefined;
105
+ }
106
+ }
107
+ //# sourceMappingURL=connection-manager.js.map
@@ -0,0 +1,11 @@
1
+ import { WebSocket } from 'ws';
2
+ import { EventEmitter } from 'node:events';
3
+ export declare class HandshakeHandler extends EventEmitter {
4
+ private capabilityToken?;
5
+ private log;
6
+ private readonly DEFAULT_HANDSHAKE_TIMEOUT_MS;
7
+ constructor(capabilityToken?: string | undefined);
8
+ initiateHandshake(socket: WebSocket, timeoutMs?: number): Promise<Record<string, unknown>>;
9
+ private sanitizeHandshakeMetadata;
10
+ }
11
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1,89 @@
1
+ import { WebSocket } from 'ws';
2
+ import { Logger } from '../utils/logger.js';
3
+ import { EventEmitter } from 'node:events';
4
+ export class HandshakeHandler extends EventEmitter {
5
+ capabilityToken;
6
+ log = new Logger('HandshakeHandler');
7
+ DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
8
+ constructor(capabilityToken) {
9
+ super();
10
+ this.capabilityToken = capabilityToken;
11
+ }
12
+ initiateHandshake(socket, timeoutMs = this.DEFAULT_HANDSHAKE_TIMEOUT_MS) {
13
+ return new Promise((resolve, reject) => {
14
+ let handshakeComplete = false;
15
+ const timeout = setTimeout(() => {
16
+ if (!handshakeComplete) {
17
+ this.log.warn('Automation bridge client handshake timed out');
18
+ socket.close(4002, 'Handshake timeout');
19
+ reject(new Error('Handshake timeout'));
20
+ }
21
+ }, timeoutMs);
22
+ const onMessage = (data) => {
23
+ let parsed;
24
+ const text = typeof data === 'string' ? data : data.toString('utf8');
25
+ try {
26
+ parsed = JSON.parse(text);
27
+ }
28
+ catch (error) {
29
+ this.log.error('Received non-JSON automation message during handshake', error);
30
+ socket.close(4003, 'Invalid JSON payload');
31
+ cleanup();
32
+ reject(new Error('Invalid JSON payload'));
33
+ return;
34
+ }
35
+ if (parsed.type === 'bridge_ack') {
36
+ handshakeComplete = true;
37
+ cleanup();
38
+ const metadata = this.sanitizeHandshakeMetadata(parsed);
39
+ resolve(metadata);
40
+ }
41
+ else {
42
+ this.log.warn(`Expected bridge_ack handshake, received ${parsed.type}`);
43
+ socket.close(4004, 'Handshake expected bridge_ack');
44
+ cleanup();
45
+ reject(new Error(`Handshake expected bridge_ack, got ${parsed.type}`));
46
+ }
47
+ };
48
+ const onError = (error) => {
49
+ cleanup();
50
+ reject(error);
51
+ };
52
+ const onClose = () => {
53
+ cleanup();
54
+ reject(new Error('Socket closed during handshake'));
55
+ };
56
+ const cleanup = () => {
57
+ clearTimeout(timeout);
58
+ socket.off('message', onMessage);
59
+ socket.off('error', onError);
60
+ socket.off('close', onClose);
61
+ };
62
+ socket.on('message', onMessage);
63
+ socket.on('error', onError);
64
+ socket.on('close', onClose);
65
+ setTimeout(() => {
66
+ if (socket.readyState === WebSocket.OPEN) {
67
+ const helloPayload = {
68
+ type: 'bridge_hello',
69
+ capabilityToken: this.capabilityToken || undefined
70
+ };
71
+ this.log.debug(`Sending bridge_hello (delayed): ${JSON.stringify(helloPayload)}`);
72
+ socket.send(JSON.stringify(helloPayload));
73
+ }
74
+ else {
75
+ this.log.warn('Socket closed before bridge_hello could be sent');
76
+ }
77
+ }, 500);
78
+ });
79
+ }
80
+ sanitizeHandshakeMetadata(payload) {
81
+ const sanitized = { ...payload };
82
+ delete sanitized.type;
83
+ if ('capabilityToken' in sanitized) {
84
+ sanitized.capabilityToken = 'REDACTED';
85
+ }
86
+ return sanitized;
87
+ }
88
+ }
89
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1,3 @@
1
+ export { AutomationBridge } from './bridge.js';
2
+ export * from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map