unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -0,0 +1,75 @@
1
+ export class CommandValidator {
2
+ private static readonly DANGEROUS_COMMANDS = [
3
+ 'quit', 'exit', 'delete', 'destroy', 'kill', 'crash',
4
+ 'viewmode visualizebuffer basecolor',
5
+ 'viewmode visualizebuffer worldnormal',
6
+ 'r.gpucrash',
7
+ 'buildpaths', // Can cause access violation if nav system not initialized
8
+ 'rebuildnavigation' // Can also crash without proper nav setup
9
+ ];
10
+
11
+ private static readonly FORBIDDEN_TOKENS = [
12
+ 'rm ', 'rm-', 'del ', 'format ', 'shutdown', 'reboot',
13
+ 'rmdir', 'mklink', 'copy ', 'move ', 'start "', 'system(',
14
+ 'import os', 'import subprocess', 'subprocess.', 'os.system',
15
+ 'exec(', 'eval(', '__import__', 'import sys', 'import importlib',
16
+ 'with open', 'open('
17
+ ];
18
+
19
+ private static readonly INVALID_PATTERNS = [
20
+ /^\d+$/, // Just numbers
21
+ /^invalid_command/i,
22
+ /^this_is_not_a_valid/i,
23
+ ];
24
+
25
+ static validate(command: string): void {
26
+ if (!command || typeof command !== 'string') {
27
+ throw new Error('Invalid command: must be a non-empty string');
28
+ }
29
+
30
+ const cmdTrimmed = command.trim();
31
+ if (cmdTrimmed.length === 0) {
32
+ return; // Empty commands are technically valid (no-op)
33
+ }
34
+
35
+ if (cmdTrimmed.includes('\n') || cmdTrimmed.includes('\r')) {
36
+ throw new Error('Multi-line console commands are not allowed. Send one command per call.');
37
+ }
38
+
39
+ const cmdLower = cmdTrimmed.toLowerCase();
40
+
41
+ if (cmdLower === 'py' || cmdLower.startsWith('py ')) {
42
+ throw new Error('Python console commands are blocked from external calls for safety.');
43
+ }
44
+
45
+ if (this.DANGEROUS_COMMANDS.some(dangerous => cmdLower.includes(dangerous))) {
46
+ throw new Error(`Dangerous command blocked: ${command}`);
47
+ }
48
+
49
+ if (cmdLower.includes('&&') || cmdLower.includes('||')) {
50
+ throw new Error('Command chaining with && or || is blocked for safety.');
51
+ }
52
+
53
+ if (this.FORBIDDEN_TOKENS.some(token => cmdLower.includes(token))) {
54
+ throw new Error(`Command contains unsafe token and was blocked: ${command}`);
55
+ }
56
+ }
57
+
58
+ static isLikelyInvalid(command: string): boolean {
59
+ const cmdTrimmed = command.trim();
60
+ return this.INVALID_PATTERNS.some(pattern => pattern.test(cmdTrimmed));
61
+ }
62
+
63
+ static getPriority(command: string): number {
64
+ if (command.includes('BuildLighting') || command.includes('BuildPaths')) {
65
+ return 1; // Heavy operation
66
+ } else if (command.includes('summon') || command.includes('spawn')) {
67
+ return 5; // Medium operation
68
+ } else if (command.startsWith('stat')) {
69
+ return 8; // Dedicated throttling for stat commands
70
+ } else if (command.startsWith('show')) {
71
+ return 9; // Light operation
72
+ }
73
+ return 7; // Default priority
74
+ }
75
+ }
@@ -2,7 +2,7 @@ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { Logger } from './logger.js';
3
3
 
4
4
  // Minimal helper to opportunistically use MCP Elicitation when available.
5
- // Safe across clients: validates schema shape, handles timeouts and -32601 fallbacks.
5
+ // Safe across clients: validates schema shape and handles timeouts and -32601 errors.
6
6
  export type PrimitiveSchema =
7
7
  | { type: 'string'; title?: string; description?: string; minLength?: number; maxLength?: number; pattern?: string; format?: 'email'|'uri'|'date'|'date-time'; default?: string }
8
8
  | { type: 'number'|'integer'; title?: string; description?: string; minimum?: number; maximum?: number; default?: number }
@@ -17,7 +17,9 @@ export interface ElicitSchema {
17
17
 
18
18
  export interface ElicitOptions {
19
19
  timeoutMs?: number;
20
- fallback?: () => Promise<{ ok: boolean; value?: any; error?: string }>;
20
+ // Handler invoked when elicitation cannot be performed; previously named
21
+ // Handler invoked when elicitation cannot be performed.
22
+ alternate?: () => Promise<{ ok: boolean; value?: any; error?: string }>;
21
23
  }
22
24
 
23
25
  export function createElicitationHelper(server: Server, log: Logger) {
@@ -77,7 +79,7 @@ export function createElicitationHelper(server: Server, log: Logger) {
77
79
 
78
80
  async function elicit(message: string, requestedSchema: ElicitSchema, opts: ElicitOptions = {}) {
79
81
  if (!supported || !isSafeSchema(requestedSchema)) {
80
- if (opts.fallback) return opts.fallback();
82
+ if (opts.alternate) return opts.alternate();
81
83
  return { ok: false, error: 'elicitation-unsupported' };
82
84
  }
83
85
 
@@ -98,10 +100,10 @@ export function createElicitationHelper(server: Server, log: Logger) {
98
100
 
99
101
  if (action === 'accept') return { ok: true, value: content };
100
102
  if (action === 'decline' || action === 'cancel') {
101
- if (opts.fallback) return opts.fallback();
103
+ if (opts.alternate) return opts.alternate();
102
104
  return { ok: false, error: action };
103
105
  }
104
- if (opts.fallback) return opts.fallback();
106
+ if (opts.alternate) return opts.alternate();
105
107
  return { ok: false, error: 'unexpected-response' };
106
108
  } catch (e: any) {
107
109
  const msg = String(e?.message || e);
@@ -115,8 +117,9 @@ export function createElicitationHelper(server: Server, log: Logger) {
115
117
  ) {
116
118
  supported = false;
117
119
  }
118
- log.debug('Elicitation failed; falling back', { error: msg, code });
119
- if (opts.fallback) return opts.fallback();
120
+ // Use an alternate handler if provided when elicitation fails.
121
+ log.debug('Elicitation failed; using alternate handler', { error: msg, code });
122
+ if (opts.alternate) return opts.alternate();
120
123
  return { ok: false, error: msg.includes('timeout') ? 'timeout' : 'rpc-failed' };
121
124
  }
122
125
  }
@@ -95,7 +95,7 @@ export class ErrorHandler {
95
95
  // Unreal Engine specific errors
96
96
  if (
97
97
  errorMessage.includes('unreal') ||
98
- errorMessage.includes('remote control') ||
98
+ errorMessage.includes('connection failed') ||
99
99
  errorMessage.includes('blueprint') ||
100
100
  errorMessage.includes('actor') ||
101
101
  errorMessage.includes('asset')
@@ -128,7 +128,7 @@ export class ErrorHandler {
128
128
 
129
129
  switch (type) {
130
130
  case ErrorType.CONNECTION:
131
- return 'Failed to connect to Unreal Engine. Please ensure Remote Control is enabled and the engine is running.';
131
+ return 'Failed to connect to Unreal Engine. Please ensure the Automation Bridge plugin is active and the editor is running.';
132
132
 
133
133
  case ErrorType.VALIDATION:
134
134
  return `Invalid input: ${originalMessage}`;
@@ -164,114 +164,38 @@ export class ErrorHandler {
164
164
  }
165
165
 
166
166
  /**
167
- * Retry an async operation with exponential backoff
168
- * Best practice from TypeScript async programming patterns
169
- * @param operation - Async operation to retry
170
- * @param options - Retry configuration
171
- * @returns Result of the operation
167
+ * Retry a function with exponential backoff
172
168
  */
173
169
  static async retryWithBackoff<T>(
174
- operation: () => Promise<T>,
170
+ fn: () => Promise<T>,
175
171
  options: {
176
172
  maxRetries?: number;
177
173
  initialDelay?: number;
178
174
  maxDelay?: number;
179
175
  backoffMultiplier?: number;
180
- shouldRetry?: (error: unknown) => boolean;
176
+ shouldRetry?: (error: any) => boolean;
181
177
  } = {}
182
178
  ): Promise<T> {
183
- const {
184
- maxRetries = 3,
185
- initialDelay = 100,
186
- maxDelay = 10000,
187
- backoffMultiplier = 2,
188
- shouldRetry = (error) => this.isRetriable(error)
189
- } = options;
179
+ const maxRetries = options.maxRetries ?? 3;
180
+ const initialDelay = options.initialDelay ?? 1000;
181
+ const maxDelay = options.maxDelay ?? 10000;
182
+ const multiplier = options.backoffMultiplier ?? 2;
183
+ const shouldRetry = options.shouldRetry ?? ((err) => this.isRetriable(err));
190
184
 
191
- let lastError: unknown;
192
185
  let delay = initialDelay;
193
186
 
194
187
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
195
188
  try {
196
- return await operation();
189
+ return await fn();
197
190
  } catch (error) {
198
- lastError = error;
199
-
200
191
  if (attempt === maxRetries || !shouldRetry(error)) {
201
192
  throw error;
202
193
  }
203
-
204
- log.debug(`Retry attempt ${attempt + 1}/${maxRetries} after ${delay}ms`);
194
+
205
195
  await new Promise(resolve => setTimeout(resolve, delay));
206
-
207
- delay = Math.min(delay * backoffMultiplier, maxDelay);
196
+ delay = Math.min(delay * multiplier, maxDelay);
208
197
  }
209
198
  }
210
-
211
- throw lastError;
212
- }
213
-
214
- /**
215
- * Add timeout to any promise
216
- * @param promise - Promise to add timeout to
217
- * @param timeoutMs - Timeout in milliseconds
218
- * @param errorMessage - Custom error message for timeout
219
- * @returns Promise that rejects on timeout
220
- */
221
- static async withTimeout<T>(
222
- promise: Promise<T>,
223
- timeoutMs: number,
224
- errorMessage = 'Operation timed out'
225
- ): Promise<T> {
226
- let timeoutHandle: NodeJS.Timeout | undefined;
227
-
228
- const timeoutPromise = new Promise<never>((_, reject) => {
229
- timeoutHandle = setTimeout(() => {
230
- reject(new Error(errorMessage));
231
- }, timeoutMs);
232
- });
233
-
234
- try {
235
- return await Promise.race([promise, timeoutPromise]);
236
- } finally {
237
- if (timeoutHandle !== undefined) {
238
- clearTimeout(timeoutHandle);
239
- }
240
- }
241
- }
242
-
243
- /**
244
- * Execute multiple operations with Promise.allSettled for better error handling
245
- * Returns detailed results for each operation, including failures
246
- * @param operations - Array of async operations to execute
247
- * @returns Object with successful and failed operations separated
248
- */
249
- static async batchExecute<T>(
250
- operations: Array<() => Promise<T>>
251
- ): Promise<{
252
- successful: Array<{ index: number; value: T }>;
253
- failed: Array<{ index: number; reason: unknown }>;
254
- successCount: number;
255
- failureCount: number;
256
- }> {
257
- const results = await Promise.allSettled(operations.map(op => op()));
258
-
259
- const successful: Array<{ index: number; value: T }> = [];
260
- const failed: Array<{ index: number; reason: unknown }> = [];
261
-
262
- results.forEach((result, index) => {
263
- if (result.status === 'fulfilled') {
264
- successful.push({ index, value: result.value });
265
- } else {
266
- failed.push({ index, reason: result.reason });
267
- }
268
- });
269
-
270
- return {
271
- successful,
272
- failed,
273
- successCount: successful.length,
274
- failureCount: failed.length
275
- };
199
+ throw new Error('Max retries exceeded');
276
200
  }
277
201
  }
@@ -0,0 +1,86 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export async function readIniFile(filePath: string): Promise<Record<string, Record<string, string>>> {
5
+ try {
6
+ const content = await fs.readFile(filePath, 'utf-8');
7
+ const result: Record<string, Record<string, string>> = {};
8
+ let currentSection = '';
9
+
10
+ const lines = content.split(/\r?\n/);
11
+ for (const line of lines) {
12
+ const trimmed = line.trim();
13
+ if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) {
14
+ continue;
15
+ }
16
+
17
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
18
+ currentSection = trimmed.substring(1, trimmed.length - 1);
19
+ result[currentSection] = {};
20
+ } else if (currentSection) {
21
+ const parts = trimmed.split('=');
22
+ if (parts.length >= 2) {
23
+ const key = parts[0].trim();
24
+ const value = parts.slice(1).join('=').trim();
25
+ result[currentSection][key] = value;
26
+ }
27
+ }
28
+ }
29
+
30
+ return result;
31
+ } catch (error) {
32
+ throw new Error(`Failed to read INI file at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
33
+ }
34
+ }
35
+
36
+ export async function getProjectSetting(projectPath: string, category: string, sectionName: string, key?: string): Promise<any> {
37
+ // Normalize project path to directory
38
+ let dirPath = projectPath;
39
+ if (dirPath.toLowerCase().endsWith('.uproject')) {
40
+ dirPath = path.dirname(dirPath);
41
+ }
42
+
43
+ // Possible file names/locations in order of preference (Config/DefaultX.ini, Saved/Config/WindowsEditor/X.ini)
44
+ // category is usually 'Project', 'Engine', 'Game', 'Input', etc.
45
+ const cleanCategory = category.replace(/^Default/, ''); // If caller passed 'DefaultEngine', normalize to 'Engine'
46
+
47
+ const candidates = [
48
+ path.join(dirPath, 'Config', `Default${cleanCategory}.ini`),
49
+ path.join(dirPath, 'Saved', 'Config', 'WindowsEditor', `${cleanCategory}.ini`),
50
+ path.join(dirPath, 'Saved', 'Config', 'Windows', `${cleanCategory}.ini`),
51
+ path.join(dirPath, 'Saved', 'Config', 'Mac', `${cleanCategory}.ini`),
52
+ path.join(dirPath, 'Saved', 'Config', 'Linux', `${cleanCategory}.ini`)
53
+ ];
54
+
55
+ for (const configPath of candidates) {
56
+ try {
57
+ const iniData = await readIniFile(configPath);
58
+ // If we successfully read the file, check for the section
59
+ if (sectionName) {
60
+ const section = iniData[sectionName];
61
+ if (section) {
62
+ if (key) {
63
+ return section[key];
64
+ }
65
+ return section;
66
+ }
67
+ // If section not found in this file, continue to next candidate?
68
+ // Usually we want the most authoritative, but if it's missing the section, maybe it's in another?
69
+ // For now, if we find the file, we return the data from it or null if section missing.
70
+ // Merging is complex without a proper config hierarchy implementation.
71
+ // We will assume if the file exists, it's the one we want, or if section is missing, we fail for this file.
72
+ // But 'Default' might lack user overrides.
73
+ // Given this is a simple reader, we'll return the first match that contains the section,
74
+ // or if sectionName is empty, the first file found.
75
+ } else {
76
+ if (Object.keys(iniData).length > 0) {
77
+ return iniData;
78
+ }
79
+ }
80
+ } catch (_e) {
81
+ // Continue to next candidate
82
+ }
83
+ }
84
+
85
+ return null;
86
+ }
@@ -16,13 +16,18 @@ export class Logger {
16
16
  }
17
17
 
18
18
  debug(...args: any[]) {
19
- if (this.shouldLog('debug')) console.error(`[${this.scope}]`, ...args);
19
+ if (!this.shouldLog('debug')) return;
20
+ // Write to stderr to avoid corrupting MCP stdout stream
21
+ console.error(`[${this.scope}]`, ...args);
20
22
  }
21
23
  info(...args: any[]) {
22
- if (this.shouldLog('info')) console.error(`[${this.scope}]`, ...args);
24
+ if (!this.shouldLog('info')) return;
25
+ // Write to stderr to avoid corrupting MCP stdout stream
26
+ console.error(`[${this.scope}]`, ...args);
23
27
  }
24
28
  warn(...args: any[]) {
25
- if (this.shouldLog('warn')) console.error(`[${this.scope}]`, ...args);
29
+ if (!this.shouldLog('warn')) return;
30
+ console.warn(`[${this.scope}]`, ...args);
26
31
  }
27
32
  error(...args: any[]) {
28
33
  if (this.shouldLog('error')) console.error(`[${this.scope}]`, ...args);
@@ -62,3 +62,63 @@ export function toRotTuple(input: any): Rot3Tuple | null {
62
62
  return [pitch, yaw, roll];
63
63
  }
64
64
 
65
+ /**
66
+ * Parse a raw value into a finite number when possible.
67
+ * Accepts strings like "1.0" and returns number or undefined when invalid.
68
+ */
69
+ export function toFiniteNumber(raw: unknown): number | undefined {
70
+ if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
71
+ if (typeof raw === 'string') {
72
+ const trimmed = raw.trim();
73
+ if (trimmed.length === 0) return undefined;
74
+ const parsed = Number(trimmed);
75
+ if (Number.isFinite(parsed)) return parsed;
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ /**
81
+ * Normalize a partial vector input. Unlike toVec3Object, this accepts
82
+ * partial specifications and returns an object containing only present
83
+ * components (x/y/z) when any are provided; otherwise returns undefined.
84
+ */
85
+ export function normalizePartialVector(value: any, alternateKeys: string[] = ['x', 'y', 'z']): Record<string, number> | undefined {
86
+ if (value === undefined || value === null) return undefined;
87
+ const result: Record<string, number> = {};
88
+ const assignIfPresent = (component: 'x' | 'y' | 'z', raw: unknown) => {
89
+ const num = toFiniteNumber(raw);
90
+ if (num !== undefined) result[component] = num;
91
+ };
92
+
93
+ if (Array.isArray(value)) {
94
+ if (value.length > 0) assignIfPresent('x', value[0]);
95
+ if (value.length > 1) assignIfPresent('y', value[1]);
96
+ if (value.length > 2) assignIfPresent('z', value[2]);
97
+ } else if (typeof value === 'object') {
98
+ const obj = value as Record<string, unknown>;
99
+ assignIfPresent('x', obj.x ?? obj[alternateKeys[0]]);
100
+ assignIfPresent('y', obj.y ?? obj[alternateKeys[1]]);
101
+ assignIfPresent('z', obj.z ?? obj[alternateKeys[2]]);
102
+ } else {
103
+ assignIfPresent('x', value);
104
+ }
105
+
106
+ return Object.keys(result).length > 0 ? result : undefined;
107
+ }
108
+
109
+ /**
110
+ * Normalize a transform-like input into a minimal object containing
111
+ * location/rotation/scale partial descriptors when present.
112
+ */
113
+ export function normalizeTransformInput(transform: any): Record<string, unknown> | undefined {
114
+ if (!transform || typeof transform !== 'object') return undefined;
115
+ const result: Record<string, unknown> = {};
116
+ const location = normalizePartialVector(transform.location);
117
+ if (location) result.location = location;
118
+ const rotation = normalizePartialVector(transform.rotation, ['pitch', 'yaw', 'roll']);
119
+ if (rotation) result.rotation = rotation;
120
+ const scale = normalizePartialVector(transform.scale);
121
+ if (scale) result.scale = scale;
122
+ return Object.keys(result).length > 0 ? result : undefined;
123
+ }
124
+
@@ -0,0 +1,39 @@
1
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+
3
+ export class ResponseFactory {
4
+ static success(message: string, data?: unknown): CallToolResult {
5
+ const content: any[] = [{ type: 'text', text: message }];
6
+
7
+ if (data) {
8
+ content.push({
9
+ type: 'text',
10
+ text: JSON.stringify(data, null, 2)
11
+ });
12
+ }
13
+
14
+ return {
15
+ content,
16
+ isError: false
17
+ };
18
+ }
19
+
20
+ static error(message: string, error?: unknown): CallToolResult {
21
+ const errorText = error instanceof Error ? error.message : String(error);
22
+ const fullMessage = error ? `${message}: ${errorText}` : message;
23
+
24
+ return {
25
+ content: [{ type: 'text', text: fullMessage }],
26
+ isError: true
27
+ };
28
+ }
29
+
30
+ static json(data: unknown): CallToolResult {
31
+ return {
32
+ content: [{
33
+ type: 'text',
34
+ text: JSON.stringify(data, null, 2)
35
+ }],
36
+ isError: false
37
+ };
38
+ }
39
+ }