unreal-engine-mcp-server 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
@@ -0,0 +1,1050 @@
1
+ // Consolidated tool handlers - maps 10 tools to all 36 operations
2
+ import { handleToolCall } from './tool-handlers.js';
3
+ import { cleanObject } from '../utils/safe-json.js';
4
+ export async function handleConsolidatedToolCall(name, args, tools) {
5
+ const startTime = Date.now();
6
+ console.log(`[ConsolidatedToolHandler] Starting execution of ${name} at ${new Date().toISOString()}`);
7
+ try {
8
+ // Validate args is not null/undefined
9
+ if (args === null || args === undefined) {
10
+ throw new Error('Invalid arguments: null or undefined');
11
+ }
12
+ let mappedName;
13
+ let mappedArgs = { ...args };
14
+ switch (name) {
15
+ // 1. ASSET MANAGER
16
+ case 'manage_asset':
17
+ // Validate args is not null/undefined
18
+ if (args === null || args === undefined) {
19
+ throw new Error('Invalid arguments: null or undefined');
20
+ }
21
+ // Validate action exists
22
+ if (!args.action) {
23
+ throw new Error('Missing required parameter: action');
24
+ }
25
+ switch (args.action) {
26
+ case 'list':
27
+ // Directory is optional
28
+ if (args.directory !== undefined && args.directory !== null) {
29
+ if (typeof args.directory !== 'string') {
30
+ throw new Error('Invalid directory: must be a string');
31
+ }
32
+ }
33
+ mappedName = 'list_assets';
34
+ mappedArgs = {
35
+ directory: args.directory
36
+ // recursive removed - always false internally
37
+ };
38
+ break;
39
+ case 'import':
40
+ // Validate required parameters
41
+ if (args.sourcePath === undefined || args.sourcePath === null) {
42
+ throw new Error('Missing required parameter: sourcePath');
43
+ }
44
+ if (typeof args.sourcePath !== 'string') {
45
+ throw new Error('Invalid sourcePath: must be a string');
46
+ }
47
+ if (args.sourcePath.trim() === '') {
48
+ throw new Error('Invalid sourcePath: cannot be empty');
49
+ }
50
+ if (args.destinationPath === undefined || args.destinationPath === null) {
51
+ throw new Error('Missing required parameter: destinationPath');
52
+ }
53
+ if (typeof args.destinationPath !== 'string') {
54
+ throw new Error('Invalid destinationPath: must be a string');
55
+ }
56
+ if (args.destinationPath.trim() === '') {
57
+ throw new Error('Invalid destinationPath: cannot be empty');
58
+ }
59
+ mappedName = 'import_asset';
60
+ mappedArgs = {
61
+ sourcePath: args.sourcePath,
62
+ destinationPath: args.destinationPath
63
+ };
64
+ break;
65
+ case 'create_material':
66
+ // Validate required parameters
67
+ if (args.name === undefined || args.name === null) {
68
+ throw new Error('Missing required parameter: name');
69
+ }
70
+ if (typeof args.name !== 'string') {
71
+ throw new Error('Invalid name: must be a string');
72
+ }
73
+ if (args.name.trim() === '') {
74
+ throw new Error('Invalid name: cannot be empty');
75
+ }
76
+ // Path is optional
77
+ if (args.path !== undefined && args.path !== null) {
78
+ if (typeof args.path !== 'string') {
79
+ throw new Error('Invalid path: must be a string');
80
+ }
81
+ }
82
+ mappedName = 'create_material';
83
+ mappedArgs = {
84
+ name: args.name,
85
+ path: args.path
86
+ };
87
+ break;
88
+ default:
89
+ throw new Error(`Unknown asset action: ${args.action}`);
90
+ }
91
+ break;
92
+ // 2. ACTOR CONTROL
93
+ case 'control_actor':
94
+ // Validate action exists
95
+ if (!args.action) {
96
+ throw new Error('Missing required parameter: action');
97
+ }
98
+ switch (args.action) {
99
+ case 'spawn':
100
+ // Validate spawn parameters
101
+ if (!args.classPath) {
102
+ throw new Error('Missing required parameter: classPath');
103
+ }
104
+ if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
105
+ throw new Error('Invalid classPath: must be a non-empty string');
106
+ }
107
+ mappedName = 'spawn_actor';
108
+ mappedArgs = {
109
+ classPath: args.classPath,
110
+ location: args.location,
111
+ rotation: args.rotation
112
+ };
113
+ break;
114
+ case 'delete':
115
+ // Validate delete parameters
116
+ if (!args.actorName) {
117
+ throw new Error('Missing required parameter: actorName');
118
+ }
119
+ if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
120
+ throw new Error('Invalid actorName: must be a non-empty string');
121
+ }
122
+ mappedName = 'delete_actor';
123
+ mappedArgs = {
124
+ actorName: args.actorName
125
+ };
126
+ break;
127
+ case 'apply_force':
128
+ // Validate apply_force parameters
129
+ if (!args.actorName) {
130
+ throw new Error('Missing required parameter: actorName');
131
+ }
132
+ if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
133
+ throw new Error('Invalid actorName: must be a non-empty string');
134
+ }
135
+ if (!args.force) {
136
+ throw new Error('Missing required parameter: force');
137
+ }
138
+ if (typeof args.force !== 'object' || args.force === null) {
139
+ throw new Error('Invalid force: must be an object with x, y, z properties');
140
+ }
141
+ if (typeof args.force.x !== 'number' ||
142
+ typeof args.force.y !== 'number' ||
143
+ typeof args.force.z !== 'number') {
144
+ throw new Error('Invalid force: x, y, z must all be numbers');
145
+ }
146
+ mappedName = 'apply_force';
147
+ mappedArgs = {
148
+ actorName: args.actorName,
149
+ force: args.force
150
+ };
151
+ break;
152
+ default:
153
+ throw new Error(`Unknown actor action: ${args.action}`);
154
+ }
155
+ break;
156
+ // 3. EDITOR CONTROL
157
+ case 'control_editor':
158
+ // Validate action exists
159
+ if (!args.action) {
160
+ throw new Error('Missing required parameter: action');
161
+ }
162
+ switch (args.action) {
163
+ case 'play':
164
+ mappedName = 'play_in_editor';
165
+ mappedArgs = {};
166
+ break;
167
+ case 'stop':
168
+ mappedName = 'stop_play_in_editor';
169
+ mappedArgs = {};
170
+ break;
171
+ case 'pause':
172
+ mappedName = 'pause_play_in_editor';
173
+ mappedArgs = {};
174
+ break;
175
+ case 'set_game_speed':
176
+ // Validate game speed parameter
177
+ if (args.speed === undefined || args.speed === null) {
178
+ throw new Error('Missing required parameter: speed');
179
+ }
180
+ if (typeof args.speed !== 'number') {
181
+ throw new Error('Invalid speed: must be a number');
182
+ }
183
+ if (isNaN(args.speed)) {
184
+ throw new Error('Invalid speed: cannot be NaN');
185
+ }
186
+ if (!isFinite(args.speed)) {
187
+ throw new Error('Invalid speed: must be finite');
188
+ }
189
+ if (args.speed <= 0) {
190
+ throw new Error('Invalid speed: must be positive');
191
+ }
192
+ mappedName = 'set_game_speed';
193
+ mappedArgs = {
194
+ speed: args.speed
195
+ };
196
+ break;
197
+ case 'eject':
198
+ mappedName = 'eject_from_pawn';
199
+ mappedArgs = {};
200
+ break;
201
+ case 'possess':
202
+ mappedName = 'possess_pawn';
203
+ mappedArgs = {};
204
+ break;
205
+ case 'set_camera':
206
+ // Allow either location or rotation or both
207
+ // Don't require both to be present
208
+ mappedName = 'set_camera';
209
+ mappedArgs = {
210
+ location: args.location,
211
+ rotation: args.rotation
212
+ };
213
+ break;
214
+ case 'set_view_mode':
215
+ // Validate view mode parameter
216
+ if (!args.viewMode) {
217
+ throw new Error('Missing required parameter: viewMode');
218
+ }
219
+ if (typeof args.viewMode !== 'string' || args.viewMode.trim() === '') {
220
+ throw new Error('Invalid viewMode: must be a non-empty string');
221
+ }
222
+ // Normalize view mode to match what debug.ts expects
223
+ const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
224
+ 'light_complexity', 'shader_complexity', 'lightmap_density',
225
+ 'stationary_light_overlap', 'reflections', 'visualize_buffer',
226
+ 'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
227
+ const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
228
+ // Map to proper case for debug.ts
229
+ let mappedMode = '';
230
+ switch (normalizedMode) {
231
+ case 'lit':
232
+ mappedMode = 'Lit';
233
+ break;
234
+ case 'unlit':
235
+ mappedMode = 'Unlit';
236
+ break;
237
+ case 'wireframe':
238
+ mappedMode = 'Wireframe';
239
+ break;
240
+ case 'detaillighting':
241
+ mappedMode = 'DetailLighting';
242
+ break;
243
+ case 'lightingonly':
244
+ mappedMode = 'LightingOnly';
245
+ break;
246
+ case 'lightcomplexity':
247
+ mappedMode = 'LightComplexity';
248
+ break;
249
+ case 'shadercomplexity':
250
+ mappedMode = 'ShaderComplexity';
251
+ break;
252
+ case 'lightmapdensity':
253
+ mappedMode = 'LightmapDensity';
254
+ break;
255
+ case 'stationarylightoverlap':
256
+ mappedMode = 'StationaryLightOverlap';
257
+ break;
258
+ case 'reflections':
259
+ mappedMode = 'ReflectionOverride';
260
+ break;
261
+ case 'visualizebuffer':
262
+ mappedMode = 'VisualizeBuffer';
263
+ break;
264
+ case 'collisionpawn':
265
+ mappedMode = 'CollisionPawn';
266
+ break;
267
+ case 'collisionvisibility':
268
+ mappedMode = 'CollisionVisibility';
269
+ break;
270
+ case 'lodcoloration':
271
+ mappedMode = 'LODColoration';
272
+ break;
273
+ case 'quadoverdraw':
274
+ mappedMode = 'QuadOverdraw';
275
+ break;
276
+ default:
277
+ throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
278
+ }
279
+ mappedName = 'set_view_mode';
280
+ mappedArgs = {
281
+ mode: mappedMode
282
+ };
283
+ break;
284
+ default:
285
+ throw new Error(`Unknown editor action: ${args.action}`);
286
+ }
287
+ break;
288
+ // 4. LEVEL MANAGER
289
+ case 'manage_level':
290
+ switch (args.action) {
291
+ case 'load':
292
+ mappedName = 'load_level';
293
+ mappedArgs = {
294
+ levelPath: args.levelPath
295
+ };
296
+ if (args.streaming !== undefined) {
297
+ mappedArgs.streaming = args.streaming;
298
+ }
299
+ break;
300
+ case 'save':
301
+ mappedName = 'save_level';
302
+ mappedArgs = {
303
+ levelName: args.levelName
304
+ };
305
+ if (args.savePath !== undefined) {
306
+ mappedArgs.savePath = args.savePath;
307
+ }
308
+ break;
309
+ case 'stream':
310
+ mappedName = 'stream_level';
311
+ mappedArgs = {
312
+ levelName: args.levelName,
313
+ shouldBeLoaded: args.shouldBeLoaded,
314
+ shouldBeVisible: args.shouldBeVisible
315
+ };
316
+ break;
317
+ case 'create_light':
318
+ // Validate light type
319
+ if (!args.lightType) {
320
+ throw new Error('Missing required parameter: lightType');
321
+ }
322
+ const validLightTypes = ['directional', 'point', 'spot', 'rect', 'sky'];
323
+ const normalizedLightType = String(args.lightType).toLowerCase();
324
+ if (!validLightTypes.includes(normalizedLightType)) {
325
+ throw new Error(`Invalid lightType: '${args.lightType}'. Valid types are: ${validLightTypes.join(', ')}`);
326
+ }
327
+ // Validate name
328
+ if (!args.name) {
329
+ throw new Error('Missing required parameter: name');
330
+ }
331
+ if (typeof args.name !== 'string' || args.name.trim() === '') {
332
+ throw new Error('Invalid name: must be a non-empty string');
333
+ }
334
+ // Validate intensity if provided
335
+ if (args.intensity !== undefined) {
336
+ if (typeof args.intensity !== 'number' || !isFinite(args.intensity)) {
337
+ throw new Error(`Invalid intensity: must be a finite number, got ${typeof args.intensity}`);
338
+ }
339
+ if (args.intensity < 0) {
340
+ throw new Error('Invalid intensity: must be non-negative');
341
+ }
342
+ }
343
+ // Validate location if provided
344
+ if (args.location !== undefined && args.location !== null) {
345
+ if (!Array.isArray(args.location) && typeof args.location !== 'object') {
346
+ throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
347
+ }
348
+ }
349
+ mappedName = 'create_light';
350
+ mappedArgs = {
351
+ lightType: args.lightType,
352
+ name: args.name,
353
+ location: args.location,
354
+ intensity: args.intensity
355
+ };
356
+ break;
357
+ case 'build_lighting':
358
+ mappedName = 'build_lighting';
359
+ mappedArgs = {
360
+ quality: args.quality
361
+ };
362
+ break;
363
+ default:
364
+ throw new Error(`Unknown level action: ${args.action}`);
365
+ }
366
+ break;
367
+ // 5. ANIMATION & PHYSICS
368
+ case 'animation_physics':
369
+ // Validate action exists
370
+ if (!args.action) {
371
+ throw new Error('Missing required parameter: action');
372
+ }
373
+ switch (args.action) {
374
+ case 'create_animation_bp':
375
+ // Validate required parameters
376
+ if (args.name === undefined || args.name === null) {
377
+ throw new Error('Missing required parameter: name');
378
+ }
379
+ if (typeof args.name !== 'string') {
380
+ throw new Error('Invalid name: must be a string');
381
+ }
382
+ if (args.name.trim() === '') {
383
+ throw new Error('Invalid name: cannot be empty');
384
+ }
385
+ if (args.skeletonPath === undefined || args.skeletonPath === null) {
386
+ throw new Error('Missing required parameter: skeletonPath');
387
+ }
388
+ if (typeof args.skeletonPath !== 'string') {
389
+ throw new Error('Invalid skeletonPath: must be a string');
390
+ }
391
+ if (args.skeletonPath.trim() === '') {
392
+ throw new Error('Invalid skeletonPath: cannot be empty');
393
+ }
394
+ // Optional savePath validation
395
+ if (args.savePath !== undefined && args.savePath !== null) {
396
+ if (typeof args.savePath !== 'string') {
397
+ throw new Error('Invalid savePath: must be a string');
398
+ }
399
+ }
400
+ mappedName = 'create_animation_blueprint';
401
+ mappedArgs = {
402
+ name: args.name,
403
+ skeletonPath: args.skeletonPath,
404
+ savePath: args.savePath
405
+ };
406
+ break;
407
+ case 'play_montage':
408
+ // Validate required parameters
409
+ if (args.actorName === undefined || args.actorName === null) {
410
+ throw new Error('Missing required parameter: actorName');
411
+ }
412
+ if (typeof args.actorName !== 'string') {
413
+ throw new Error('Invalid actorName: must be a string');
414
+ }
415
+ if (args.actorName.trim() === '') {
416
+ throw new Error('Invalid actorName: cannot be empty');
417
+ }
418
+ // Check for montagePath or animationPath
419
+ const montagePath = args.montagePath || args.animationPath;
420
+ if (montagePath === undefined || montagePath === null) {
421
+ throw new Error('Missing required parameter: montagePath or animationPath');
422
+ }
423
+ if (typeof montagePath !== 'string') {
424
+ throw new Error('Invalid montagePath: must be a string');
425
+ }
426
+ if (montagePath.trim() === '') {
427
+ throw new Error('Invalid montagePath: cannot be empty');
428
+ }
429
+ // Optional playRate validation
430
+ if (args.playRate !== undefined && args.playRate !== null) {
431
+ if (typeof args.playRate !== 'number') {
432
+ throw new Error('Invalid playRate: must be a number');
433
+ }
434
+ if (isNaN(args.playRate)) {
435
+ throw new Error('Invalid playRate: cannot be NaN');
436
+ }
437
+ if (!isFinite(args.playRate)) {
438
+ throw new Error('Invalid playRate: must be finite');
439
+ }
440
+ }
441
+ mappedName = 'play_animation_montage';
442
+ mappedArgs = {
443
+ actorName: args.actorName,
444
+ montagePath: montagePath,
445
+ playRate: args.playRate
446
+ };
447
+ break;
448
+ case 'setup_ragdoll':
449
+ // Validate required parameters
450
+ if (args.skeletonPath === undefined || args.skeletonPath === null) {
451
+ throw new Error('Missing required parameter: skeletonPath');
452
+ }
453
+ if (typeof args.skeletonPath !== 'string') {
454
+ throw new Error('Invalid skeletonPath: must be a string');
455
+ }
456
+ if (args.skeletonPath.trim() === '') {
457
+ throw new Error('Invalid skeletonPath: cannot be empty');
458
+ }
459
+ if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
460
+ throw new Error('Missing required parameter: physicsAssetName');
461
+ }
462
+ if (typeof args.physicsAssetName !== 'string') {
463
+ throw new Error('Invalid physicsAssetName: must be a string');
464
+ }
465
+ if (args.physicsAssetName.trim() === '') {
466
+ throw new Error('Invalid physicsAssetName: cannot be empty');
467
+ }
468
+ // Optional blendWeight validation
469
+ if (args.blendWeight !== undefined && args.blendWeight !== null) {
470
+ if (typeof args.blendWeight !== 'number') {
471
+ throw new Error('Invalid blendWeight: must be a number');
472
+ }
473
+ if (isNaN(args.blendWeight)) {
474
+ throw new Error('Invalid blendWeight: cannot be NaN');
475
+ }
476
+ if (!isFinite(args.blendWeight)) {
477
+ throw new Error('Invalid blendWeight: must be finite');
478
+ }
479
+ }
480
+ // Optional savePath validation
481
+ if (args.savePath !== undefined && args.savePath !== null) {
482
+ if (typeof args.savePath !== 'string') {
483
+ throw new Error('Invalid savePath: must be a string');
484
+ }
485
+ }
486
+ mappedName = 'setup_ragdoll';
487
+ mappedArgs = {
488
+ skeletonPath: args.skeletonPath,
489
+ physicsAssetName: args.physicsAssetName,
490
+ blendWeight: args.blendWeight,
491
+ savePath: args.savePath
492
+ };
493
+ break;
494
+ default:
495
+ throw new Error(`Unknown animation/physics action: ${args.action}`);
496
+ }
497
+ break;
498
+ // 6. EFFECTS SYSTEM
499
+ case 'create_effect':
500
+ switch (args.action) {
501
+ case 'particle':
502
+ mappedName = 'create_particle_effect';
503
+ mappedArgs = {
504
+ effectType: args.effectType,
505
+ name: args.name,
506
+ location: args.location
507
+ };
508
+ break;
509
+ case 'niagara':
510
+ mappedName = 'spawn_niagara_system';
511
+ mappedArgs = {
512
+ systemPath: args.systemPath,
513
+ location: args.location,
514
+ scale: args.scale
515
+ };
516
+ break;
517
+ case 'debug_shape':
518
+ mappedName = 'draw_debug_shape';
519
+ // Convert location object to array for position
520
+ const pos = args.location || args.position;
521
+ mappedArgs = {
522
+ shape: args.shape,
523
+ position: pos ? [pos.x || 0, pos.y || 0, pos.z || 0] : [0, 0, 0],
524
+ size: args.size,
525
+ color: args.color,
526
+ duration: args.duration
527
+ };
528
+ break;
529
+ default:
530
+ throw new Error(`Unknown effect action: ${args.action}`);
531
+ }
532
+ break;
533
+ // 7. BLUEPRINT MANAGER
534
+ case 'manage_blueprint':
535
+ switch (args.action) {
536
+ case 'create':
537
+ mappedName = 'create_blueprint';
538
+ mappedArgs = {
539
+ name: args.name,
540
+ blueprintType: args.blueprintType,
541
+ savePath: args.savePath
542
+ };
543
+ break;
544
+ case 'add_component':
545
+ mappedName = 'add_blueprint_component';
546
+ mappedArgs = {
547
+ blueprintName: args.name,
548
+ componentType: args.componentType,
549
+ componentName: args.componentName
550
+ };
551
+ break;
552
+ default:
553
+ throw new Error(`Unknown blueprint action: ${args.action}`);
554
+ }
555
+ break;
556
+ // 8. ENVIRONMENT BUILDER
557
+ case 'build_environment':
558
+ switch (args.action) {
559
+ case 'create_landscape':
560
+ mappedName = 'create_landscape';
561
+ mappedArgs = {
562
+ name: args.name,
563
+ sizeX: args.sizeX,
564
+ sizeY: args.sizeY,
565
+ materialPath: args.materialPath
566
+ };
567
+ break;
568
+ case 'sculpt':
569
+ mappedName = 'sculpt_landscape';
570
+ mappedArgs = {
571
+ landscapeName: args.name,
572
+ tool: args.tool,
573
+ brushSize: args.brushSize,
574
+ strength: args.strength
575
+ };
576
+ break;
577
+ case 'add_foliage':
578
+ // Validate foliage creation parameters to avoid bad console commands / engine warnings
579
+ if (args.name === undefined || args.name === null || typeof args.name !== 'string' || args.name.trim() === '' || String(args.name).toLowerCase() === 'undefined' || String(args.name).toLowerCase() === 'any') {
580
+ throw new Error(`Invalid foliage name: '${args.name}'`);
581
+ }
582
+ if (args.meshPath === undefined || args.meshPath === null || typeof args.meshPath !== 'string' || args.meshPath.trim() === '' || String(args.meshPath).toLowerCase() === 'undefined') {
583
+ throw new Error(`Invalid meshPath: '${args.meshPath}'`);
584
+ }
585
+ if (args.density !== undefined) {
586
+ if (typeof args.density !== 'number' || !isFinite(args.density) || args.density < 0) {
587
+ throw new Error(`Invalid density: '${args.density}' (must be non-negative finite number)`);
588
+ }
589
+ }
590
+ mappedName = 'add_foliage_type';
591
+ mappedArgs = {
592
+ name: args.name,
593
+ meshPath: args.meshPath,
594
+ density: args.density
595
+ };
596
+ break;
597
+ case 'paint_foliage':
598
+ // Validate paint parameters
599
+ if (args.foliageType === undefined || args.foliageType === null || typeof args.foliageType !== 'string' || args.foliageType.trim() === '' || String(args.foliageType).toLowerCase() === 'undefined' || String(args.foliageType).toLowerCase() === 'any') {
600
+ throw new Error(`Invalid foliageType: '${args.foliageType}'`);
601
+ }
602
+ // Convert position object to array if needed
603
+ let positionArray;
604
+ if (args.position) {
605
+ if (Array.isArray(args.position)) {
606
+ positionArray = args.position;
607
+ }
608
+ else if (typeof args.position === 'object') {
609
+ positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
610
+ }
611
+ else {
612
+ positionArray = [0, 0, 0];
613
+ }
614
+ }
615
+ else {
616
+ positionArray = [0, 0, 0];
617
+ }
618
+ // Validate numbers in position
619
+ if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
620
+ throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
621
+ }
622
+ if (args.brushSize !== undefined) {
623
+ if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
624
+ throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
625
+ }
626
+ }
627
+ mappedName = 'paint_foliage';
628
+ mappedArgs = {
629
+ foliageType: args.foliageType,
630
+ position: positionArray,
631
+ brushSize: args.brushSize
632
+ };
633
+ break;
634
+ default:
635
+ throw new Error(`Unknown environment action: ${args.action}`);
636
+ }
637
+ break;
638
+ // 9. SYSTEM CONTROL
639
+ case 'system_control':
640
+ // Validate args is not null/undefined
641
+ if (args === null || args === undefined) {
642
+ throw new Error('Invalid arguments: null or undefined');
643
+ }
644
+ if (typeof args !== 'object') {
645
+ throw new Error('Invalid arguments: must be an object');
646
+ }
647
+ // Validate action exists
648
+ if (!args.action) {
649
+ throw new Error('Missing required parameter: action');
650
+ }
651
+ switch (args.action) {
652
+ case 'profile':
653
+ // Validate profile type
654
+ const validProfileTypes = ['CPU', 'GPU', 'Memory', 'RenderThread', 'GameThread', 'All'];
655
+ if (!args.profileType || !validProfileTypes.includes(args.profileType)) {
656
+ throw new Error(`Invalid profileType: '${args.profileType}'. Valid types: ${validProfileTypes.join(', ')}`);
657
+ }
658
+ mappedName = 'start_profiling';
659
+ mappedArgs = {
660
+ type: args.profileType,
661
+ duration: args.duration
662
+ };
663
+ break;
664
+ case 'show_fps':
665
+ // Validate enabled is boolean
666
+ if (args.enabled !== undefined && typeof args.enabled !== 'boolean') {
667
+ throw new Error(`Invalid enabled: must be boolean, got ${typeof args.enabled}`);
668
+ }
669
+ mappedName = 'show_fps';
670
+ mappedArgs = {
671
+ enabled: args.enabled,
672
+ verbose: args.verbose
673
+ };
674
+ break;
675
+ case 'set_quality':
676
+ // Validate category - normalize aliases and singular forms used by sg.*Quality
677
+ const validCategories = ['ViewDistance', 'AntiAliasing', 'PostProcessing', 'PostProcess',
678
+ 'Shadows', 'Shadow', 'GlobalIllumination', 'Reflections', 'Reflection', 'Textures', 'Texture',
679
+ 'Effects', 'Foliage', 'Shading'];
680
+ if (!args.category || !validCategories.includes(args.category)) {
681
+ throw new Error(`Invalid category: '${args.category}'. Valid categories: ${validCategories.join(', ')}`);
682
+ }
683
+ // Validate level
684
+ if (args.level === undefined || args.level === null) {
685
+ throw new Error('Missing required parameter: level');
686
+ }
687
+ if (typeof args.level !== 'number' || !Number.isInteger(args.level) || args.level < 0 || args.level > 4) {
688
+ throw new Error(`Invalid level: must be integer 0-4, got ${args.level}`);
689
+ }
690
+ // Normalize category to sg.<Base>Quality base (singular where needed)
691
+ const map = {
692
+ ViewDistance: 'ViewDistance',
693
+ AntiAliasing: 'AntiAliasing',
694
+ PostProcessing: 'PostProcess',
695
+ PostProcess: 'PostProcess',
696
+ Shadows: 'Shadow',
697
+ Shadow: 'Shadow',
698
+ GlobalIllumination: 'GlobalIllumination',
699
+ Reflections: 'Reflection',
700
+ Reflection: 'Reflection',
701
+ Textures: 'Texture',
702
+ Texture: 'Texture',
703
+ Effects: 'Effects',
704
+ Foliage: 'Foliage',
705
+ Shading: 'Shading',
706
+ };
707
+ const categoryName = map[String(args.category)] || args.category;
708
+ mappedName = 'set_scalability';
709
+ mappedArgs = {
710
+ category: categoryName,
711
+ level: args.level
712
+ };
713
+ break;
714
+ case 'play_sound':
715
+ // Validate sound path
716
+ if (!args.soundPath || typeof args.soundPath !== 'string') {
717
+ throw new Error('Invalid soundPath: must be a non-empty string');
718
+ }
719
+ // Validate volume if provided
720
+ if (args.volume !== undefined) {
721
+ if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
722
+ throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
723
+ }
724
+ }
725
+ mappedName = 'play_sound';
726
+ mappedArgs = {
727
+ soundPath: args.soundPath,
728
+ location: args.location,
729
+ volume: args.volume,
730
+ is3D: args.is3D
731
+ };
732
+ break;
733
+ case 'create_widget':
734
+ // Validate widget name
735
+ if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
736
+ throw new Error('Invalid widgetName: must be a non-empty string');
737
+ }
738
+ mappedName = 'create_widget';
739
+ mappedArgs = {
740
+ name: args.widgetName,
741
+ type: args.widgetType,
742
+ savePath: args.savePath
743
+ };
744
+ break;
745
+ case 'show_widget':
746
+ // Validate widget name
747
+ if (!args.widgetName || typeof args.widgetName !== 'string') {
748
+ throw new Error('Invalid widgetName: must be a non-empty string');
749
+ }
750
+ // Validate visible is boolean (default to true if not provided)
751
+ const isVisible = args.visible !== undefined ? args.visible : true;
752
+ if (typeof isVisible !== 'boolean') {
753
+ throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
754
+ }
755
+ mappedName = 'show_widget';
756
+ mappedArgs = {
757
+ widgetName: args.widgetName,
758
+ visible: isVisible
759
+ };
760
+ break;
761
+ case 'screenshot':
762
+ mappedName = 'take_screenshot';
763
+ mappedArgs = { resolution: args.resolution };
764
+ break;
765
+ case 'engine_start':
766
+ mappedName = 'launch_editor';
767
+ mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
768
+ break;
769
+ case 'engine_quit':
770
+ mappedName = 'quit_editor';
771
+ mappedArgs = {};
772
+ break;
773
+ default:
774
+ throw new Error(`Unknown system action: ${args.action}`);
775
+ }
776
+ break;
777
+ // 10. CONSOLE COMMAND - handle validation here
778
+ case 'console_command':
779
+ // Handle empty/invalid commands gracefully
780
+ if (args.command === undefined || args.command === null || args.command === '' || typeof args.command !== 'string') {
781
+ return {
782
+ content: [{
783
+ type: 'text',
784
+ text: 'Empty command ignored'
785
+ }],
786
+ isError: false,
787
+ success: true,
788
+ message: 'Empty command'
789
+ };
790
+ }
791
+ const cmdTrimmed = args.command.trim();
792
+ if (cmdTrimmed.length === 0) {
793
+ return {
794
+ content: [{
795
+ type: 'text',
796
+ text: 'Empty command ignored'
797
+ }],
798
+ isError: false,
799
+ success: true,
800
+ message: 'Empty command'
801
+ };
802
+ }
803
+ mappedName = 'console_command';
804
+ mappedArgs = args;
805
+ break;
806
+ // 11. REMOTE CONTROL PRESETS - Direct implementation
807
+ case 'manage_rc':
808
+ if (!args.action)
809
+ throw new Error('Missing required parameter: action');
810
+ // Handle RC operations directly through RcTools
811
+ let rcResult;
812
+ switch (args.action) {
813
+ // Support both 'create_preset' and 'create' for compatibility
814
+ case 'create_preset':
815
+ case 'create':
816
+ // Support both 'name' and 'presetName' parameter names
817
+ const presetName = args.name || args.presetName;
818
+ if (!presetName)
819
+ throw new Error('Missing required parameter: name or presetName');
820
+ rcResult = await tools.rcTools.createPreset({
821
+ name: presetName,
822
+ path: args.path
823
+ });
824
+ // Return consistent output with presetId for tests
825
+ if (rcResult.success) {
826
+ rcResult.message = `Remote Control preset created: ${presetName}`;
827
+ // Ensure presetId is set (for test compatibility)
828
+ if (rcResult.presetPath && !rcResult.presetId) {
829
+ rcResult.presetId = rcResult.presetPath;
830
+ }
831
+ }
832
+ break;
833
+ case 'list':
834
+ // List all presets - implement via RcTools
835
+ rcResult = await tools.rcTools.listPresets();
836
+ break;
837
+ case 'delete':
838
+ if (!args.presetId)
839
+ throw new Error('Missing required parameter: presetId');
840
+ rcResult = await tools.rcTools.deletePreset(args.presetId);
841
+ if (rcResult.success) {
842
+ rcResult.message = 'Preset deleted successfully';
843
+ }
844
+ break;
845
+ case 'expose_actor':
846
+ if (!args.presetPath)
847
+ throw new Error('Missing required parameter: presetPath');
848
+ if (!args.actorName)
849
+ throw new Error('Missing required parameter: actorName');
850
+ rcResult = await tools.rcTools.exposeActor({
851
+ presetPath: args.presetPath,
852
+ actorName: args.actorName
853
+ });
854
+ if (rcResult.success) {
855
+ rcResult.message = `Actor '${args.actorName}' exposed to preset`;
856
+ }
857
+ break;
858
+ case 'expose_property':
859
+ case 'expose': // Support simplified name from tests
860
+ // Support both presetPath and presetId
861
+ const presetPathExp = args.presetPath || args.presetId;
862
+ if (!presetPathExp)
863
+ throw new Error('Missing required parameter: presetPath or presetId');
864
+ if (!args.objectPath)
865
+ throw new Error('Missing required parameter: objectPath');
866
+ if (!args.propertyName)
867
+ throw new Error('Missing required parameter: propertyName');
868
+ rcResult = await tools.rcTools.exposeProperty({
869
+ presetPath: presetPathExp,
870
+ objectPath: args.objectPath,
871
+ propertyName: args.propertyName
872
+ });
873
+ if (rcResult.success) {
874
+ rcResult.message = `Property '${args.propertyName}' exposed to preset`;
875
+ }
876
+ break;
877
+ case 'list_fields':
878
+ case 'get_exposed': // Support test naming
879
+ const presetPathList = args.presetPath || args.presetId;
880
+ if (!presetPathList)
881
+ throw new Error('Missing required parameter: presetPath or presetId');
882
+ rcResult = await tools.rcTools.listFields({
883
+ presetPath: presetPathList
884
+ });
885
+ // Map 'fields' to 'exposedProperties' for test compatibility
886
+ if (rcResult.success && rcResult.fields) {
887
+ rcResult.exposedProperties = rcResult.fields;
888
+ }
889
+ break;
890
+ case 'set_property':
891
+ case 'set_value': // Support test naming
892
+ // Support both patterns
893
+ const objPathSet = args.objectPath || args.presetId;
894
+ const propNameSet = args.propertyName || args.propertyLabel;
895
+ if (!objPathSet)
896
+ throw new Error('Missing required parameter: objectPath or presetId');
897
+ if (!propNameSet)
898
+ throw new Error('Missing required parameter: propertyName or propertyLabel');
899
+ if (args.value === undefined)
900
+ throw new Error('Missing required parameter: value');
901
+ rcResult = await tools.rcTools.setProperty({
902
+ objectPath: objPathSet,
903
+ propertyName: propNameSet,
904
+ value: args.value
905
+ });
906
+ if (rcResult.success) {
907
+ rcResult.message = `Property '${propNameSet}' value updated`;
908
+ }
909
+ break;
910
+ case 'get_property':
911
+ case 'get_value': // Support test naming
912
+ const objPathGet = args.objectPath || args.presetId;
913
+ const propNameGet = args.propertyName || args.propertyLabel;
914
+ if (!objPathGet)
915
+ throw new Error('Missing required parameter: objectPath or presetId');
916
+ if (!propNameGet)
917
+ throw new Error('Missing required parameter: propertyName or propertyLabel');
918
+ rcResult = await tools.rcTools.getProperty({
919
+ objectPath: objPathGet,
920
+ propertyName: propNameGet
921
+ });
922
+ break;
923
+ case 'call_function':
924
+ if (!args.presetId)
925
+ throw new Error('Missing required parameter: presetId');
926
+ if (!args.functionLabel)
927
+ throw new Error('Missing required parameter: functionLabel');
928
+ // For now, return not implemented
929
+ rcResult = {
930
+ success: false,
931
+ error: 'Function calls not yet implemented'
932
+ };
933
+ break;
934
+ default:
935
+ throw new Error(`Unknown RC action: ${args.action}. Valid actions are: create_preset, expose_actor, expose_property, list_fields, set_property, get_property, or their simplified versions: create, list, delete, expose, get_exposed, set_value, get_value, call_function`);
936
+ }
937
+ // Return result directly - MCP formatting will be handled by response validator
938
+ // Clean to prevent circular references
939
+ return cleanObject(rcResult);
940
+ // 12. SEQUENCER / CINEMATICS
941
+ case 'manage_sequence':
942
+ if (!args.action)
943
+ throw new Error('Missing required parameter: action');
944
+ // Direct handling for sequence operations
945
+ const seqResult = await (async () => {
946
+ const sequenceTools = tools.sequenceTools;
947
+ if (!sequenceTools)
948
+ throw new Error('Sequence tools not available');
949
+ switch (args.action) {
950
+ case 'create':
951
+ return await sequenceTools.create({ name: args.name, path: args.path });
952
+ case 'open':
953
+ return await sequenceTools.open({ path: args.path });
954
+ case 'add_camera':
955
+ return await sequenceTools.addCamera({ spawnable: args.spawnable !== false });
956
+ case 'add_actor':
957
+ return await sequenceTools.addActor({ actorName: args.actorName });
958
+ case 'add_actors':
959
+ if (!args.actorNames)
960
+ throw new Error('Missing required parameter: actorNames');
961
+ return await sequenceTools.addActors({ actorNames: args.actorNames });
962
+ case 'remove_actors':
963
+ if (!args.actorNames)
964
+ throw new Error('Missing required parameter: actorNames');
965
+ return await sequenceTools.removeActors({ actorNames: args.actorNames });
966
+ case 'get_bindings':
967
+ return await sequenceTools.getBindings({ path: args.path });
968
+ case 'add_spawnable_from_class':
969
+ if (!args.className)
970
+ throw new Error('Missing required parameter: className');
971
+ return await sequenceTools.addSpawnableFromClass({ className: args.className, path: args.path });
972
+ case 'play':
973
+ return await sequenceTools.play({ loopMode: args.loopMode });
974
+ case 'pause':
975
+ return await sequenceTools.pause();
976
+ case 'stop':
977
+ return await sequenceTools.stop();
978
+ case 'set_properties':
979
+ return await sequenceTools.setSequenceProperties({
980
+ path: args.path,
981
+ frameRate: args.frameRate,
982
+ lengthInFrames: args.lengthInFrames,
983
+ playbackStart: args.playbackStart,
984
+ playbackEnd: args.playbackEnd
985
+ });
986
+ case 'get_properties':
987
+ return await sequenceTools.getSequenceProperties({ path: args.path });
988
+ case 'set_playback_speed':
989
+ if (args.speed === undefined)
990
+ throw new Error('Missing required parameter: speed');
991
+ return await sequenceTools.setPlaybackSpeed({ speed: args.speed });
992
+ default:
993
+ throw new Error(`Unknown sequence action: ${args.action}`);
994
+ }
995
+ })();
996
+ // Return result directly - MCP formatting will be handled by response validator
997
+ // Clean to prevent circular references
998
+ return cleanObject(seqResult);
999
+ // 13. INTROSPECTION
1000
+ case 'inspect':
1001
+ if (!args.action)
1002
+ throw new Error('Missing required parameter: action');
1003
+ switch (args.action) {
1004
+ case 'inspect_object':
1005
+ mappedName = 'inspect_object';
1006
+ mappedArgs = { objectPath: args.objectPath };
1007
+ break;
1008
+ case 'set_property':
1009
+ mappedName = 'inspect_set_property';
1010
+ mappedArgs = { objectPath: args.objectPath, propertyName: args.propertyName, value: args.value };
1011
+ break;
1012
+ default:
1013
+ throw new Error(`Unknown inspect action: ${args.action}`);
1014
+ }
1015
+ break;
1016
+ default:
1017
+ throw new Error(`Unknown consolidated tool: ${name}`);
1018
+ }
1019
+ // Call the original handler with mapped name and args with timeout
1020
+ const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
1021
+ const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
1022
+ const timeoutPromise = new Promise((_, reject) => {
1023
+ setTimeout(() => {
1024
+ reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
1025
+ }, TOOL_TIMEOUT);
1026
+ });
1027
+ const result = await Promise.race([toolPromise, timeoutPromise]);
1028
+ const duration = Date.now() - startTime;
1029
+ console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
1030
+ // Clean the result to prevent circular reference errors
1031
+ return cleanObject(result);
1032
+ }
1033
+ catch (err) {
1034
+ const duration = Date.now() - startTime;
1035
+ console.log(`[ConsolidatedToolHandler] Failed execution of ${name} after ${duration}ms: ${err?.message || String(err)}`);
1036
+ // Return consistent error structure matching regular tool handlers
1037
+ const errorMessage = err?.message || String(err);
1038
+ const isTimeout = errorMessage.includes('timeout');
1039
+ return {
1040
+ content: [{
1041
+ type: 'text',
1042
+ text: isTimeout
1043
+ ? `Tool ${name} timed out. Please check Unreal Engine connection.`
1044
+ : `Failed to execute ${name}: ${errorMessage}`
1045
+ }],
1046
+ isError: true
1047
+ };
1048
+ }
1049
+ }
1050
+ //# sourceMappingURL=consolidated-tool-handlers.js.map