unreal-engine-mcp-server 0.5.0 → 0.5.2

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 (188) hide show
  1. package/.env.example +1 -1
  2. package/.github/release-drafter-config.yml +51 -0
  3. package/.github/workflows/greetings.yml +5 -1
  4. package/.github/workflows/labeler.yml +2 -1
  5. package/.github/workflows/publish-mcp.yml +2 -4
  6. package/.github/workflows/release-drafter.yml +3 -2
  7. package/.github/workflows/release.yml +3 -3
  8. package/CHANGELOG.md +109 -0
  9. package/CONTRIBUTING.md +1 -1
  10. package/GEMINI.md +115 -0
  11. package/Public/Plugin_setup_guide.mp4 +0 -0
  12. package/README.md +166 -200
  13. package/dist/automation/bridge.d.ts +1 -2
  14. package/dist/automation/bridge.js +24 -23
  15. package/dist/automation/connection-manager.d.ts +1 -0
  16. package/dist/automation/connection-manager.js +10 -0
  17. package/dist/automation/message-handler.js +5 -4
  18. package/dist/automation/request-tracker.d.ts +4 -0
  19. package/dist/automation/request-tracker.js +11 -3
  20. package/dist/config.d.ts +0 -1
  21. package/dist/config.js +0 -1
  22. package/dist/constants.d.ts +4 -0
  23. package/dist/constants.js +4 -0
  24. package/dist/graphql/loaders.d.ts +64 -0
  25. package/dist/graphql/loaders.js +117 -0
  26. package/dist/graphql/resolvers.d.ts +3 -3
  27. package/dist/graphql/resolvers.js +33 -30
  28. package/dist/graphql/server.js +3 -1
  29. package/dist/graphql/types.d.ts +2 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +13 -2
  32. package/dist/server-setup.d.ts +0 -1
  33. package/dist/server-setup.js +0 -40
  34. package/dist/tools/actors.d.ts +58 -24
  35. package/dist/tools/actors.js +22 -6
  36. package/dist/tools/assets.d.ts +19 -71
  37. package/dist/tools/assets.js +28 -22
  38. package/dist/tools/base-tool.d.ts +4 -4
  39. package/dist/tools/base-tool.js +1 -1
  40. package/dist/tools/blueprint.d.ts +45 -61
  41. package/dist/tools/blueprint.js +43 -14
  42. package/dist/tools/consolidated-tool-definitions.js +2 -1
  43. package/dist/tools/consolidated-tool-handlers.js +96 -110
  44. package/dist/tools/dynamic-handler-registry.d.ts +11 -9
  45. package/dist/tools/dynamic-handler-registry.js +17 -95
  46. package/dist/tools/editor.d.ts +19 -193
  47. package/dist/tools/editor.js +11 -2
  48. package/dist/tools/environment.d.ts +8 -14
  49. package/dist/tools/foliage.d.ts +18 -143
  50. package/dist/tools/foliage.js +4 -2
  51. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  52. package/dist/tools/handlers/actor-handlers.js +14 -13
  53. package/dist/tools/handlers/asset-handlers.js +454 -454
  54. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  55. package/dist/tools/handlers/sequence-handlers.js +24 -13
  56. package/dist/tools/introspection.d.ts +1 -1
  57. package/dist/tools/introspection.js +1 -1
  58. package/dist/tools/landscape.d.ts +16 -116
  59. package/dist/tools/landscape.js +7 -3
  60. package/dist/tools/level.d.ts +22 -103
  61. package/dist/tools/level.js +26 -18
  62. package/dist/tools/lighting.d.ts +54 -7
  63. package/dist/tools/lighting.js +9 -5
  64. package/dist/tools/materials.d.ts +1 -1
  65. package/dist/tools/materials.js +5 -1
  66. package/dist/tools/niagara.js +37 -2
  67. package/dist/tools/performance.d.ts +0 -1
  68. package/dist/tools/performance.js +0 -1
  69. package/dist/tools/physics.js +5 -1
  70. package/dist/tools/sequence.d.ts +24 -24
  71. package/dist/tools/sequence.js +13 -0
  72. package/dist/tools/ui.d.ts +0 -2
  73. package/dist/types/automation-responses.d.ts +115 -0
  74. package/dist/types/automation-responses.js +2 -0
  75. package/dist/types/responses.d.ts +249 -0
  76. package/dist/types/responses.js +2 -0
  77. package/dist/types/tool-interfaces.d.ts +135 -135
  78. package/dist/types/tool-types.d.ts +2 -0
  79. package/dist/unreal-bridge.js +4 -4
  80. package/dist/utils/command-validator.js +7 -5
  81. package/dist/utils/error-handler.d.ts +24 -2
  82. package/dist/utils/error-handler.js +58 -23
  83. package/dist/utils/normalize.d.ts +7 -4
  84. package/dist/utils/normalize.js +12 -10
  85. package/dist/utils/path-security.d.ts +2 -0
  86. package/dist/utils/path-security.js +24 -0
  87. package/dist/utils/response-factory.d.ts +4 -4
  88. package/dist/utils/response-factory.js +15 -21
  89. package/dist/utils/response-validator.js +88 -73
  90. package/dist/utils/unreal-command-queue.d.ts +2 -0
  91. package/dist/utils/unreal-command-queue.js +8 -1
  92. package/docs/Migration-Guide-v0.5.0.md +1 -9
  93. package/docs/handler-mapping.md +4 -2
  94. package/docs/testing-guide.md +2 -2
  95. package/package.json +12 -6
  96. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  97. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  98. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  99. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  100. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  101. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  102. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  103. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  104. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  105. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  106. package/scripts/run-all-tests.mjs +25 -20
  107. package/server.json +3 -2
  108. package/src/automation/bridge.ts +27 -25
  109. package/src/automation/connection-manager.ts +18 -0
  110. package/src/automation/message-handler.ts +33 -8
  111. package/src/automation/request-tracker.ts +39 -7
  112. package/src/config.ts +1 -1
  113. package/src/constants.ts +7 -0
  114. package/src/graphql/loaders.ts +244 -0
  115. package/src/graphql/resolvers.ts +47 -49
  116. package/src/graphql/server.ts +3 -1
  117. package/src/graphql/types.ts +3 -0
  118. package/src/index.ts +15 -2
  119. package/src/resources/assets.ts +5 -4
  120. package/src/server/tool-registry.ts +3 -3
  121. package/src/server-setup.ts +3 -37
  122. package/src/tools/actors.ts +77 -44
  123. package/src/tools/animation.ts +1 -0
  124. package/src/tools/assets.ts +76 -65
  125. package/src/tools/base-tool.ts +3 -3
  126. package/src/tools/blueprint.ts +170 -104
  127. package/src/tools/consolidated-tool-definitions.ts +2 -1
  128. package/src/tools/consolidated-tool-handlers.ts +129 -150
  129. package/src/tools/dynamic-handler-registry.ts +22 -140
  130. package/src/tools/editor.ts +43 -29
  131. package/src/tools/environment.ts +21 -27
  132. package/src/tools/foliage.ts +28 -25
  133. package/src/tools/handlers/actor-handlers.ts +16 -17
  134. package/src/tools/handlers/asset-handlers.ts +484 -484
  135. package/src/tools/handlers/sequence-handlers.ts +85 -62
  136. package/src/tools/introspection.ts +7 -7
  137. package/src/tools/landscape.ts +34 -28
  138. package/src/tools/level.ts +100 -80
  139. package/src/tools/lighting.ts +25 -20
  140. package/src/tools/materials.ts +9 -3
  141. package/src/tools/niagara.ts +44 -2
  142. package/src/tools/performance.ts +1 -2
  143. package/src/tools/physics.ts +7 -1
  144. package/src/tools/sequence.ts +42 -26
  145. package/src/tools/ui.ts +1 -3
  146. package/src/types/automation-responses.ts +119 -0
  147. package/src/types/responses.ts +355 -0
  148. package/src/types/tool-interfaces.ts +135 -135
  149. package/src/types/tool-types.ts +4 -0
  150. package/src/unreal-bridge.ts +71 -26
  151. package/src/utils/command-validator.ts +47 -5
  152. package/src/utils/error-handler.ts +128 -45
  153. package/src/utils/normalize.test.ts +162 -0
  154. package/src/utils/normalize.ts +38 -16
  155. package/src/utils/path-security.ts +43 -0
  156. package/src/utils/response-factory.ts +29 -24
  157. package/src/utils/response-validator.ts +103 -87
  158. package/src/utils/safe-json.test.ts +90 -0
  159. package/src/utils/unreal-command-queue.ts +13 -1
  160. package/src/utils/validation.test.ts +184 -0
  161. package/tests/test-animation.mjs +358 -33
  162. package/tests/test-asset-graph.mjs +311 -0
  163. package/tests/test-audio.mjs +314 -116
  164. package/tests/test-behavior-tree.mjs +327 -144
  165. package/tests/test-blueprint-graph.mjs +343 -12
  166. package/tests/test-control-editor.mjs +85 -53
  167. package/tests/test-graphql.mjs +58 -8
  168. package/tests/test-input.mjs +349 -0
  169. package/tests/test-inspect.mjs +291 -61
  170. package/tests/test-landscape.mjs +304 -48
  171. package/tests/test-lighting.mjs +428 -0
  172. package/tests/test-manage-level.mjs +70 -51
  173. package/tests/test-performance.mjs +539 -0
  174. package/tests/test-sequence.mjs +82 -46
  175. package/tests/test-system.mjs +72 -33
  176. package/tests/test-wasm.mjs +98 -8
  177. package/vitest.config.ts +35 -0
  178. package/.github/release-drafter.yml +0 -148
  179. package/dist/prompts/index.d.ts +0 -21
  180. package/dist/prompts/index.js +0 -217
  181. package/dist/tools/blueprint/helpers.d.ts +0 -29
  182. package/dist/tools/blueprint/helpers.js +0 -182
  183. package/src/prompts/index.ts +0 -249
  184. package/src/tools/blueprint/helpers.ts +0 -189
  185. package/tests/test-blueprint-events.mjs +0 -35
  186. package/tests/test-extra-tools.mjs +0 -38
  187. package/tests/test-render.mjs +0 -33
  188. package/tests/test-search-assets.mjs +0 -66
@@ -1,6 +1,7 @@
1
1
  import { cleanObject } from '../utils/safe-json.js';
2
2
  import { Logger } from '../utils/logger.js';
3
3
  import { ResponseFactory } from '../utils/response-factory.js';
4
+ import { toolRegistry } from './dynamic-handler-registry.js';
4
5
  import { executeAutomationRequest, requireAction } from './handlers/common-handlers.js';
5
6
  import { handleAssetTools } from './handlers/asset-handlers.js';
6
7
  import { handleActorTools } from './handlers/actor-handlers.js';
@@ -19,8 +20,6 @@ import { handleAudioTools } from './handlers/audio-handlers.js';
19
20
  import { handleLightingTools } from './handlers/lighting-handlers.js';
20
21
  import { handlePerformanceTools } from './handlers/performance-handlers.js';
21
22
  import { handleInputTools } from './handlers/input-handlers.js';
22
- import { getDynamicHandlerForTool } from './dynamic-handler-registry.js';
23
- import { consolidatedToolDefinitions } from './consolidated-tool-definitions.js';
24
23
  const MATERIAL_GRAPH_ACTION_MAP = {
25
24
  add_material_node: 'add_node',
26
25
  connect_material_pins: 'connect_pins',
@@ -92,105 +91,103 @@ function normalizeToolCall(name, args) {
92
91
  args
93
92
  };
94
93
  }
95
- async function invokeNamedTool(name, action, args, tools) {
96
- switch (name) {
97
- case 'manage_asset': {
98
- if (['create_render_target', 'nanite_rebuild_mesh'].includes(action)) {
99
- const payload = { ...args, subAction: action };
100
- return cleanObject(await executeAutomationRequest(tools, 'manage_render', payload, `Automation bridge not available for ${action}`));
101
- }
102
- if (isMaterialGraphAction(action)) {
103
- const subAction = MATERIAL_GRAPH_ACTION_MAP[action] || action;
104
- return await handleGraphTools('manage_material_graph', subAction, args, tools);
105
- }
106
- if (isBehaviorTreeGraphAction(action)) {
107
- const subAction = BEHAVIOR_TREE_ACTION_MAP[action] || action;
108
- return await handleGraphTools('manage_behavior_tree', subAction, args, tools);
109
- }
110
- return await handleAssetTools(action, args, tools);
94
+ function registerDefaultHandlers() {
95
+ toolRegistry.register('manage_asset', async (args, tools) => {
96
+ const action = args.subAction || args.action || requireAction(args);
97
+ if (['create_render_target', 'nanite_rebuild_mesh'].includes(action)) {
98
+ const payload = { ...args, subAction: action };
99
+ return cleanObject(await executeAutomationRequest(tools, 'manage_render', payload, `Automation bridge not available for ${action}`));
111
100
  }
112
- case 'manage_blueprint': {
113
- if (action === 'get_blueprint') {
114
- return await handleBlueprintGet(args, tools);
115
- }
116
- const graphActions = ['create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property', 'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'];
117
- if (graphActions.includes(action)) {
118
- return await handleGraphTools('manage_blueprint_graph', action, args, tools);
119
- }
120
- return await handleBlueprintTools(action, args, tools);
101
+ if (isMaterialGraphAction(action)) {
102
+ const subAction = MATERIAL_GRAPH_ACTION_MAP[action] || action;
103
+ return await handleGraphTools('manage_material_graph', subAction, args, tools);
121
104
  }
122
- case 'control_actor':
123
- return await handleActorTools(action, args, tools);
124
- case 'control_editor': {
125
- if (action === 'simulate_input') {
126
- const payload = { ...args, subAction: action };
127
- return cleanObject(await executeAutomationRequest(tools, 'manage_ui', payload, 'Automation bridge not available'));
128
- }
129
- return await handleEditorTools(action, args, tools);
105
+ if (isBehaviorTreeGraphAction(action)) {
106
+ const subAction = BEHAVIOR_TREE_ACTION_MAP[action] || action;
107
+ return await handleGraphTools('manage_behavior_tree', subAction, args, tools);
130
108
  }
131
- case 'manage_level': {
132
- if (['load_cells', 'set_datalayer'].includes(action)) {
133
- const payload = { ...args, subAction: action };
134
- return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', payload, 'Automation bridge not available'));
135
- }
136
- return await handleLevelTools(action, args, tools);
109
+ return await handleAssetTools(action, args, tools);
110
+ });
111
+ toolRegistry.register('manage_blueprint', async (args, tools) => {
112
+ const action = args.action || requireAction(args);
113
+ if (action === 'get_blueprint') {
114
+ return await handleBlueprintGet(args, tools);
137
115
  }
138
- case 'animation_physics':
139
- return await handleAnimationTools(action, args, tools);
140
- case 'manage_effect': {
141
- if (isNiagaraGraphAction(action)) {
142
- const isInstanceOp = action === 'set_niagara_parameter' && (args.actorName || (args.systemName && !args.assetPath && !args.systemPath));
143
- if (isInstanceOp) {
144
- return await handleEffectTools(action, args, tools);
145
- }
146
- const subAction = NIAGARA_GRAPH_ACTION_MAP[action] || action;
147
- return await handleGraphTools('manage_niagara_graph', subAction, args, tools);
148
- }
149
- return await handleEffectTools(action, args, tools);
116
+ const graphActions = ['create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property', 'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'];
117
+ if (graphActions.includes(action)) {
118
+ return await handleGraphTools('manage_blueprint_graph', action, args, tools);
150
119
  }
151
- case 'build_environment':
152
- return await handleEnvironmentTools(action, args, tools);
153
- case 'system_control': {
154
- if (action === 'console_command')
155
- return await handleConsoleCommand(args, tools);
156
- if (action === 'run_ubt')
157
- return await handlePipelineTools(action, args, tools);
158
- if (action === 'run_tests')
159
- return cleanObject(await executeAutomationRequest(tools, 'manage_tests', { ...args, subAction: action }, 'Bridge unavailable'));
160
- if (action === 'subscribe' || action === 'unsubscribe')
161
- return cleanObject(await executeAutomationRequest(tools, 'manage_logs', { ...args, subAction: action }, 'Bridge unavailable'));
162
- if (action === 'spawn_category')
163
- return cleanObject(await executeAutomationRequest(tools, 'manage_debug', { ...args, subAction: action }, 'Bridge unavailable'));
164
- if (action === 'start_session')
165
- return cleanObject(await executeAutomationRequest(tools, 'manage_insights', { ...args, subAction: action }, 'Bridge unavailable'));
166
- if (action === 'lumen_update_scene')
167
- return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
168
- return await handleSystemTools(action, args, tools);
120
+ return await handleBlueprintTools(action, args, tools);
121
+ });
122
+ toolRegistry.register('control_actor', async (args, tools) => {
123
+ return await handleActorTools(args.action || requireAction(args), args, tools);
124
+ });
125
+ toolRegistry.register('control_editor', async (args, tools) => {
126
+ const action = args.action || requireAction(args);
127
+ if (action === 'simulate_input') {
128
+ const payload = { ...args, subAction: action };
129
+ return cleanObject(await executeAutomationRequest(tools, 'manage_ui', payload, 'Automation bridge not available'));
169
130
  }
170
- case 'manage_sequence':
171
- return await handleSequenceTools(action, args, tools);
172
- case 'inspect':
173
- return await handleInspectTools(action, args, tools);
174
- case 'manage_audio':
175
- return await handleAudioTools(action, args, tools);
176
- case 'manage_behavior_tree':
177
- return await handleGraphTools('manage_behavior_tree', action, args, tools);
178
- case 'manage_blueprint_graph':
179
- return await handleGraphTools('manage_blueprint_graph', action, args, tools);
180
- case 'manage_render':
131
+ return await handleEditorTools(action, args, tools);
132
+ });
133
+ toolRegistry.register('manage_level', async (args, tools) => {
134
+ const action = args.action || requireAction(args);
135
+ if (['load_cells', 'set_datalayer'].includes(action)) {
136
+ const payload = { ...args, subAction: action };
137
+ return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', payload, 'Automation bridge not available'));
138
+ }
139
+ return await handleLevelTools(action, args, tools);
140
+ });
141
+ toolRegistry.register('animation_physics', async (args, tools) => await handleAnimationTools(args.action || requireAction(args), args, tools));
142
+ toolRegistry.register('manage_effect', async (args, tools) => {
143
+ const action = args.action || requireAction(args);
144
+ if (isNiagaraGraphAction(action)) {
145
+ const isInstanceOp = action === 'set_niagara_parameter' && (args.actorName || (args.systemName && !args.assetPath && !args.systemPath));
146
+ if (isInstanceOp) {
147
+ return await handleEffectTools(action, args, tools);
148
+ }
149
+ const subAction = NIAGARA_GRAPH_ACTION_MAP[action] || action;
150
+ return await handleGraphTools('manage_niagara_graph', subAction, args, tools);
151
+ }
152
+ return await handleEffectTools(action, args, tools);
153
+ });
154
+ toolRegistry.register('build_environment', async (args, tools) => await handleEnvironmentTools(args.action || requireAction(args), args, tools));
155
+ toolRegistry.register('system_control', async (args, tools) => {
156
+ const action = args.action || requireAction(args);
157
+ if (action === 'console_command')
158
+ return await handleConsoleCommand(args, tools);
159
+ if (action === 'run_ubt')
160
+ return await handlePipelineTools(action, args, tools);
161
+ if (action === 'run_tests')
162
+ return cleanObject(await executeAutomationRequest(tools, 'manage_tests', { ...args, subAction: action }, 'Bridge unavailable'));
163
+ if (action === 'subscribe' || action === 'unsubscribe')
164
+ return cleanObject(await executeAutomationRequest(tools, 'manage_logs', { ...args, subAction: action }, 'Bridge unavailable'));
165
+ if (action === 'spawn_category')
166
+ return cleanObject(await executeAutomationRequest(tools, 'manage_debug', { ...args, subAction: action }, 'Bridge unavailable'));
167
+ if (action === 'start_session')
168
+ return cleanObject(await executeAutomationRequest(tools, 'manage_insights', { ...args, subAction: action }, 'Bridge unavailable'));
169
+ if (action === 'lumen_update_scene')
181
170
  return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
182
- case 'manage_world_partition':
183
- return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', { ...args, subAction: action }, 'Bridge unavailable'));
184
- case 'manage_lighting':
185
- return await handleLightingTools(action, args, tools);
186
- case 'manage_performance':
187
- return await handlePerformanceTools(action, args, tools);
188
- case 'manage_input':
189
- return await handleInputTools(action, args, tools);
190
- default:
191
- return cleanObject({ success: false, error: 'UNKNOWN_TOOL', message: `Unknown consolidated tool: ${name}` });
192
- }
171
+ return await handleSystemTools(action, args, tools);
172
+ });
173
+ toolRegistry.register('manage_sequence', async (args, tools) => await handleSequenceTools(args.action || requireAction(args), args, tools));
174
+ toolRegistry.register('inspect', async (args, tools) => await handleInspectTools(args.action || requireAction(args), args, tools));
175
+ toolRegistry.register('manage_audio', async (args, tools) => await handleAudioTools(args.action || requireAction(args), args, tools));
176
+ toolRegistry.register('manage_behavior_tree', async (args, tools) => await handleGraphTools('manage_behavior_tree', args.action || requireAction(args), args, tools));
177
+ toolRegistry.register('manage_blueprint_graph', async (args, tools) => await handleGraphTools('manage_blueprint_graph', args.action || requireAction(args), args, tools));
178
+ toolRegistry.register('manage_render', async (args, tools) => {
179
+ const action = args.action || requireAction(args);
180
+ return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
181
+ });
182
+ toolRegistry.register('manage_world_partition', async (args, tools) => {
183
+ const action = args.action || requireAction(args);
184
+ return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', { ...args, subAction: action }, 'Bridge unavailable'));
185
+ });
186
+ toolRegistry.register('manage_lighting', async (args, tools) => await handleLightingTools(args.action || requireAction(args), args, tools));
187
+ toolRegistry.register('manage_performance', async (args, tools) => await handlePerformanceTools(args.action || requireAction(args), args, tools));
188
+ toolRegistry.register('manage_input', async (args, tools) => await handleInputTools(args.action || requireAction(args), args, tools));
193
189
  }
190
+ registerDefaultHandlers();
194
191
  export async function handleConsolidatedToolCall(name, args, tools) {
195
192
  const logger = new Logger('ConsolidatedToolHandler');
196
193
  const startTime = Date.now();
@@ -198,26 +195,15 @@ export async function handleConsolidatedToolCall(name, args, tools) {
198
195
  try {
199
196
  const normalized = normalizeToolCall(name, args);
200
197
  const normalizedName = normalized.name;
201
- const action = normalized.action;
202
198
  const normalizedArgs = normalized.args;
203
- const toolDef = consolidatedToolDefinitions.find(t => t.name === normalizedName);
204
- const dynamicHandler = await getDynamicHandlerForTool(normalizedName, action);
205
- if (!dynamicHandler && toolDef && toolDef.inputSchema && toolDef.inputSchema.properties && toolDef.inputSchema.properties.action) {
206
- const actionProp = toolDef.inputSchema.properties.action;
207
- const allowed = Array.isArray(actionProp.enum) ? actionProp.enum : undefined;
208
- if (allowed && !allowed.includes(action)) {
209
- return cleanObject({
210
- success: false,
211
- error: 'UNKNOWN_ACTION',
212
- message: `Unknown action '${action}' for consolidated tool '${normalizedName}'.`
213
- });
214
- }
199
+ if (normalized.action && !normalizedArgs.action) {
200
+ normalizedArgs.action = normalized.action;
215
201
  }
216
- const defaultHandler = async () => invokeNamedTool(normalizedName, action, normalizedArgs, tools);
217
- if (dynamicHandler) {
218
- return await dynamicHandler({ name: normalizedName, action, args: normalizedArgs, tools, defaultHandler });
202
+ const handler = toolRegistry.getHandler(normalizedName);
203
+ if (handler) {
204
+ return await handler(normalizedArgs, tools);
219
205
  }
220
- return await defaultHandler();
206
+ return cleanObject({ success: false, error: 'UNKNOWN_TOOL', message: `Unknown consolidated tool: ${name}` });
221
207
  }
222
208
  catch (err) {
223
209
  const duration = Date.now() - startTime;
@@ -1,11 +1,13 @@
1
- import type { ITools } from '../types/tool-interfaces.js';
2
- export interface DynamicHandlerContext {
3
- name: string;
4
- action: string;
5
- args: any;
6
- tools: ITools;
7
- defaultHandler: () => Promise<any>;
1
+ import { ITools } from '../types/tool-interfaces.js';
2
+ type ToolHandler = (args: any, tools: ITools) => Promise<any>;
3
+ export declare class DynamicHandlerRegistry {
4
+ private handlers;
5
+ register(toolName: string, handler: ToolHandler): void;
6
+ getHandler(toolName: string): ToolHandler | undefined;
7
+ hasHandler(toolName: string): boolean;
8
+ removeHandler(toolName: string): boolean;
9
+ getAllRegisteredTools(): string[];
8
10
  }
9
- export type DynamicHandlerFn = (ctx: DynamicHandlerContext) => Promise<any>;
10
- export declare function getDynamicHandlerForTool(toolName: string, action?: string): Promise<DynamicHandlerFn | null>;
11
+ export declare const toolRegistry: DynamicHandlerRegistry;
12
+ export {};
11
13
  //# sourceMappingURL=dynamic-handler-registry.d.ts.map
@@ -1,101 +1,23 @@
1
- import { Logger } from '../utils/logger.js';
2
- import fs from 'fs/promises';
3
- import path from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
- const log = new Logger('DynamicHandlerRegistry');
6
- let registryLoaded = false;
7
- const loadedHandlers = [];
8
- async function loadRegistryIfNeeded() {
9
- if (registryLoaded)
10
- return;
11
- registryLoaded = true;
12
- const rootUrl = new URL('../..', import.meta.url);
13
- const envPath = (process.env.MCP_HANDLER_REGISTRY || '').trim();
14
- let configUrl = null;
15
- try {
16
- if (envPath) {
17
- if (envPath.startsWith('file:')) {
18
- configUrl = new URL(envPath);
19
- }
20
- else if (path.isAbsolute(envPath)) {
21
- configUrl = pathToFileURL(envPath);
22
- }
23
- else {
24
- configUrl = new URL(envPath, rootUrl);
25
- }
26
- }
27
- else {
28
- configUrl = new URL('handler-registry.json', rootUrl);
29
- }
30
- let jsonText;
31
- try {
32
- jsonText = await fs.readFile(configUrl, 'utf8');
33
- }
34
- catch (err) {
35
- if (err && (err.code === 'ENOENT' || err.code === 'ERR_MODULE_NOT_FOUND')) {
36
- log.debug(`Handler registry not found at ${configUrl.href}; dynamic handlers disabled.`);
37
- return;
38
- }
39
- log.warn(`Failed to read handler registry from ${configUrl.href}:`, err);
40
- return;
41
- }
42
- let raw;
43
- try {
44
- raw = JSON.parse(jsonText);
45
- }
46
- catch (err) {
47
- log.warn(`Invalid JSON in handler registry at ${configUrl.href}:`, err);
48
- return;
49
- }
50
- if (!raw.handlers || !Array.isArray(raw.handlers)) {
51
- log.debug('Handler registry contains no handlers.');
52
- return;
53
- }
54
- for (const entry of raw.handlers) {
55
- if (!entry || typeof entry.tool !== 'string' || typeof entry.module !== 'string') {
56
- continue;
57
- }
58
- try {
59
- const modUrl = new URL(entry.module, rootUrl);
60
- const mod = await import(modUrl.href);
61
- const exportName = entry.function && entry.function.trim().length > 0
62
- ? entry.function.trim()
63
- : 'default';
64
- const fnExport = mod[exportName];
65
- if (typeof fnExport !== 'function') {
66
- log.warn(`Dynamic handler module ${modUrl.href} for tool '${entry.tool}' does not export a callable '${exportName}'.`);
67
- continue;
68
- }
69
- const wrapped = fnExport;
70
- loadedHandlers.push({
71
- tool: entry.tool,
72
- actions: Array.isArray(entry.actions) ? entry.actions.slice() : undefined,
73
- fn: wrapped
74
- });
75
- log.info(`Registered dynamic handler for tool '${entry.tool}' from ${modUrl.href}`);
76
- }
77
- catch (err) {
78
- log.warn(`Failed to load dynamic handler for tool '${entry.tool}' from module spec '${entry.module}':`, err);
79
- }
1
+ export class DynamicHandlerRegistry {
2
+ handlers = new Map();
3
+ register(toolName, handler) {
4
+ if (this.handlers.has(toolName)) {
5
+ console.warn(`Handler for tool '${toolName}' is being overwritten.`);
80
6
  }
7
+ this.handlers.set(toolName, handler);
81
8
  }
82
- catch (err) {
83
- log.warn('Unexpected error while loading handler registry:', err);
9
+ getHandler(toolName) {
10
+ return this.handlers.get(toolName);
84
11
  }
85
- }
86
- export async function getDynamicHandlerForTool(toolName, action) {
87
- await loadRegistryIfNeeded();
88
- if (!loadedHandlers.length)
89
- return null;
90
- const name = toolName;
91
- const act = action;
92
- for (const handler of loadedHandlers) {
93
- if (handler.tool !== name)
94
- continue;
95
- if (handler.actions && act && !handler.actions.includes(act))
96
- continue;
97
- return handler.fn;
12
+ hasHandler(toolName) {
13
+ return this.handlers.has(toolName);
14
+ }
15
+ removeHandler(toolName) {
16
+ return this.handlers.delete(toolName);
17
+ }
18
+ getAllRegisteredTools() {
19
+ return Array.from(this.handlers.keys());
98
20
  }
99
- return null;
100
21
  }
22
+ export const toolRegistry = new DynamicHandlerRegistry();
101
23
  //# sourceMappingURL=dynamic-handler-registry.js.map
@@ -1,56 +1,16 @@
1
1
  import { BaseTool } from './base-tool.js';
2
- import { IEditorTools } from '../types/tool-interfaces.js';
2
+ import { IEditorTools, StandardActionResponse } from '../types/tool-interfaces.js';
3
3
  export declare class EditorTools extends BaseTool implements IEditorTools {
4
4
  private cameraBookmarks;
5
5
  private editorPreferences;
6
6
  private activeRecording?;
7
7
  isInPIE(): Promise<boolean>;
8
8
  ensureNotInPIE(): Promise<void>;
9
- playInEditor(timeoutMs?: number): Promise<{
10
- success: boolean;
11
- message: any;
12
- error?: undefined;
13
- } | {
14
- success: boolean;
15
- error: any;
16
- message?: undefined;
17
- }>;
18
- stopPlayInEditor(): Promise<{
19
- success: boolean;
20
- message: any;
21
- error?: undefined;
22
- } | {
23
- success: boolean;
24
- error: any;
25
- message?: undefined;
26
- }>;
27
- pausePlayInEditor(): Promise<{
28
- success: boolean;
29
- message: string;
30
- error?: undefined;
31
- } | {
32
- success: boolean;
33
- error: string;
34
- message?: undefined;
35
- }>;
36
- pauseInEditor(): Promise<{
37
- success: boolean;
38
- message: string;
39
- error?: undefined;
40
- } | {
41
- success: boolean;
42
- error: string;
43
- message?: undefined;
44
- }>;
45
- buildLighting(): Promise<{
46
- success: boolean;
47
- message: string;
48
- error?: undefined;
49
- } | {
50
- success: boolean;
51
- error: string;
52
- message?: undefined;
53
- }>;
9
+ playInEditor(timeoutMs?: number): Promise<StandardActionResponse>;
10
+ stopPlayInEditor(): Promise<StandardActionResponse>;
11
+ pausePlayInEditor(): Promise<StandardActionResponse>;
12
+ pauseInEditor(): Promise<StandardActionResponse>;
13
+ buildLighting(): Promise<StandardActionResponse>;
54
14
  private getViewportCameraInfo;
55
15
  setViewportCamera(location?: {
56
16
  x: number;
@@ -60,157 +20,23 @@ export declare class EditorTools extends BaseTool implements IEditorTools {
60
20
  pitch: number;
61
21
  yaw: number;
62
22
  roll: number;
63
- } | [number, number, number] | null | undefined): Promise<{
64
- success: boolean;
65
- message: any;
66
- location: [number, number, number] | {
67
- x: number;
68
- y: number;
69
- z: number;
70
- } | null | undefined;
71
- rotation: [number, number, number] | {
72
- pitch: number;
73
- yaw: number;
74
- roll: number;
75
- } | null | undefined;
76
- error?: undefined;
77
- } | {
78
- success: boolean;
79
- error: any;
80
- message?: undefined;
81
- location?: undefined;
82
- rotation?: undefined;
83
- }>;
84
- setCameraSpeed(speed: number): Promise<{
85
- success: boolean;
86
- message: string;
87
- error?: undefined;
88
- } | {
89
- success: boolean;
90
- error: string;
91
- message?: undefined;
92
- }>;
93
- setFOV(fov: number): Promise<{
94
- success: boolean;
95
- message: string;
96
- error?: undefined;
97
- } | {
98
- success: boolean;
99
- error: string;
100
- message?: undefined;
101
- }>;
102
- takeScreenshot(filename?: string, resolution?: string): Promise<{
103
- success: boolean;
104
- error: string;
105
- message?: undefined;
106
- filename?: undefined;
107
- command?: undefined;
108
- } | {
109
- success: boolean;
110
- message: string;
111
- filename: string;
112
- command: string;
113
- error?: undefined;
114
- }>;
115
- resumePlayInEditor(): Promise<{
116
- success: boolean;
117
- message: string;
118
- error?: undefined;
119
- } | {
120
- success: boolean;
121
- error: string;
122
- message?: undefined;
123
- }>;
124
- stepPIEFrame(steps?: number): Promise<{
125
- success: boolean;
126
- message: string;
127
- steps: number;
128
- error?: undefined;
129
- } | {
130
- success: boolean;
131
- error: string;
132
- message?: undefined;
133
- steps?: undefined;
134
- }>;
23
+ } | [number, number, number] | null | undefined): Promise<StandardActionResponse>;
24
+ setCameraSpeed(speed: number): Promise<StandardActionResponse>;
25
+ setFOV(fov: number): Promise<StandardActionResponse>;
26
+ takeScreenshot(filename?: string, resolution?: string): Promise<StandardActionResponse>;
27
+ resumePlayInEditor(): Promise<StandardActionResponse>;
28
+ stepPIEFrame(steps?: number): Promise<StandardActionResponse>;
135
29
  startRecording(options?: {
136
30
  filename?: string;
137
31
  frameRate?: number;
138
32
  durationSeconds?: number;
139
33
  metadata?: Record<string, unknown>;
140
- }): Promise<{
141
- success: true;
142
- message: string;
143
- recording: {
144
- name: string | undefined;
145
- startedAt: number;
146
- options: Record<string, unknown> | undefined;
147
- };
148
- }>;
149
- stopRecording(): Promise<{
150
- success: true;
151
- message: string;
152
- recording?: undefined;
153
- } | {
154
- success: true;
155
- message: string;
156
- recording: {
157
- name?: string;
158
- options?: Record<string, unknown>;
159
- startedAt: number;
160
- };
161
- }>;
162
- createCameraBookmark(name: string): Promise<{
163
- success: false;
164
- error: string;
165
- message?: undefined;
166
- bookmark?: undefined;
167
- } | {
168
- success: true;
169
- message: string;
170
- bookmark: {
171
- name: string;
172
- location: [number, number, number];
173
- rotation: [number, number, number];
174
- };
175
- error?: undefined;
176
- }>;
177
- jumpToCameraBookmark(name: string): Promise<{
178
- success: false;
179
- error: string;
180
- message?: undefined;
181
- } | {
182
- success: true;
183
- message: string;
184
- error?: undefined;
185
- }>;
186
- setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<{
187
- success: true;
188
- message: string;
189
- preferences: Record<string, unknown> | undefined;
190
- }>;
191
- setViewportResolution(width: number, height: number): Promise<{
192
- success: boolean;
193
- message: string;
194
- width: number;
195
- height: number;
196
- error?: undefined;
197
- } | {
198
- success: boolean;
199
- error: string;
200
- message?: undefined;
201
- width?: undefined;
202
- height?: undefined;
203
- }>;
204
- executeConsoleCommand(command: string): Promise<{
205
- success: boolean;
206
- error: string;
207
- message?: undefined;
208
- output?: undefined;
209
- } | {
210
- success: boolean;
211
- message: string;
212
- output: any;
213
- error?: undefined;
214
- }>;
34
+ }): Promise<StandardActionResponse>;
35
+ stopRecording(): Promise<StandardActionResponse>;
36
+ createCameraBookmark(name: string): Promise<StandardActionResponse>;
37
+ jumpToCameraBookmark(name: string): Promise<StandardActionResponse>;
38
+ setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<StandardActionResponse>;
39
+ setViewportResolution(width: number, height: number): Promise<StandardActionResponse>;
40
+ executeConsoleCommand(command: string): Promise<StandardActionResponse>;
215
41
  }
216
42
  //# sourceMappingURL=editor.d.ts.map
@@ -1,6 +1,7 @@
1
1
  import { BaseTool } from './base-tool.js';
2
2
  import { toVec3Object, toRotObject } from '../utils/normalize.js';
3
3
  import { DEFAULT_SCREENSHOT_RESOLUTION } from '../constants.js';
4
+ import { wasmIntegration } from '../wasm/index.js';
4
5
  export class EditorTools extends BaseTool {
5
6
  cameraBookmarks = new Map();
6
7
  editorPreferences = new Map();
@@ -33,8 +34,9 @@ export class EditorTools extends BaseTool {
33
34
  return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
34
35
  }
35
36
  catch (err) {
36
- if (err.message && /time.*out/i.test(err.message)) {
37
- return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
37
+ const errMsg = err instanceof Error ? err.message : String(err);
38
+ if (errMsg && /time.*out/i.test(errMsg)) {
39
+ return { success: false, error: `Timeout waiting for PIE to start: ${errMsg}` };
38
40
  }
39
41
  await this.bridge.executeConsoleCommand('t.MaxFPS 60');
40
42
  await this.bridge.executeConsoleCommand('PlayInViewport');
@@ -136,6 +138,13 @@ export class EditorTools extends BaseTool {
136
138
  rotation = rotObj;
137
139
  }
138
140
  try {
141
+ const locArray = location
142
+ ? [(location.x ?? location[0] ?? 0), (location.y ?? location[1] ?? 0), (location.z ?? location[2] ?? 0)]
143
+ : [0, 0, 0];
144
+ const rotArray = rotation
145
+ ? [(rotation.pitch ?? rotation[0] ?? 0), (rotation.yaw ?? rotation[1] ?? 0), (rotation.roll ?? rotation[2] ?? 0)]
146
+ : [0, 0, 0];
147
+ wasmIntegration.composeTransform(locArray, rotArray, [1, 1, 1]);
139
148
  const resp = await this.sendAutomationRequest('control_editor', {
140
149
  action: 'set_camera',
141
150
  location: location,