unreal-engine-mcp-server 0.3.1 → 0.4.3

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 (144) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +1 -1
  4. package/README.md +22 -7
  5. package/dist/index.js +137 -46
  6. package/dist/prompts/index.d.ts +10 -3
  7. package/dist/prompts/index.js +186 -7
  8. package/dist/resources/actors.d.ts +19 -1
  9. package/dist/resources/actors.js +55 -64
  10. package/dist/resources/assets.d.ts +3 -2
  11. package/dist/resources/assets.js +117 -109
  12. package/dist/resources/levels.d.ts +21 -3
  13. package/dist/resources/levels.js +31 -56
  14. package/dist/tools/actors.d.ts +3 -14
  15. package/dist/tools/actors.js +246 -302
  16. package/dist/tools/animation.d.ts +57 -102
  17. package/dist/tools/animation.js +429 -450
  18. package/dist/tools/assets.d.ts +13 -2
  19. package/dist/tools/assets.js +58 -46
  20. package/dist/tools/audio.d.ts +22 -13
  21. package/dist/tools/audio.js +467 -121
  22. package/dist/tools/blueprint.d.ts +32 -13
  23. package/dist/tools/blueprint.js +699 -448
  24. package/dist/tools/build_environment_advanced.d.ts +0 -1
  25. package/dist/tools/build_environment_advanced.js +236 -87
  26. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  27. package/dist/tools/consolidated-tool-definitions.js +124 -255
  28. package/dist/tools/consolidated-tool-handlers.js +749 -766
  29. package/dist/tools/debug.d.ts +72 -10
  30. package/dist/tools/debug.js +170 -36
  31. package/dist/tools/editor.d.ts +9 -2
  32. package/dist/tools/editor.js +30 -44
  33. package/dist/tools/foliage.d.ts +34 -15
  34. package/dist/tools/foliage.js +97 -107
  35. package/dist/tools/introspection.js +19 -21
  36. package/dist/tools/landscape.d.ts +1 -2
  37. package/dist/tools/landscape.js +311 -168
  38. package/dist/tools/level.d.ts +3 -28
  39. package/dist/tools/level.js +642 -192
  40. package/dist/tools/lighting.d.ts +14 -3
  41. package/dist/tools/lighting.js +236 -123
  42. package/dist/tools/materials.d.ts +25 -7
  43. package/dist/tools/materials.js +102 -79
  44. package/dist/tools/niagara.d.ts +10 -12
  45. package/dist/tools/niagara.js +74 -94
  46. package/dist/tools/performance.d.ts +12 -4
  47. package/dist/tools/performance.js +38 -79
  48. package/dist/tools/physics.d.ts +34 -10
  49. package/dist/tools/physics.js +364 -292
  50. package/dist/tools/rc.js +98 -24
  51. package/dist/tools/sequence.d.ts +1 -0
  52. package/dist/tools/sequence.js +146 -24
  53. package/dist/tools/ui.d.ts +31 -4
  54. package/dist/tools/ui.js +83 -66
  55. package/dist/tools/visual.d.ts +11 -0
  56. package/dist/tools/visual.js +245 -30
  57. package/dist/types/tool-types.d.ts +0 -6
  58. package/dist/types/tool-types.js +1 -8
  59. package/dist/unreal-bridge.d.ts +32 -2
  60. package/dist/unreal-bridge.js +621 -127
  61. package/dist/utils/elicitation.d.ts +57 -0
  62. package/dist/utils/elicitation.js +104 -0
  63. package/dist/utils/error-handler.d.ts +0 -33
  64. package/dist/utils/error-handler.js +4 -111
  65. package/dist/utils/http.d.ts +2 -22
  66. package/dist/utils/http.js +12 -75
  67. package/dist/utils/normalize.d.ts +4 -4
  68. package/dist/utils/normalize.js +15 -7
  69. package/dist/utils/python-output.d.ts +18 -0
  70. package/dist/utils/python-output.js +290 -0
  71. package/dist/utils/python.d.ts +2 -0
  72. package/dist/utils/python.js +4 -0
  73. package/dist/utils/response-validator.d.ts +6 -1
  74. package/dist/utils/response-validator.js +66 -13
  75. package/dist/utils/result-helpers.d.ts +27 -0
  76. package/dist/utils/result-helpers.js +147 -0
  77. package/dist/utils/safe-json.d.ts +0 -2
  78. package/dist/utils/safe-json.js +0 -43
  79. package/dist/utils/validation.d.ts +16 -0
  80. package/dist/utils/validation.js +70 -7
  81. package/mcp-config-example.json +2 -2
  82. package/package.json +11 -10
  83. package/server.json +37 -14
  84. package/src/index.ts +146 -50
  85. package/src/prompts/index.ts +211 -13
  86. package/src/resources/actors.ts +59 -44
  87. package/src/resources/assets.ts +123 -102
  88. package/src/resources/levels.ts +37 -47
  89. package/src/tools/actors.ts +269 -313
  90. package/src/tools/animation.ts +556 -539
  91. package/src/tools/assets.ts +59 -45
  92. package/src/tools/audio.ts +507 -113
  93. package/src/tools/blueprint.ts +778 -462
  94. package/src/tools/build_environment_advanced.ts +312 -106
  95. package/src/tools/consolidated-tool-definitions.ts +136 -267
  96. package/src/tools/consolidated-tool-handlers.ts +871 -795
  97. package/src/tools/debug.ts +179 -38
  98. package/src/tools/editor.ts +35 -37
  99. package/src/tools/foliage.ts +110 -104
  100. package/src/tools/introspection.ts +24 -22
  101. package/src/tools/landscape.ts +334 -181
  102. package/src/tools/level.ts +683 -182
  103. package/src/tools/lighting.ts +244 -123
  104. package/src/tools/materials.ts +114 -83
  105. package/src/tools/niagara.ts +87 -81
  106. package/src/tools/performance.ts +49 -88
  107. package/src/tools/physics.ts +393 -299
  108. package/src/tools/rc.ts +103 -25
  109. package/src/tools/sequence.ts +157 -30
  110. package/src/tools/ui.ts +101 -70
  111. package/src/tools/visual.ts +250 -29
  112. package/src/types/tool-types.ts +0 -9
  113. package/src/unreal-bridge.ts +658 -140
  114. package/src/utils/elicitation.ts +129 -0
  115. package/src/utils/error-handler.ts +4 -159
  116. package/src/utils/http.ts +16 -115
  117. package/src/utils/normalize.ts +20 -10
  118. package/src/utils/python-output.ts +351 -0
  119. package/src/utils/python.ts +3 -0
  120. package/src/utils/response-validator.ts +68 -17
  121. package/src/utils/result-helpers.ts +193 -0
  122. package/src/utils/safe-json.ts +0 -50
  123. package/src/utils/validation.ts +94 -7
  124. package/tests/run-unreal-tool-tests.mjs +720 -0
  125. package/tsconfig.json +2 -2
  126. package/dist/python-utils.d.ts +0 -29
  127. package/dist/python-utils.js +0 -54
  128. package/dist/tools/tool-definitions.d.ts +0 -4919
  129. package/dist/tools/tool-definitions.js +0 -1065
  130. package/dist/tools/tool-handlers.d.ts +0 -47
  131. package/dist/tools/tool-handlers.js +0 -863
  132. package/dist/types/index.d.ts +0 -323
  133. package/dist/types/index.js +0 -28
  134. package/dist/utils/cache-manager.d.ts +0 -64
  135. package/dist/utils/cache-manager.js +0 -176
  136. package/dist/utils/errors.d.ts +0 -133
  137. package/dist/utils/errors.js +0 -256
  138. package/src/python/editor_compat.py +0 -181
  139. package/src/python-utils.ts +0 -57
  140. package/src/tools/tool-definitions.ts +0 -1081
  141. package/src/tools/tool-handlers.ts +0 -973
  142. package/src/types/index.ts +0 -414
  143. package/src/utils/cache-manager.ts +0 -213
  144. package/src/utils/errors.ts +0 -312
@@ -1,29 +1,16 @@
1
1
  // Performance tools for Unreal Engine
2
2
  import { UnrealBridge } from '../unreal-bridge.js';
3
+ import { coerceBoolean, coerceNumber, interpretStandardResult } from '../utils/result-helpers.js';
3
4
 
4
5
  export class PerformanceTools {
5
6
  constructor(private bridge: UnrealBridge) {}
6
7
 
7
- // Execute console command
8
- private async _executeCommand(command: string) {
9
- return this.bridge.httpCall('/remote/object/call', 'PUT', {
10
- objectPath: '/Script/Engine.Default__KismetSystemLibrary',
11
- functionName: 'ExecuteConsoleCommand',
12
- parameters: {
13
- WorldContextObject: null,
14
- Command: command,
15
- SpecificPlayer: null
16
- },
17
- generateTransaction: false
18
- });
19
- }
20
-
21
8
  // Start profiling
22
9
  async startProfiling(params: {
23
10
  type: 'CPU' | 'GPU' | 'Memory' | 'RenderThread' | 'GameThread' | 'All';
24
11
  duration?: number;
25
12
  }) {
26
- const commands = [];
13
+ const commands: string[] = [];
27
14
 
28
15
  switch (params.type) {
29
16
  case 'CPU':
@@ -52,9 +39,7 @@ export class PerformanceTools {
52
39
  commands.push(`stat stopfile ${params.duration}`);
53
40
  }
54
41
 
55
- for (const cmd of commands) {
56
- await this.bridge.executeConsoleCommand(cmd);
57
- }
42
+ await this.bridge.executeConsoleCommands(commands);
58
43
 
59
44
  return { success: true, message: `${params.type} profiling started` };
60
45
  }
@@ -66,9 +51,7 @@ export class PerformanceTools {
66
51
  'stat none'
67
52
  ];
68
53
 
69
- for (const cmd of commands) {
70
- await this.bridge.executeConsoleCommand(cmd);
71
- }
54
+ await this.bridge.executeConsoleCommands(commands);
72
55
 
73
56
  return { success: true, message: 'Profiling stopped' };
74
57
  }
@@ -138,12 +121,19 @@ export class PerformanceTools {
138
121
  Foliage: 'Foliage',
139
122
  Shading: 'Shading',
140
123
  };
124
+ const requestedLevel = Number(params.level);
125
+ if (!Number.isInteger(requestedLevel) || requestedLevel < 0 || requestedLevel > 4) {
126
+ return {
127
+ success: false,
128
+ error: 'Invalid scalability level. Expected integer between 0 and 4.'
129
+ };
130
+ }
141
131
 
142
132
  const base = categoryBaseMap[params.category] || params.category;
143
133
 
144
134
  // Use direct console command to set with highest priority (SetByConsole)
145
135
  // This avoids conflicts with the scalability system
146
- const setCommand = `sg.${base}Quality ${params.level}`;
136
+ const setCommand = `sg.${base}Quality ${requestedLevel}`;
147
137
 
148
138
  // Apply the console command directly
149
139
  await this.bridge.executeConsoleCommand(setCommand);
@@ -153,7 +143,7 @@ export class PerformanceTools {
153
143
  /* eslint-disable no-useless-escape */
154
144
  const py = `
155
145
  import unreal, json
156
- result = {'success': True, 'category': '${base}', 'requested': ${params.level}, 'actual': ${params.level}, 'method': 'ConsoleOnly'}
146
+ result = {'success': True, 'category': '${base}', 'requested': ${requestedLevel}, 'actual': ${requestedLevel}, 'method': 'ConsoleOnly'}
157
147
 
158
148
  # Simply verify the console variable was set correctly
159
149
  try:
@@ -198,30 +188,21 @@ print('RESULT:' + json.dumps(result))
198
188
  // Always try to apply through Python for consistency
199
189
  try {
200
190
  const pyResp = await this.bridge.executePython(py);
201
- let out = '';
202
- if (pyResp?.LogOutput && Array.isArray(pyResp.LogOutput)) {
203
- out = pyResp.LogOutput.map((l: any) => l.Output || '').join('');
204
- } else if (typeof pyResp === 'string') {
205
- out = pyResp;
206
- } else {
207
- out = JSON.stringify(pyResp);
208
- }
209
-
210
- const m = out.match(/RESULT:({.*})/);
211
- if (m) {
212
- try {
213
- const parsed = JSON.parse(m[1]);
214
- const verified = parsed.success && (parsed.actual === params.level);
215
- return {
216
- success: true,
217
- message: `${params.category} quality set to level ${params.level}`,
218
- verified,
219
- readback: parsed.actual,
220
- method: parsed.method || 'Unknown'
221
- };
222
- } catch {
223
- // Fall through to simple success
224
- }
191
+ const interpreted = interpretStandardResult(pyResp, {
192
+ successMessage: `${params.category} quality set to level ${requestedLevel}`,
193
+ failureMessage: `Failed to set ${params.category} quality`
194
+ });
195
+
196
+ if (interpreted.success) {
197
+ const actual = coerceNumber(interpreted.payload.actual) ?? requestedLevel;
198
+ const verified = coerceBoolean(interpreted.payload.success, true) === true && actual === requestedLevel;
199
+ return {
200
+ success: true,
201
+ message: interpreted.message,
202
+ verified,
203
+ readback: actual,
204
+ method: (interpreted.payload.method as string) || 'ConsoleOnly'
205
+ };
225
206
  }
226
207
  } catch {
227
208
  // Ignore Python errors and fall through
@@ -230,7 +211,7 @@ print('RESULT:' + json.dumps(result))
230
211
  // If Python fails, the console command was still applied
231
212
  return {
232
213
  success: true,
233
- message: `${params.category} quality set to level ${params.level}`,
214
+ message: `${params.category} quality set to level ${requestedLevel}`,
234
215
  method: 'CVarOnly'
235
216
  };
236
217
  }
@@ -295,7 +276,7 @@ print('RESULT:' + json.dumps(result))
295
276
  detailed?: boolean;
296
277
  outputPath?: string;
297
278
  }) {
298
- const commands = [];
279
+ const commands: string[] = [];
299
280
 
300
281
  if (params.detailed) {
301
282
  commands.push('memreport -full');
@@ -307,9 +288,7 @@ print('RESULT:' + json.dumps(result))
307
288
  commands.push(`obj savepackage ${params.outputPath}`);
308
289
  }
309
290
 
310
- for (const cmd of commands) {
311
- await this.bridge.executeConsoleCommand(cmd);
312
- }
291
+ await this.bridge.executeConsoleCommands(commands);
313
292
 
314
293
  return { success: true, message: 'Memory report generated' };
315
294
  }
@@ -320,7 +299,7 @@ print('RESULT:' + json.dumps(result))
320
299
  poolSize?: number; // MB
321
300
  boostPlayerLocation?: boolean;
322
301
  }) {
323
- const commands = [];
302
+ const commands: string[] = [];
324
303
 
325
304
  commands.push(`r.TextureStreaming ${params.enabled ? 1 : 0}`);
326
305
 
@@ -332,9 +311,7 @@ print('RESULT:' + json.dumps(result))
332
311
  commands.push(`r.Streaming.UseFixedPoolSize ${params.boostPlayerLocation ? 1 : 0}`);
333
312
  }
334
313
 
335
- for (const cmd of commands) {
336
- await this.bridge.executeConsoleCommand(cmd);
337
- }
314
+ await this.bridge.executeConsoleCommands(commands);
338
315
 
339
316
  return { success: true, message: 'Texture streaming configured' };
340
317
  }
@@ -345,7 +322,7 @@ print('RESULT:' + json.dumps(result))
345
322
  lodBias?: number; // skeletal LOD bias (int)
346
323
  distanceScale?: number; // distance scale (float) applied to both static and skeletal
347
324
  }) {
348
- const commands = [];
325
+ const commands: string[] = [];
349
326
 
350
327
  if (params.forceLOD !== undefined) {
351
328
  commands.push(`r.ForceLOD ${params.forceLOD}`);
@@ -362,9 +339,7 @@ print('RESULT:' + json.dumps(result))
362
339
  commands.push(`r.SkeletalMeshLODDistanceScale ${params.distanceScale}`);
363
340
  }
364
341
 
365
- for (const cmd of commands) {
366
- await this.bridge.executeConsoleCommand(cmd);
367
- }
342
+ await this.bridge.executeConsoleCommands(commands);
368
343
 
369
344
  return { success: true, message: 'LOD settings configured' };
370
345
  }
@@ -394,9 +369,7 @@ print('RESULT:' + json.dumps(result))
394
369
  `t.MaxFPS ${p.maxFPS}`,
395
370
  ];
396
371
 
397
- for (const cmd of commands) {
398
- await this.bridge.executeConsoleCommand(cmd);
399
- }
372
+ await this.bridge.executeConsoleCommands(commands);
400
373
 
401
374
  return { success: true, message: 'Baseline performance settings applied', params: p };
402
375
  }
@@ -407,7 +380,7 @@ print('RESULT:' + json.dumps(result))
407
380
  enableBatching?: boolean; // no-op (deprecated internal toggle)
408
381
  mergeActors?: boolean;
409
382
  }) {
410
- const commands = [];
383
+ const commands: string[] = [];
411
384
 
412
385
  if (params.enableInstancing !== undefined) {
413
386
  commands.push(`r.MeshDrawCommands.DynamicInstancing ${params.enableInstancing ? 1 : 0}`);
@@ -419,9 +392,7 @@ print('RESULT:' + json.dumps(result))
419
392
  commands.push('MergeActors');
420
393
  }
421
394
 
422
- for (const cmd of commands) {
423
- await this.bridge.executeConsoleCommand(cmd);
424
- }
395
+ await this.bridge.executeConsoleCommands(commands);
425
396
 
426
397
  return { success: true, message: 'Draw call optimization configured' };
427
398
  }
@@ -432,7 +403,7 @@ print('RESULT:' + json.dumps(result))
432
403
  method?: 'Hardware' | 'Software' | 'Hierarchical';
433
404
  freezeRendering?: boolean;
434
405
  }) {
435
- const commands = [];
406
+ const commands: string[] = [];
436
407
 
437
408
  // Enable/disable HZB occlusion (boolean)
438
409
  commands.push(`r.HZBOcclusion ${params.enabled ? 1 : 0}`);
@@ -442,9 +413,7 @@ print('RESULT:' + json.dumps(result))
442
413
  commands.push(`FreezeRendering ${params.freezeRendering ? 1 : 0}`);
443
414
  }
444
415
 
445
- for (const cmd of commands) {
446
- await this.bridge.executeConsoleCommand(cmd);
447
- }
416
+ await this.bridge.executeConsoleCommands(commands);
448
417
 
449
418
  return { success: true, message: 'Occlusion culling configured' };
450
419
  }
@@ -455,7 +424,7 @@ print('RESULT:' + json.dumps(result))
455
424
  cacheShaders?: boolean;
456
425
  reducePermutations?: boolean;
457
426
  }) {
458
- const commands = [];
427
+ const commands: string[] = [];
459
428
 
460
429
  if (params.compileOnDemand !== undefined) {
461
430
  commands.push(`r.ShaderDevelopmentMode ${params.compileOnDemand ? 1 : 0}`);
@@ -469,9 +438,7 @@ print('RESULT:' + json.dumps(result))
469
438
  commands.push('RecompileShaders changed');
470
439
  }
471
440
 
472
- for (const cmd of commands) {
473
- await this.bridge.executeConsoleCommand(cmd);
474
- }
441
+ await this.bridge.executeConsoleCommands(commands);
475
442
 
476
443
  return { success: true, message: 'Shader optimization configured' };
477
444
  }
@@ -482,7 +449,7 @@ print('RESULT:' + json.dumps(result))
482
449
  maxPixelsPerEdge?: number;
483
450
  streamingPoolSize?: number;
484
451
  }) {
485
- const commands = [];
452
+ const commands: string[] = [];
486
453
 
487
454
  commands.push(`r.Nanite ${params.enabled ? 1 : 0}`);
488
455
 
@@ -494,9 +461,7 @@ print('RESULT:' + json.dumps(result))
494
461
  commands.push(`r.Nanite.StreamingPoolSize ${params.streamingPoolSize}`);
495
462
  }
496
463
 
497
- for (const cmd of commands) {
498
- await this.bridge.executeConsoleCommand(cmd);
499
- }
464
+ await this.bridge.executeConsoleCommands(commands);
500
465
 
501
466
  return { success: true, message: 'Nanite configured' };
502
467
  }
@@ -507,7 +472,7 @@ print('RESULT:' + json.dumps(result))
507
472
  streamingDistance?: number;
508
473
  cellSize?: number;
509
474
  }) {
510
- const commands = [];
475
+ const commands: string[] = [];
511
476
 
512
477
  commands.push(`wp.Runtime.EnableStreaming ${params.enabled ? 1 : 0}`);
513
478
 
@@ -519,9 +484,7 @@ print('RESULT:' + json.dumps(result))
519
484
  commands.push(`wp.Runtime.CellSize ${params.cellSize}`);
520
485
  }
521
486
 
522
- for (const cmd of commands) {
523
- await this.bridge.executeConsoleCommand(cmd);
524
- }
487
+ await this.bridge.executeConsoleCommands(commands);
525
488
 
526
489
  return { success: true, message: 'World Partition configured' };
527
490
  }
@@ -533,16 +496,14 @@ print('RESULT:' + json.dumps(result))
533
496
  }) {
534
497
  const duration = params.duration || 60;
535
498
 
536
- // Start recording and GPU profiling
537
- await this.bridge.executeConsoleCommand('stat startfile');
538
- await this.bridge.executeConsoleCommand('profilegpu');
499
+ // Start recording and GPU profiling
500
+ await this.bridge.executeConsoleCommands(['stat startfile', 'profilegpu']);
539
501
 
540
502
  // Wait for the requested duration
541
503
  await new Promise(resolve => setTimeout(resolve, duration * 1000));
542
504
 
543
505
  // Stop recording and clear stats
544
- await this.bridge.executeConsoleCommand('stat stopfile');
545
- await this.bridge.executeConsoleCommand('stat none');
506
+ await this.bridge.executeConsoleCommands(['stat stopfile', 'stat none']);
546
507
 
547
508
  return { success: true, message: `Benchmark completed for ${duration} seconds` };
548
509
  }