unreal-engine-mcp-server 0.4.0 → 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 (135) 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 +21 -5
  5. package/dist/index.js +124 -31
  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.js +46 -62
  11. package/dist/resources/levels.d.ts +21 -3
  12. package/dist/resources/levels.js +29 -54
  13. package/dist/tools/actors.d.ts +3 -14
  14. package/dist/tools/actors.js +246 -302
  15. package/dist/tools/animation.d.ts +57 -102
  16. package/dist/tools/animation.js +429 -450
  17. package/dist/tools/assets.d.ts +13 -2
  18. package/dist/tools/assets.js +52 -44
  19. package/dist/tools/audio.d.ts +22 -13
  20. package/dist/tools/audio.js +467 -121
  21. package/dist/tools/blueprint.d.ts +32 -13
  22. package/dist/tools/blueprint.js +699 -448
  23. package/dist/tools/build_environment_advanced.d.ts +0 -1
  24. package/dist/tools/build_environment_advanced.js +190 -45
  25. package/dist/tools/consolidated-tool-definitions.js +78 -252
  26. package/dist/tools/consolidated-tool-handlers.js +506 -133
  27. package/dist/tools/debug.d.ts +72 -10
  28. package/dist/tools/debug.js +167 -31
  29. package/dist/tools/editor.d.ts +9 -2
  30. package/dist/tools/editor.js +30 -44
  31. package/dist/tools/foliage.d.ts +34 -15
  32. package/dist/tools/foliage.js +97 -107
  33. package/dist/tools/introspection.js +19 -21
  34. package/dist/tools/landscape.d.ts +1 -2
  35. package/dist/tools/landscape.js +311 -168
  36. package/dist/tools/level.d.ts +3 -28
  37. package/dist/tools/level.js +642 -192
  38. package/dist/tools/lighting.d.ts +14 -3
  39. package/dist/tools/lighting.js +236 -123
  40. package/dist/tools/materials.d.ts +25 -7
  41. package/dist/tools/materials.js +102 -79
  42. package/dist/tools/niagara.d.ts +10 -12
  43. package/dist/tools/niagara.js +74 -94
  44. package/dist/tools/performance.d.ts +12 -4
  45. package/dist/tools/performance.js +38 -79
  46. package/dist/tools/physics.d.ts +34 -10
  47. package/dist/tools/physics.js +364 -292
  48. package/dist/tools/rc.js +97 -23
  49. package/dist/tools/sequence.d.ts +1 -0
  50. package/dist/tools/sequence.js +125 -22
  51. package/dist/tools/ui.d.ts +31 -4
  52. package/dist/tools/ui.js +83 -66
  53. package/dist/tools/visual.d.ts +11 -0
  54. package/dist/tools/visual.js +245 -30
  55. package/dist/types/tool-types.d.ts +0 -6
  56. package/dist/types/tool-types.js +1 -8
  57. package/dist/unreal-bridge.d.ts +32 -2
  58. package/dist/unreal-bridge.js +621 -127
  59. package/dist/utils/elicitation.d.ts +57 -0
  60. package/dist/utils/elicitation.js +104 -0
  61. package/dist/utils/error-handler.d.ts +0 -33
  62. package/dist/utils/error-handler.js +4 -111
  63. package/dist/utils/http.d.ts +2 -22
  64. package/dist/utils/http.js +12 -75
  65. package/dist/utils/normalize.d.ts +4 -4
  66. package/dist/utils/normalize.js +15 -7
  67. package/dist/utils/python-output.d.ts +18 -0
  68. package/dist/utils/python-output.js +290 -0
  69. package/dist/utils/python.d.ts +2 -0
  70. package/dist/utils/python.js +4 -0
  71. package/dist/utils/response-validator.js +28 -2
  72. package/dist/utils/result-helpers.d.ts +27 -0
  73. package/dist/utils/result-helpers.js +147 -0
  74. package/dist/utils/safe-json.d.ts +0 -2
  75. package/dist/utils/safe-json.js +0 -43
  76. package/dist/utils/validation.d.ts +16 -0
  77. package/dist/utils/validation.js +70 -7
  78. package/mcp-config-example.json +2 -2
  79. package/package.json +10 -9
  80. package/server.json +37 -14
  81. package/src/index.ts +130 -33
  82. package/src/prompts/index.ts +211 -13
  83. package/src/resources/actors.ts +59 -44
  84. package/src/resources/assets.ts +48 -51
  85. package/src/resources/levels.ts +35 -45
  86. package/src/tools/actors.ts +269 -313
  87. package/src/tools/animation.ts +556 -539
  88. package/src/tools/assets.ts +53 -43
  89. package/src/tools/audio.ts +507 -113
  90. package/src/tools/blueprint.ts +778 -462
  91. package/src/tools/build_environment_advanced.ts +266 -64
  92. package/src/tools/consolidated-tool-definitions.ts +90 -264
  93. package/src/tools/consolidated-tool-handlers.ts +630 -121
  94. package/src/tools/debug.ts +176 -33
  95. package/src/tools/editor.ts +35 -37
  96. package/src/tools/foliage.ts +110 -104
  97. package/src/tools/introspection.ts +24 -22
  98. package/src/tools/landscape.ts +334 -181
  99. package/src/tools/level.ts +683 -182
  100. package/src/tools/lighting.ts +244 -123
  101. package/src/tools/materials.ts +114 -83
  102. package/src/tools/niagara.ts +87 -81
  103. package/src/tools/performance.ts +49 -88
  104. package/src/tools/physics.ts +393 -299
  105. package/src/tools/rc.ts +102 -24
  106. package/src/tools/sequence.ts +136 -28
  107. package/src/tools/ui.ts +101 -70
  108. package/src/tools/visual.ts +250 -29
  109. package/src/types/tool-types.ts +0 -9
  110. package/src/unreal-bridge.ts +658 -140
  111. package/src/utils/elicitation.ts +129 -0
  112. package/src/utils/error-handler.ts +4 -159
  113. package/src/utils/http.ts +16 -115
  114. package/src/utils/normalize.ts +20 -10
  115. package/src/utils/python-output.ts +351 -0
  116. package/src/utils/python.ts +3 -0
  117. package/src/utils/response-validator.ts +25 -2
  118. package/src/utils/result-helpers.ts +193 -0
  119. package/src/utils/safe-json.ts +0 -50
  120. package/src/utils/validation.ts +94 -7
  121. package/tests/run-unreal-tool-tests.mjs +720 -0
  122. package/tsconfig.json +2 -2
  123. package/dist/python-utils.d.ts +0 -29
  124. package/dist/python-utils.js +0 -54
  125. package/dist/types/index.d.ts +0 -323
  126. package/dist/types/index.js +0 -28
  127. package/dist/utils/cache-manager.d.ts +0 -64
  128. package/dist/utils/cache-manager.js +0 -176
  129. package/dist/utils/errors.d.ts +0 -133
  130. package/dist/utils/errors.js +0 -256
  131. package/src/python/editor_compat.py +0 -181
  132. package/src/python-utils.ts +0 -57
  133. package/src/types/index.ts +0 -414
  134. package/src/utils/cache-manager.ts +0 -213
  135. package/src/utils/errors.ts +0 -312
@@ -1,4 +1,5 @@
1
1
  import { UnrealBridge } from '../unreal-bridge.js';
2
+ import { interpretStandardResult, coerceBoolean, coerceNumber, coerceString, coerceStringArray } from '../utils/result-helpers.js';
2
3
 
3
4
  /**
4
5
  * Advanced Build Environment Tools
@@ -137,19 +138,85 @@ except Exception as e:
137
138
  print(f"RESULT:{json.dumps(result)}")
138
139
  `.trim();
139
140
 
140
- const response = await this.bridge.executePython(pythonScript);
141
- const output = this.parseResponse(response);
142
- const match = output.match(/RESULT:({.*})/);
143
-
144
- if (match) {
145
- try {
146
- return JSON.parse(match[1]);
147
- } catch (_e) {
148
- return { success: false, error: 'Failed to parse result', details: output };
149
- }
150
- }
151
-
152
- return { success: false, error: 'No result found', output };
141
+ const response = await this.bridge.executePython(pythonScript);
142
+ const interpreted = interpretStandardResult(response, {
143
+ successMessage: `Created procedural terrain '${params.name}'`,
144
+ failureMessage: `Failed to create procedural terrain '${params.name}'`
145
+ });
146
+
147
+ if (!interpreted.success) {
148
+ const failure: {
149
+ success: false;
150
+ error: string;
151
+ message: string;
152
+ warnings?: string[];
153
+ details?: string[];
154
+ payload?: Record<string, unknown>;
155
+ } = {
156
+ success: false,
157
+ error: interpreted.error ?? interpreted.message,
158
+ message: interpreted.message
159
+ };
160
+
161
+ if (interpreted.warnings) {
162
+ failure.warnings = interpreted.warnings;
163
+ }
164
+ if (interpreted.details) {
165
+ failure.details = interpreted.details;
166
+ }
167
+ if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
168
+ failure.payload = interpreted.payload;
169
+ }
170
+
171
+ return failure;
172
+ }
173
+
174
+ const payload = { ...interpreted.payload } as Record<string, unknown>;
175
+ const actorName = coerceString(payload.actor_name) ?? coerceString(payload.actorName);
176
+ const vertices = coerceNumber(payload.vertices);
177
+ const triangles = coerceNumber(payload.triangles);
178
+ const subdivisions = coerceNumber(payload.subdivisions);
179
+ const sizeArray = Array.isArray(payload.size)
180
+ ? (payload.size as unknown[]).map(entry => {
181
+ if (typeof entry === 'number' && Number.isFinite(entry)) {
182
+ return entry;
183
+ }
184
+ if (typeof entry === 'string') {
185
+ const parsed = Number(entry);
186
+ return Number.isFinite(parsed) ? parsed : undefined;
187
+ }
188
+ return undefined;
189
+ }).filter((entry): entry is number => typeof entry === 'number')
190
+ : undefined;
191
+
192
+ payload.success = true;
193
+ payload.message = interpreted.message;
194
+
195
+ if (actorName) {
196
+ payload.actor_name = actorName;
197
+ payload.actorName = actorName;
198
+ }
199
+ if (typeof vertices === 'number') {
200
+ payload.vertices = vertices;
201
+ }
202
+ if (typeof triangles === 'number') {
203
+ payload.triangles = triangles;
204
+ }
205
+ if (typeof subdivisions === 'number') {
206
+ payload.subdivisions = subdivisions;
207
+ }
208
+ if (sizeArray && sizeArray.length === 2) {
209
+ payload.size = sizeArray;
210
+ }
211
+
212
+ if (interpreted.warnings) {
213
+ payload.warnings = interpreted.warnings;
214
+ }
215
+ if (interpreted.details) {
216
+ payload.details = interpreted.details;
217
+ }
218
+
219
+ return payload as any;
153
220
  }
154
221
 
155
222
  /**
@@ -305,19 +372,80 @@ except Exception as e:
305
372
  print(f"RESULT:{json.dumps(result)}")
306
373
  `.trim();
307
374
 
308
- const response = await this.bridge.executePython(pythonScript);
309
- const output = this.parseResponse(response);
310
- const match = output.match(/RESULT:({.*})/);
311
-
312
- if (match) {
313
- try {
314
- return JSON.parse(match[1]);
315
- } catch (_e) {
316
- return { success: false, error: 'Failed to parse result', details: output };
317
- }
318
- }
319
-
320
- return { success: false, error: 'No result found', output };
375
+ const response = await this.bridge.executePython(pythonScript);
376
+ const interpreted = interpretStandardResult(response, {
377
+ successMessage: `Created procedural foliage volume '${params.name}'`,
378
+ failureMessage: `Failed to create procedural foliage volume '${params.name}'`
379
+ });
380
+
381
+ if (!interpreted.success) {
382
+ const failure: {
383
+ success: false;
384
+ error: string;
385
+ message: string;
386
+ warnings?: string[];
387
+ details?: string[];
388
+ payload?: Record<string, unknown>;
389
+ } = {
390
+ success: false,
391
+ error: interpreted.error ?? interpreted.message,
392
+ message: interpreted.message
393
+ };
394
+
395
+ if (interpreted.warnings) {
396
+ failure.warnings = interpreted.warnings;
397
+ }
398
+ if (interpreted.details) {
399
+ failure.details = interpreted.details;
400
+ }
401
+ if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
402
+ failure.payload = interpreted.payload;
403
+ }
404
+
405
+ return failure;
406
+ }
407
+
408
+ const payload = { ...interpreted.payload } as Record<string, unknown>;
409
+ const volumeActor = coerceString(payload.volume_actor) ?? coerceString(payload.volumeActor);
410
+ const spawnerPath = coerceString(payload.spawner_path) ?? coerceString(payload.spawnerPath);
411
+ const foliageCount = coerceNumber(payload.foliage_types_count) ?? coerceNumber(payload.foliageTypesCount);
412
+ const resimulated = coerceBoolean(payload.resimulated);
413
+ const note = coerceString(payload.note);
414
+ const messages = coerceStringArray(payload.messages);
415
+
416
+ payload.success = true;
417
+ payload.message = interpreted.message;
418
+
419
+ if (volumeActor) {
420
+ payload.volume_actor = volumeActor;
421
+ payload.volumeActor = volumeActor;
422
+ }
423
+ if (spawnerPath) {
424
+ payload.spawner_path = spawnerPath;
425
+ payload.spawnerPath = spawnerPath;
426
+ }
427
+ if (typeof foliageCount === 'number') {
428
+ payload.foliage_types_count = foliageCount;
429
+ payload.foliageTypesCount = foliageCount;
430
+ }
431
+ if (typeof resimulated === 'boolean') {
432
+ payload.resimulated = resimulated;
433
+ }
434
+ if (note) {
435
+ payload.note = note;
436
+ }
437
+ if (messages && messages.length > 0) {
438
+ payload.messages = messages;
439
+ }
440
+
441
+ if (interpreted.warnings) {
442
+ payload.warnings = interpreted.warnings;
443
+ }
444
+ if (interpreted.details) {
445
+ payload.details = interpreted.details;
446
+ }
447
+
448
+ return payload as any;
321
449
  }
322
450
 
323
451
  /**
@@ -389,19 +517,59 @@ except Exception as e:
389
517
  print(f"RESULT:{json.dumps(result)}")
390
518
  `.trim();
391
519
 
392
- const response = await this.bridge.executePython(pythonScript);
393
- const output = this.parseResponse(response);
394
- const match = output.match(/RESULT:({.*})/);
395
-
396
- if (match) {
397
- try {
398
- return JSON.parse(match[1]);
399
- } catch (_e) {
400
- return { success: false, error: 'Failed to parse result', details: output };
401
- }
402
- }
403
-
404
- return { success: false, error: 'No result found', output };
520
+ const response = await this.bridge.executePython(pythonScript);
521
+ const interpreted = interpretStandardResult(response, {
522
+ successMessage: 'Foliage instances added',
523
+ failureMessage: 'Failed to add foliage instances'
524
+ });
525
+
526
+ if (!interpreted.success) {
527
+ const failure: {
528
+ success: false;
529
+ error: string;
530
+ message: string;
531
+ warnings?: string[];
532
+ details?: string[];
533
+ payload?: Record<string, unknown>;
534
+ } = {
535
+ success: false,
536
+ error: interpreted.error ?? interpreted.message,
537
+ message: interpreted.message
538
+ };
539
+
540
+ if (interpreted.warnings) {
541
+ failure.warnings = interpreted.warnings;
542
+ }
543
+ if (interpreted.details) {
544
+ failure.details = interpreted.details;
545
+ }
546
+ if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
547
+ failure.payload = interpreted.payload;
548
+ }
549
+
550
+ return failure;
551
+ }
552
+
553
+ const payload = { ...interpreted.payload } as Record<string, unknown>;
554
+ const count = coerceNumber(payload.instances_count) ?? coerceNumber(payload.instancesCount);
555
+ const message = coerceString(payload.message) ?? interpreted.message;
556
+
557
+ payload.success = true;
558
+ payload.message = message;
559
+
560
+ if (typeof count === 'number') {
561
+ payload.instances_count = count;
562
+ payload.instancesCount = count;
563
+ }
564
+
565
+ if (interpreted.warnings) {
566
+ payload.warnings = interpreted.warnings;
567
+ }
568
+ if (interpreted.details) {
569
+ payload.details = interpreted.details;
570
+ }
571
+
572
+ return payload as any;
405
573
  }
406
574
 
407
575
  /**
@@ -500,31 +668,65 @@ except Exception as e:
500
668
  print(f"RESULT:{json.dumps(result)}")
501
669
  `.trim();
502
670
 
503
- const response = await this.bridge.executePython(pythonScript);
504
- const output = this.parseResponse(response);
505
- const match = output.match(/RESULT:({.*})/);
506
-
507
- if (match) {
508
- try {
509
- return JSON.parse(match[1]);
510
- } catch (_e) {
511
- return { success: false, error: 'Failed to parse result', details: output };
512
- }
513
- }
514
-
515
- return { success: false, error: 'No result found', output };
516
- }
671
+ const response = await this.bridge.executePython(pythonScript);
672
+ const interpreted = interpretStandardResult(response, {
673
+ successMessage: `Created landscape grass type '${params.name}'`,
674
+ failureMessage: `Failed to create landscape grass type '${params.name}'`
675
+ });
676
+
677
+ if (!interpreted.success) {
678
+ const failure: {
679
+ success: false;
680
+ error: string;
681
+ message: string;
682
+ warnings?: string[];
683
+ details?: string[];
684
+ payload?: Record<string, unknown>;
685
+ } = {
686
+ success: false,
687
+ error: interpreted.error ?? interpreted.message,
688
+ message: interpreted.message
689
+ };
690
+
691
+ if (interpreted.warnings) {
692
+ failure.warnings = interpreted.warnings;
693
+ }
694
+ if (interpreted.details) {
695
+ failure.details = interpreted.details;
696
+ }
697
+ if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
698
+ failure.payload = interpreted.payload;
699
+ }
700
+
701
+ return failure;
702
+ }
703
+
704
+ const payload = { ...interpreted.payload } as Record<string, unknown>;
705
+ const assetPath = coerceString(payload.asset_path) ?? coerceString(payload.assetPath);
706
+ const note = coerceString(payload.note);
707
+ const messages = coerceStringArray(payload.messages);
708
+
709
+ payload.success = true;
710
+ payload.message = interpreted.message;
711
+
712
+ if (assetPath) {
713
+ payload.asset_path = assetPath;
714
+ payload.assetPath = assetPath;
715
+ }
716
+ if (note) {
717
+ payload.note = note;
718
+ }
719
+ if (messages && messages.length > 0) {
720
+ payload.messages = messages;
721
+ }
722
+
723
+ if (interpreted.warnings) {
724
+ payload.warnings = interpreted.warnings;
725
+ }
726
+ if (interpreted.details) {
727
+ payload.details = interpreted.details;
728
+ }
517
729
 
518
- private parseResponse(response: any): string {
519
- if (response && typeof response === 'object') {
520
- if (response.LogOutput && Array.isArray(response.LogOutput)) {
521
- return response.LogOutput.map((log: any) => log.Output || '').join('');
522
- } else if (response.CommandResult) {
523
- return response.CommandResult;
524
- } else if (response.ReturnValue) {
525
- return JSON.stringify(response);
526
- }
527
- }
528
- return typeof response === 'string' ? response : JSON.stringify(response);
730
+ return payload as any;
529
731
  }
530
732
  }