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,5 +1,13 @@
1
1
  import { BaseTool } from './base-tool.js';
2
- import { ILevelTools } from '../types/tool-interfaces.js';
2
+ import { ILevelTools, StandardActionResponse } from '../types/tool-interfaces.js';
3
+ import { LevelResponse } from '../types/automation-responses.js';
4
+ import { wasmIntegration as _wasmIntegration } from '../wasm/index.js';
5
+ import { sanitizePath } from '../utils/path-security.js';
6
+ import {
7
+ DEFAULT_OPERATION_TIMEOUT_MS,
8
+ DEFAULT_ASSET_OP_TIMEOUT_MS,
9
+ LONG_RUNNING_OP_TIMEOUT_MS
10
+ } from '../constants.js';
3
11
 
4
12
  type LevelExportRecord = { target: string; timestamp: number; note?: string };
5
13
  type ManagedLevelRecord = {
@@ -38,6 +46,18 @@ export class LevelTools extends BaseTool implements ILevelTools {
38
46
  if (!formatted.startsWith('/Game/')) {
39
47
  formatted = `/Game/${formatted.replace(/^\/+/, '')}`;
40
48
  }
49
+
50
+ // Security validation
51
+ try {
52
+ formatted = sanitizePath(formatted);
53
+ } catch (e: unknown) {
54
+ // If sanitizePath fails, we should probably propagate that error,
55
+ // but normalizeLevelPath signature expects to return an object.
56
+ // For now, let's log and rethrow or fallback?
57
+ // Throwing is safer as it prevents operation on invalid path.
58
+ throw new Error(`Security validation failed for level path: ${e instanceof Error ? e.message : String(e)}`);
59
+ }
60
+
41
61
  formatted = formatted.replace(/\.umap$/i, '');
42
62
  if (formatted.endsWith('/')) {
43
63
  formatted = formatted.slice(0, -1);
@@ -205,10 +225,10 @@ export class LevelTools extends BaseTool implements ILevelTools {
205
225
  this.ensureRecord(normalized.path, { loaded: true, visible: true });
206
226
  }
207
227
 
208
- async listLevels() {
228
+ async listLevels(): Promise<StandardActionResponse> {
209
229
  // Try to get actual levels from UE via automation bridge
210
230
  try {
211
- const response = await this.sendAutomationRequest('list_levels', {}, {
231
+ const response = await this.sendAutomationRequest<LevelResponse>('list_levels', {}, {
212
232
  timeoutMs: 10000
213
233
  });
214
234
 
@@ -222,6 +242,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
222
242
  const finalLevels = [...ueLevels, ...managedOnly];
223
243
 
224
244
  const result: Record<string, unknown> = {
245
+ ...response,
225
246
  success: true,
226
247
  message: 'Levels listed from Unreal Engine',
227
248
  levels: finalLevels,
@@ -232,12 +253,11 @@ export class LevelTools extends BaseTool implements ILevelTools {
232
253
  levels: finalLevels,
233
254
  count: finalLevels.length
234
255
  },
235
- ...response,
236
256
  managedLevels: managed.levels,
237
257
  managedLevelCount: managed.count
238
258
  };
239
259
 
240
- return result;
260
+ return result as StandardActionResponse;
241
261
  }
242
262
  } catch {
243
263
  // Fall back to managed levels if automation bridge fails
@@ -247,12 +267,12 @@ export class LevelTools extends BaseTool implements ILevelTools {
247
267
  return this.listManagedLevels();
248
268
  }
249
269
 
250
- async getLevelSummary(levelPath?: string) {
270
+ async getLevelSummary(levelPath?: string): Promise<StandardActionResponse> {
251
271
  const resolved = this.resolveLevelPath(levelPath);
252
272
  if (!resolved) {
253
273
  return { success: false, error: 'No level specified' };
254
274
  }
255
- return this.summarizeLevel(resolved);
275
+ return this.summarizeLevel(resolved) as StandardActionResponse;
256
276
  }
257
277
 
258
278
  registerLight(levelPath: string | undefined, info: { name: string; type: string; details?: Record<string, unknown> }) {
@@ -272,27 +292,27 @@ export class LevelTools extends BaseTool implements ILevelTools {
272
292
  });
273
293
  }
274
294
 
275
- async exportLevel(params: { levelPath?: string; exportPath: string; note?: string; timeoutMs?: number }) {
295
+ async exportLevel(params: { levelPath?: string; exportPath: string; note?: string; timeoutMs?: number }): Promise<StandardActionResponse> {
276
296
  const resolved = this.resolveLevelPath(params.levelPath);
277
297
  if (!resolved) {
278
298
  return { success: false, error: 'No level specified for export' };
279
299
  }
280
300
 
281
301
  try {
282
- const res = await this.sendAutomationRequest('manage_level', {
302
+ const res = await this.sendAutomationRequest<LevelResponse>('manage_level', {
283
303
  action: 'export_level',
284
304
  levelPath: resolved,
285
305
  exportPath: params.exportPath
286
- }, { timeoutMs: params.timeoutMs ?? 300000 });
306
+ }, { timeoutMs: params.timeoutMs ?? LONG_RUNNING_OP_TIMEOUT_MS });
287
307
 
288
- if ((res as any)?.success === false) {
308
+ if (res?.success === false) {
289
309
  return {
290
310
  success: false,
291
- error: (res as any).error || (res as any).message || 'Export failed',
311
+ error: res.error || res.message || 'Export failed',
292
312
  levelPath: resolved,
293
313
  exportPath: params.exportPath,
294
314
  details: res
295
- };
315
+ } as StandardActionResponse;
296
316
  }
297
317
 
298
318
  return {
@@ -301,23 +321,23 @@ export class LevelTools extends BaseTool implements ILevelTools {
301
321
  levelPath: resolved,
302
322
  exportPath: params.exportPath,
303
323
  details: res
304
- };
305
- } catch (e: any) {
306
- return { success: false, error: `Export failed: ${e.message}` };
324
+ } as StandardActionResponse;
325
+ } catch (e: unknown) {
326
+ return { success: false, error: `Export failed: ${e instanceof Error ? e.message : String(e)}` };
307
327
  }
308
328
  }
309
329
 
310
- async importLevel(params: { packagePath: string; destinationPath?: string; streaming?: boolean; timeoutMs?: number }) {
330
+ async importLevel(params: { packagePath: string; destinationPath?: string; streaming?: boolean; timeoutMs?: number }): Promise<StandardActionResponse> {
311
331
  const destination = params.destinationPath
312
332
  ? this.normalizeLevelPath(params.destinationPath)
313
333
  : this.normalizeLevelPath(`/Game/Maps/Imported_${Math.floor(Date.now() / 1000)}`);
314
334
 
315
335
  try {
316
- const res = await this.sendAutomationRequest('manage_level', {
336
+ const res = await this.sendAutomationRequest<LevelResponse>('manage_level', {
317
337
  action: 'import_level',
318
338
  packagePath: params.packagePath,
319
339
  destinationPath: destination.path
320
- }, { timeoutMs: params.timeoutMs ?? 300000 });
340
+ }, { timeoutMs: params.timeoutMs ?? LONG_RUNNING_OP_TIMEOUT_MS });
321
341
 
322
342
  if ((res as any)?.success === false) {
323
343
  return {
@@ -325,7 +345,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
325
345
  error: (res as any).error || (res as any).message || 'Import failed',
326
346
  levelPath: destination.path,
327
347
  details: res
328
- };
348
+ } as StandardActionResponse;
329
349
  }
330
350
 
331
351
  return {
@@ -335,23 +355,23 @@ export class LevelTools extends BaseTool implements ILevelTools {
335
355
  partitioned: true,
336
356
  streaming: Boolean(params.streaming),
337
357
  details: res
338
- };
339
- } catch (e: any) {
340
- return { success: false, error: `Import failed: ${e.message}` };
358
+ } as StandardActionResponse;
359
+ } catch (e: unknown) {
360
+ return { success: false, error: `Import failed: ${e instanceof Error ? e.message : String(e)}` };
341
361
  }
342
362
  }
343
363
 
344
- async saveLevelAs(params: { sourcePath?: string; targetPath: string }) {
364
+ async saveLevelAs(params: { sourcePath?: string; targetPath: string }): Promise<StandardActionResponse> {
345
365
  const source = this.resolveLevelPath(params.sourcePath);
346
366
  const target = this.normalizeLevelPath(params.targetPath);
347
367
 
348
368
  // Delegate to automation bridge
349
369
  try {
350
- const response = await this.sendAutomationRequest('manage_level', {
370
+ const response = await this.sendAutomationRequest<LevelResponse>('manage_level', {
351
371
  action: 'save_level_as',
352
372
  savePath: target.path
353
373
  }, {
354
- timeoutMs: 60000
374
+ timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
355
375
  });
356
376
 
357
377
  if (response.success === false) {
@@ -391,13 +411,13 @@ export class LevelTools extends BaseTool implements ILevelTools {
391
411
  success: true,
392
412
  message: response.message || `Level saved as ${target.path}`,
393
413
  levelPath: target.path
394
- };
414
+ } as StandardActionResponse;
395
415
  } catch (error) {
396
416
  return { success: false, error: `Failed to save level as: ${error instanceof Error ? error.message : String(error)}` };
397
417
  }
398
418
  }
399
419
 
400
- async deleteLevels(params: { levelPaths: string[] }) {
420
+ async deleteLevels(params: { levelPaths: string[] }): Promise<StandardActionResponse> {
401
421
  const removed: string[] = [];
402
422
  for (const path of params.levelPaths) {
403
423
  const normalized = this.normalizeLevelPath(path).path;
@@ -411,14 +431,14 @@ export class LevelTools extends BaseTool implements ILevelTools {
411
431
  success: true,
412
432
  message: removed.length ? `Deleted ${removed.length} managed level(s)` : 'No managed levels removed',
413
433
  removed
414
- };
434
+ } as StandardActionResponse;
415
435
  }
416
436
 
417
437
  async loadLevel(params: {
418
438
  levelPath: string;
419
439
  streaming?: boolean;
420
440
  position?: [number, number, number];
421
- }) {
441
+ }): Promise<StandardActionResponse> {
422
442
  const normalizedPath = this.normalizeLevelPath(params.levelPath).path;
423
443
 
424
444
  if (params.streaming) {
@@ -435,7 +455,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
435
455
  message: `Streaming level loaded: ${params.levelPath}`,
436
456
  levelPath: normalizedPath,
437
457
  streaming: true
438
- };
458
+ } as StandardActionResponse;
439
459
  } catch (err) {
440
460
  return {
441
461
  success: false,
@@ -446,10 +466,10 @@ export class LevelTools extends BaseTool implements ILevelTools {
446
466
  } else {
447
467
  // Try loading via automation bridge first (more robust)
448
468
  try {
449
- const response = await this.sendAutomationRequest('manage_level', {
469
+ const response = await this.sendAutomationRequest<LevelResponse>('manage_level', {
450
470
  action: 'load',
451
471
  levelPath: params.levelPath
452
- }, { timeoutMs: 30000 });
472
+ }, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
453
473
 
454
474
  if (response.success) {
455
475
  this.setCurrentLevel(normalizedPath);
@@ -459,12 +479,12 @@ export class LevelTools extends BaseTool implements ILevelTools {
459
479
  visible: true
460
480
  });
461
481
  return {
482
+ ...response,
462
483
  success: true,
463
484
  message: `Level loaded: ${params.levelPath}`,
464
485
  level: normalizedPath,
465
- streaming: false,
466
- ...response
467
- };
486
+ streaming: false
487
+ } as StandardActionResponse;
468
488
  }
469
489
  } catch (_e) {
470
490
  // Fallback to console logic
@@ -492,7 +512,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
492
512
  error: 'not_found',
493
513
  message,
494
514
  level: normalizedPath
495
- };
515
+ } as StandardActionResponse;
496
516
  }
497
517
  }
498
518
  } catch {
@@ -511,7 +531,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
511
531
  message: `Level loaded: ${params.levelPath}`,
512
532
  level: normalizedPath,
513
533
  streaming: false
514
- };
534
+ } as StandardActionResponse;
515
535
  } catch (err) {
516
536
  return {
517
537
  success: false,
@@ -525,7 +545,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
525
545
  async saveLevel(params: {
526
546
  levelName?: string;
527
547
  savePath?: string;
528
- }) {
548
+ }): Promise<StandardActionResponse> {
529
549
  try {
530
550
  if (params.savePath && !params.savePath.startsWith('/Game/')) {
531
551
  throw new Error(`Invalid save path: ${params.savePath}`);
@@ -537,8 +557,8 @@ export class LevelTools extends BaseTool implements ILevelTools {
537
557
  payload.savePath = params.savePath;
538
558
  }
539
559
 
540
- const response = await this.sendAutomationRequest('manage_level', payload, {
541
- timeoutMs: 60000
560
+ const response = await this.sendAutomationRequest<LevelResponse>('manage_level', payload, {
561
+ timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
542
562
  });
543
563
 
544
564
  if (response.success === false) {
@@ -546,9 +566,9 @@ export class LevelTools extends BaseTool implements ILevelTools {
546
566
  }
547
567
 
548
568
  const result: Record<string, unknown> = {
569
+ ...response,
549
570
  success: true,
550
- message: response.message || 'Level saved',
551
- ...response
571
+ message: response.message || 'Level saved'
552
572
  };
553
573
 
554
574
  if (response.skipped) {
@@ -564,7 +584,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
564
584
  result.details = response.details;
565
585
  }
566
586
 
567
- return result;
587
+ return result as StandardActionResponse;
568
588
  } catch (error) {
569
589
  return { success: false, error: `Failed to save level: ${error instanceof Error ? error.message : String(error)}` };
570
590
  }
@@ -574,17 +594,17 @@ export class LevelTools extends BaseTool implements ILevelTools {
574
594
  levelName: string;
575
595
  template?: 'Empty' | 'Default' | 'VR' | 'TimeOfDay';
576
596
  savePath?: string;
577
- }) {
597
+ }): Promise<StandardActionResponse> {
578
598
  const basePath = params.savePath || '/Game/Maps';
579
599
  const isPartitioned = true; // default to World Partition for UE5
580
600
  const fullPath = `${basePath}/${params.levelName}`;
581
601
 
582
602
  try {
583
- const response = await this.sendAutomationRequest('create_new_level', {
603
+ const response = await this.sendAutomationRequest<LevelResponse>('create_new_level', {
584
604
  levelPath: fullPath,
585
605
  useWorldPartition: isPartitioned
586
606
  }, {
587
- timeoutMs: 60000
607
+ timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
588
608
  });
589
609
 
590
610
  if (response.success === false) {
@@ -593,17 +613,17 @@ export class LevelTools extends BaseTool implements ILevelTools {
593
613
  error: response.error || response.message || 'Failed to create level',
594
614
  path: fullPath,
595
615
  partitioned: isPartitioned
596
- };
616
+ } as StandardActionResponse;
597
617
  }
598
618
 
599
619
  const result: Record<string, unknown> = {
620
+ ...response,
600
621
  success: true,
601
622
  message: response.message || 'Level created',
602
623
  path: response.levelPath || fullPath,
603
624
  packagePath: response.packagePath ?? fullPath,
604
625
  objectPath: response.objectPath,
605
- partitioned: isPartitioned,
606
- ...response
626
+ partitioned: isPartitioned
607
627
  };
608
628
 
609
629
  if (response.warnings) {
@@ -621,14 +641,14 @@ export class LevelTools extends BaseTool implements ILevelTools {
621
641
  createdAt: Date.now()
622
642
  });
623
643
 
624
- return result;
644
+ return result as StandardActionResponse;
625
645
  } catch (error) {
626
646
  return {
627
647
  success: false,
628
648
  error: `Failed to create level: ${error instanceof Error ? error.message : String(error)}`,
629
649
  path: fullPath,
630
650
  partitioned: isPartitioned
631
- };
651
+ } as StandardActionResponse;
632
652
  }
633
653
  }
634
654
 
@@ -636,7 +656,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
636
656
  parentLevel?: string;
637
657
  subLevelPath: string;
638
658
  streamingMethod?: 'Blueprint' | 'AlwaysLoaded';
639
- }) {
659
+ }): Promise<StandardActionResponse> {
640
660
  const parent = params.parentLevel ? this.resolveLevelPath(params.parentLevel) : this.currentLevelPath;
641
661
  const sub = this.normalizeLevelPath(params.subLevelPath).path;
642
662
 
@@ -652,35 +672,35 @@ export class LevelTools extends BaseTool implements ILevelTools {
652
672
 
653
673
  // Attempt automation first (cleaner)
654
674
  try {
655
- let response = await this.sendAutomationRequest('manage_level', {
675
+ let response = await this.sendAutomationRequest<LevelResponse>('manage_level', {
656
676
  action: 'add_sublevel',
657
677
  levelPath: sub, // Backwards compat
658
678
  subLevelPath: sub,
659
679
  parentPath: parent,
660
680
  streamingMethod: params.streamingMethod
661
- }, { timeoutMs: 30000 });
681
+ }, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
662
682
 
663
683
  // Retry with .umap if package not found (Workaround for C++ strictness)
664
684
  // Also retry if ADD_FAILED, as UEditorLevelUtils might have failed due to path resolution internally
665
685
  if (response && (response.error === 'PACKAGE_NOT_FOUND' || response.error === 'ADD_FAILED') && !sub.endsWith('.umap')) {
666
686
  const subWithExt = sub + '.umap';
667
- response = await this.sendAutomationRequest('manage_level', {
687
+ response = await this.sendAutomationRequest<LevelResponse>('manage_level', {
668
688
  action: 'add_sublevel',
669
689
  levelPath: subWithExt,
670
690
  subLevelPath: subWithExt,
671
691
  parentPath: parent,
672
692
  streamingMethod: params.streamingMethod
673
- }, { timeoutMs: 30000 });
693
+ }, { timeoutMs: DEFAULT_OPERATION_TIMEOUT_MS });
674
694
  }
675
695
 
676
696
  if (response.success) {
677
697
  this.ensureRecord(sub, { loaded: true, visible: true, streaming: true });
678
- return response;
698
+ return response as StandardActionResponse;
679
699
  } else if (response.error === 'UNKNOWN_ACTION') {
680
700
  // Fallthrough to console fallback if action not implemented
681
701
  } else {
682
702
  // Return actual error if it's something else (e.g. execution failed)
683
- return response;
703
+ return response as StandardActionResponse;
684
704
  }
685
705
  } catch (_e: any) {
686
706
  // If connection failed, might fallback. But if we got a response, respect it.
@@ -688,7 +708,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
688
708
 
689
709
  // Console fallback
690
710
  // Try using LevelEditor.AddLevel command which is available in Editor context
691
- const consoleResponse = await this.sendAutomationRequest('console_command', {
711
+ const consoleResponse = await this.sendAutomationRequest<LevelResponse>('console_command', {
692
712
  command: `LevelEditor.AddLevel ${sub}`
693
713
  });
694
714
 
@@ -698,7 +718,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
698
718
  success: true,
699
719
  message: `Sublevel added via console: ${sub}`,
700
720
  data: { method: 'console' }
701
- };
721
+ } as StandardActionResponse;
702
722
  }
703
723
 
704
724
  return {
@@ -707,7 +727,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
707
727
  // Return the last relevant error + console error
708
728
  message: 'Failed to add sublevel via automation or console.',
709
729
  details: { consoleError: consoleResponse }
710
- };
730
+ } as StandardActionResponse;
711
731
  }
712
732
 
713
733
  async streamLevel(params: {
@@ -716,7 +736,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
716
736
  shouldBeLoaded: boolean;
717
737
  shouldBeVisible?: boolean;
718
738
  position?: [number, number, number];
719
- }) {
739
+ }): Promise<StandardActionResponse> {
720
740
  const rawPath = typeof params.levelPath === 'string' ? params.levelPath.trim() : '';
721
741
  const levelPath = rawPath.length > 0 ? rawPath : undefined;
722
742
  const providedName = typeof params.levelName === 'string' ? params.levelName.trim() : '';
@@ -727,13 +747,13 @@ export class LevelTools extends BaseTool implements ILevelTools {
727
747
  const shouldBeVisible = params.shouldBeVisible ?? params.shouldBeLoaded;
728
748
 
729
749
  try {
730
- const response = await this.sendAutomationRequest('stream_level', {
750
+ const response = await this.sendAutomationRequest<LevelResponse>('stream_level', {
731
751
  levelPath: levelPath || '',
732
752
  levelName: levelName || '',
733
753
  shouldBeLoaded: params.shouldBeLoaded,
734
754
  shouldBeVisible
735
755
  }, {
736
- timeoutMs: 60000
756
+ timeoutMs: DEFAULT_ASSET_OP_TIMEOUT_MS
737
757
  });
738
758
 
739
759
  if (response.success === false) {
@@ -758,7 +778,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
758
778
  handledResult.details = response.details;
759
779
  }
760
780
 
761
- return handledResult;
781
+ return handledResult as StandardActionResponse;
762
782
  }
763
783
 
764
784
  return {
@@ -768,7 +788,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
768
788
  levelPath: levelPath,
769
789
  loaded: params.shouldBeLoaded,
770
790
  visible: shouldBeVisible
771
- };
791
+ } as StandardActionResponse;
772
792
  }
773
793
 
774
794
  const result: Record<string, unknown> = {
@@ -787,7 +807,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
787
807
  result.details = response.details;
788
808
  }
789
809
 
790
- return result;
810
+ return result as StandardActionResponse;
791
811
  } catch (_error) {
792
812
  // Fallback to console command
793
813
  const levelIdentifier = levelName ?? levelPath ?? '';
@@ -804,7 +824,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
804
824
  tileSize?: number;
805
825
  distanceStreaming?: boolean;
806
826
  streamingDistance?: number;
807
- }) {
827
+ }): Promise<StandardActionResponse> {
808
828
  const commands: string[] = [];
809
829
 
810
830
  if (params.enableComposition) {
@@ -832,7 +852,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
832
852
  position: [number, number];
833
853
  connections?: string[];
834
854
  }>;
835
- }) {
855
+ }): Promise<StandardActionResponse> {
836
856
  const command = `OpenLevelBlueprint ${params.eventType}`;
837
857
  return this.bridge.executeConsoleCommand(command);
838
858
  }
@@ -841,7 +861,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
841
861
  name: string;
842
862
  type: 'Persistent' | 'Streaming' | 'Lighting' | 'Gameplay';
843
863
  parent?: string;
844
- }) {
864
+ }): Promise<StandardActionResponse> {
845
865
  const command = `CreateSubLevel ${params.name} ${params.type} ${params.parent || 'None'}`;
846
866
  return this.bridge.executeConsoleCommand(command);
847
867
  }
@@ -852,7 +872,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
852
872
  gameMode?: string;
853
873
  defaultPawn?: string;
854
874
  killZ?: number;
855
- }) {
875
+ }): Promise<StandardActionResponse> {
856
876
  const commands: string[] = [];
857
877
 
858
878
  if (params.gravity !== undefined) {
@@ -879,7 +899,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
879
899
  async setLevelBounds(params: {
880
900
  min: [number, number, number];
881
901
  max: [number, number, number];
882
- }) {
902
+ }): Promise<StandardActionResponse> {
883
903
  const command = `SetLevelBounds ${params.min.join(',')} ${params.max.join(',')}`;
884
904
  return this.bridge.executeConsoleCommand(command);
885
905
  }
@@ -887,9 +907,9 @@ export class LevelTools extends BaseTool implements ILevelTools {
887
907
  async buildNavMesh(params: {
888
908
  rebuildAll?: boolean;
889
909
  selectedOnly?: boolean;
890
- }) {
910
+ }): Promise<StandardActionResponse> {
891
911
  try {
892
- const response = await this.sendAutomationRequest('build_navigation_mesh', {
912
+ const response = await this.sendAutomationRequest<LevelResponse>('build_navigation_mesh', {
893
913
  rebuildAll: params.rebuildAll ?? false,
894
914
  selectedOnly: params.selectedOnly ?? false
895
915
  }, {
@@ -924,7 +944,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
924
944
  result.details = response.details;
925
945
  }
926
946
 
927
- return result;
947
+ return result as StandardActionResponse;
928
948
  } catch (error) {
929
949
  return {
930
950
  success: false,
@@ -936,14 +956,14 @@ export class LevelTools extends BaseTool implements ILevelTools {
936
956
  async setLevelVisibility(params: {
937
957
  levelName: string;
938
958
  visible: boolean;
939
- }) {
959
+ }): Promise<StandardActionResponse> {
940
960
  const command = `SetLevelVisibility ${params.levelName} ${params.visible}`;
941
961
  return this.bridge.executeConsoleCommand(command);
942
962
  }
943
963
 
944
964
  async setWorldOrigin(params: {
945
965
  location: [number, number, number];
946
- }) {
966
+ }): Promise<StandardActionResponse> {
947
967
  const command = `SetWorldOriginLocation ${params.location.join(' ')}`;
948
968
  return this.bridge.executeConsoleCommand(command);
949
969
  }
@@ -953,7 +973,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
953
973
  position: [number, number, number];
954
974
  size: [number, number, number];
955
975
  streamingDistance?: number;
956
- }) {
976
+ }): Promise<StandardActionResponse> {
957
977
  const command = `CreateStreamingVolume ${params.levelName} ${params.position.join(' ')} ${params.size.join(' ')} ${params.streamingDistance || 0}`;
958
978
  return this.bridge.executeConsoleCommand(command);
959
979
  }
@@ -962,7 +982,7 @@ export class LevelTools extends BaseTool implements ILevelTools {
962
982
  levelName: string;
963
983
  lodLevel: number;
964
984
  distance: number;
965
- }) {
985
+ }): Promise<StandardActionResponse> {
966
986
  const command = `SetLevelLOD ${params.levelName} ${params.lodLevel} ${params.distance}`;
967
987
  return this.bridge.executeConsoleCommand(command);
968
988
  }