unreal-engine-mcp-server 0.2.1 → 0.3.0
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.
- package/.env.production +1 -1
- package/README.md +0 -17
- package/dist/index.js +4 -4
- package/dist/resources/assets.d.ts +1 -1
- package/dist/resources/assets.js +3 -3
- package/dist/tools/consolidated-tool-definitions.js +156 -14
- package/dist/tools/consolidated-tool-handlers.js +1 -1
- package/dist/tools/tool-definitions.js +141 -25
- package/dist/tools/tool-handlers.js +42 -9
- package/dist/unreal-bridge.js +2 -2
- package/dist/utils/response-validator.js +3 -3
- package/dist/utils/safe-json.js +1 -1
- package/package.json +3 -10
- package/server.json +2 -2
- package/src/index.ts +5 -5
- package/src/resources/assets.ts +3 -3
- package/src/tools/consolidated-tool-definitions.ts +156 -14
- package/src/tools/consolidated-tool-handlers.ts +1 -1
- package/src/tools/tool-definitions.ts +141 -25
- package/src/tools/tool-handlers.ts +41 -9
- package/src/unreal-bridge.ts +2 -2
- package/src/utils/response-validator.ts +3 -3
- package/src/utils/safe-json.ts +1 -1
|
@@ -3,7 +3,18 @@ export const toolDefinitions = [
|
|
|
3
3
|
// Asset Tools
|
|
4
4
|
{
|
|
5
5
|
name: 'list_assets',
|
|
6
|
-
description:
|
|
6
|
+
description: `List assets in a folder of the project.
|
|
7
|
+
|
|
8
|
+
When to use:
|
|
9
|
+
- Browse project content (use /Game; /Content is auto-mapped by the server).
|
|
10
|
+
- Get a quick inventory of assets in a subfolder to refine subsequent actions.
|
|
11
|
+
|
|
12
|
+
Notes:
|
|
13
|
+
- For /Game, the server may limit results for performance; prefer subfolders (e.g., /Game/ThirdPerson).
|
|
14
|
+
- Returns a structured list with Name/Path/Class/PackagePath when available.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
- {"directory":"/Game/ThirdPerson","recursive":false}`,
|
|
7
18
|
inputSchema: {
|
|
8
19
|
type: 'object',
|
|
9
20
|
properties: {
|
|
@@ -34,7 +45,17 @@ export const toolDefinitions = [
|
|
|
34
45
|
},
|
|
35
46
|
{
|
|
36
47
|
name: 'import_asset',
|
|
37
|
-
description:
|
|
48
|
+
description: `Import a file from disk into the project (e.g., FBX, PNG, WAV, EXR).
|
|
49
|
+
|
|
50
|
+
When to use:
|
|
51
|
+
- Bring external content into /Game at a specific destination path.
|
|
52
|
+
|
|
53
|
+
Notes:
|
|
54
|
+
- destinationPath is a package path like /Game/Environment/Trees.
|
|
55
|
+
- Keep file names simple (avoid spaces and special characters).
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
- {"sourcePath":"C:/Temp/Tree.fbx","destinationPath":"/Game/Environment/Trees"}`,
|
|
38
59
|
inputSchema: {
|
|
39
60
|
type: 'object',
|
|
40
61
|
properties: {
|
|
@@ -56,7 +77,17 @@ export const toolDefinitions = [
|
|
|
56
77
|
// Actor Tools
|
|
57
78
|
{
|
|
58
79
|
name: 'spawn_actor',
|
|
59
|
-
description:
|
|
80
|
+
description: `Spawn a new actor in the current level.
|
|
81
|
+
|
|
82
|
+
When to use:
|
|
83
|
+
- Place a class (e.g., StaticMeshActor, CameraActor) or spawn from an asset path (e.g., /Engine/BasicShapes/Cube).
|
|
84
|
+
|
|
85
|
+
Notes:
|
|
86
|
+
- If an asset path is provided, a StaticMeshActor is auto-spawned with the mesh set.
|
|
87
|
+
- location/rotation are optional; defaults are used if omitted.
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
- {"classPath":"/Engine/BasicShapes/Cube","location":{"x":0,"y":0,"z":100}}`,
|
|
60
91
|
inputSchema: {
|
|
61
92
|
type: 'object',
|
|
62
93
|
properties: {
|
|
@@ -91,7 +122,13 @@ export const toolDefinitions = [
|
|
|
91
122
|
},
|
|
92
123
|
{
|
|
93
124
|
name: 'delete_actor',
|
|
94
|
-
description:
|
|
125
|
+
description: `Delete one or more actors by name/label.
|
|
126
|
+
|
|
127
|
+
When to use:
|
|
128
|
+
- Remove actors matching a label/name (case-insensitive).
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
- {"actorName":"Cube_1"}`,
|
|
95
132
|
inputSchema: {
|
|
96
133
|
type: 'object',
|
|
97
134
|
properties: {
|
|
@@ -111,7 +148,13 @@ export const toolDefinitions = [
|
|
|
111
148
|
// Material Tools
|
|
112
149
|
{
|
|
113
150
|
name: 'create_material',
|
|
114
|
-
description:
|
|
151
|
+
description: `Create a simple Material asset at a path.
|
|
152
|
+
|
|
153
|
+
When to use:
|
|
154
|
+
- Quickly scaffold a basic material you can edit later.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
- {"name":"M_Mask","path":"/Game/Materials"}`,
|
|
115
158
|
inputSchema: {
|
|
116
159
|
type: 'object',
|
|
117
160
|
properties: {
|
|
@@ -132,7 +175,13 @@ export const toolDefinitions = [
|
|
|
132
175
|
},
|
|
133
176
|
{
|
|
134
177
|
name: 'apply_material_to_actor',
|
|
135
|
-
description:
|
|
178
|
+
description: `Assign a material to an actor's mesh component.
|
|
179
|
+
|
|
180
|
+
When to use:
|
|
181
|
+
- Swap an actor's material by path; slotIndex defaults to 0.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
- {"actorPath":"/Game/LevelActors/Cube_1","materialPath":"/Game/Materials/M_Mask","slotIndex":0}`,
|
|
136
185
|
inputSchema: {
|
|
137
186
|
type: 'object',
|
|
138
187
|
properties: {
|
|
@@ -153,7 +202,10 @@ export const toolDefinitions = [
|
|
|
153
202
|
// Editor Tools
|
|
154
203
|
{
|
|
155
204
|
name: 'play_in_editor',
|
|
156
|
-
description:
|
|
205
|
+
description: `Start a Play-In-Editor (PIE) session.
|
|
206
|
+
|
|
207
|
+
When to use:
|
|
208
|
+
- Begin simulating the level in the editor.`,
|
|
157
209
|
inputSchema: {
|
|
158
210
|
type: 'object',
|
|
159
211
|
properties: {}
|
|
@@ -169,7 +221,10 @@ export const toolDefinitions = [
|
|
|
169
221
|
},
|
|
170
222
|
{
|
|
171
223
|
name: 'stop_play_in_editor',
|
|
172
|
-
description:
|
|
224
|
+
description: `Stop the active PIE session.
|
|
225
|
+
|
|
226
|
+
When to use:
|
|
227
|
+
- End simulation and return to the editor.`,
|
|
173
228
|
inputSchema: {
|
|
174
229
|
type: 'object',
|
|
175
230
|
properties: {}
|
|
@@ -185,7 +240,16 @@ export const toolDefinitions = [
|
|
|
185
240
|
},
|
|
186
241
|
{
|
|
187
242
|
name: 'set_camera',
|
|
188
|
-
description:
|
|
243
|
+
description: `Reposition the editor viewport camera.
|
|
244
|
+
|
|
245
|
+
When to use:
|
|
246
|
+
- Move/aim the camera in the editor for framing.
|
|
247
|
+
|
|
248
|
+
Notes:
|
|
249
|
+
- Accepts object or array formats; values are normalized.
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
- {"location":{"x":0,"y":-600,"z":250},"rotation":{"pitch":0,"yaw":0,"roll":0}}`,
|
|
189
253
|
inputSchema: {
|
|
190
254
|
type: 'object',
|
|
191
255
|
properties: {
|
|
@@ -220,7 +284,13 @@ export const toolDefinitions = [
|
|
|
220
284
|
// Animation Tools
|
|
221
285
|
{
|
|
222
286
|
name: 'create_animation_blueprint',
|
|
223
|
-
description:
|
|
287
|
+
description: `Create an Animation Blueprint for a skeleton.
|
|
288
|
+
|
|
289
|
+
When to use:
|
|
290
|
+
- Generate a starter Anim BP for a given skeleton.
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
- {"name":"ABP_Hero","skeletonPath":"/Game/Characters/Hero/SK_Hero_Skeleton","savePath":"/Game/Characters/Hero"}`,
|
|
224
294
|
inputSchema: {
|
|
225
295
|
type: 'object',
|
|
226
296
|
properties: {
|
|
@@ -241,7 +311,13 @@ export const toolDefinitions = [
|
|
|
241
311
|
},
|
|
242
312
|
{
|
|
243
313
|
name: 'play_animation_montage',
|
|
244
|
-
description:
|
|
314
|
+
description: `Play a Montage/Animation on an actor.
|
|
315
|
+
|
|
316
|
+
When to use:
|
|
317
|
+
- Trigger a montage on a possessed or editor actor.
|
|
318
|
+
|
|
319
|
+
Example:
|
|
320
|
+
- {"actorName":"Hero","montagePath":"/Game/Anim/MT_Attack","playRate":1.0}`,
|
|
245
321
|
inputSchema: {
|
|
246
322
|
type: 'object',
|
|
247
323
|
properties: {
|
|
@@ -263,7 +339,13 @@ export const toolDefinitions = [
|
|
|
263
339
|
// Physics Tools
|
|
264
340
|
{
|
|
265
341
|
name: 'setup_ragdoll',
|
|
266
|
-
description:
|
|
342
|
+
description: `Enable simple ragdoll using a physics asset.
|
|
343
|
+
|
|
344
|
+
When to use:
|
|
345
|
+
- Toggle ragdoll behavior on a character skeleton.
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
- {"skeletonPath":"/Game/Characters/Hero/SK_Hero_Skeleton","physicsAssetName":"PHYS_Hero","blendWeight":1.0}`,
|
|
267
349
|
inputSchema: {
|
|
268
350
|
type: 'object',
|
|
269
351
|
properties: {
|
|
@@ -284,7 +366,10 @@ export const toolDefinitions = [
|
|
|
284
366
|
},
|
|
285
367
|
{
|
|
286
368
|
name: 'apply_force',
|
|
287
|
-
description:
|
|
369
|
+
description: `Apply a world-space force vector to an actor with physics enabled.
|
|
370
|
+
|
|
371
|
+
Example:
|
|
372
|
+
- {"actorName":"PhysicsBox","force":{"x":0,"y":0,"z":5000}}`,
|
|
288
373
|
inputSchema: {
|
|
289
374
|
type: 'object',
|
|
290
375
|
properties: {
|
|
@@ -313,7 +398,13 @@ export const toolDefinitions = [
|
|
|
313
398
|
// Niagara Tools
|
|
314
399
|
{
|
|
315
400
|
name: 'create_particle_effect',
|
|
316
|
-
description:
|
|
401
|
+
description: `Create a simple particle/FX by tag.
|
|
402
|
+
|
|
403
|
+
When to use:
|
|
404
|
+
- Quickly drop a generic Fire/Smoke/Water effect for previews.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
- {"effectType":"Smoke","name":"SMK1","location":{"x":100,"y":0,"z":50}}`,
|
|
317
408
|
inputSchema: {
|
|
318
409
|
type: 'object',
|
|
319
410
|
properties: {
|
|
@@ -342,7 +433,10 @@ export const toolDefinitions = [
|
|
|
342
433
|
},
|
|
343
434
|
{
|
|
344
435
|
name: 'spawn_niagara_system',
|
|
345
|
-
description:
|
|
436
|
+
description: `Spawn a Niagara system at a location.
|
|
437
|
+
|
|
438
|
+
Example:
|
|
439
|
+
- {"systemPath":"/Game/FX/NS_Explosion","location":{"x":0,"y":0,"z":200},"scale":1.0}`,
|
|
346
440
|
inputSchema: {
|
|
347
441
|
type: 'object',
|
|
348
442
|
properties: {
|
|
@@ -371,7 +465,10 @@ export const toolDefinitions = [
|
|
|
371
465
|
// Blueprint Tools
|
|
372
466
|
{
|
|
373
467
|
name: 'create_blueprint',
|
|
374
|
-
description:
|
|
468
|
+
description: `Create a new Blueprint asset at a path.
|
|
469
|
+
|
|
470
|
+
Example:
|
|
471
|
+
- {"name":"BP_Switch","blueprintType":"Actor","savePath":"/Game/Blueprints"}`,
|
|
375
472
|
inputSchema: {
|
|
376
473
|
type: 'object',
|
|
377
474
|
properties: {
|
|
@@ -392,7 +489,10 @@ export const toolDefinitions = [
|
|
|
392
489
|
},
|
|
393
490
|
{
|
|
394
491
|
name: 'add_blueprint_component',
|
|
395
|
-
description:
|
|
492
|
+
description: `Add a component to an existing Blueprint.
|
|
493
|
+
|
|
494
|
+
Example:
|
|
495
|
+
- {"blueprintName":"BP_Switch","componentType":"PointLightComponent","componentName":"KeyLight"}`,
|
|
396
496
|
inputSchema: {
|
|
397
497
|
type: 'object',
|
|
398
498
|
properties: {
|
|
@@ -415,7 +515,10 @@ export const toolDefinitions = [
|
|
|
415
515
|
// Level Tools
|
|
416
516
|
{
|
|
417
517
|
name: 'load_level',
|
|
418
|
-
description:
|
|
518
|
+
description: `Load a level by path (e.g., /Game/Maps/Lobby).
|
|
519
|
+
|
|
520
|
+
Example:
|
|
521
|
+
- {"levelPath":"/Game/Maps/Lobby","streaming":false}`,
|
|
419
522
|
inputSchema: {
|
|
420
523
|
type: 'object',
|
|
421
524
|
properties: {
|
|
@@ -435,7 +538,10 @@ export const toolDefinitions = [
|
|
|
435
538
|
},
|
|
436
539
|
{
|
|
437
540
|
name: 'save_level',
|
|
438
|
-
description:
|
|
541
|
+
description: `Save the current level to a path or by name.
|
|
542
|
+
|
|
543
|
+
Example:
|
|
544
|
+
- {"levelName":"Lobby","savePath":"/Game/Maps"}`,
|
|
439
545
|
inputSchema: {
|
|
440
546
|
type: 'object',
|
|
441
547
|
properties: {
|
|
@@ -454,7 +560,10 @@ export const toolDefinitions = [
|
|
|
454
560
|
},
|
|
455
561
|
{
|
|
456
562
|
name: 'stream_level',
|
|
457
|
-
description:
|
|
563
|
+
description: `Stream in/out a sublevel and set visibility.
|
|
564
|
+
|
|
565
|
+
Example:
|
|
566
|
+
- {'levelName':'Sublevel_A','shouldBeLoaded':true,'shouldBeVisible':true}`,
|
|
458
567
|
inputSchema: {
|
|
459
568
|
type: 'object',
|
|
460
569
|
properties: {
|
|
@@ -477,7 +586,11 @@ export const toolDefinitions = [
|
|
|
477
586
|
// Lighting Tools
|
|
478
587
|
{
|
|
479
588
|
name: 'create_light',
|
|
480
|
-
description:
|
|
589
|
+
description: `Create a light (Directional/Point/Spot/Rect/Sky) with optional transform/intensity.
|
|
590
|
+
|
|
591
|
+
Examples:
|
|
592
|
+
- {'lightType':'Directional','name':'KeyLight','intensity':5.0}
|
|
593
|
+
- {'lightType':'Point','name':'Fill','location':{'x':0,'y':100,'z':200},'intensity':2000}`,
|
|
481
594
|
inputSchema: {
|
|
482
595
|
type: 'object',
|
|
483
596
|
properties: {
|
|
@@ -506,7 +619,7 @@ export const toolDefinitions = [
|
|
|
506
619
|
},
|
|
507
620
|
{
|
|
508
621
|
name: 'build_lighting',
|
|
509
|
-
description: '
|
|
622
|
+
description: 'Start a lighting build at an optional quality level (Preview/Medium/High/Production).',
|
|
510
623
|
inputSchema: {
|
|
511
624
|
type: 'object',
|
|
512
625
|
properties: {
|
|
@@ -525,7 +638,7 @@ export const toolDefinitions = [
|
|
|
525
638
|
// Landscape Tools
|
|
526
639
|
{
|
|
527
640
|
name: 'create_landscape',
|
|
528
|
-
description: '
|
|
641
|
+
description: 'Attempt to create a landscape. Native Python APIs are limited; you may receive a guidance message to use Landscape Mode in the editor.',
|
|
529
642
|
inputSchema: {
|
|
530
643
|
type: 'object',
|
|
531
644
|
properties: {
|
|
@@ -547,7 +660,7 @@ export const toolDefinitions = [
|
|
|
547
660
|
},
|
|
548
661
|
{
|
|
549
662
|
name: 'sculpt_landscape',
|
|
550
|
-
description: 'Sculpt
|
|
663
|
+
description: 'Sculpt a landscape using editor tools (best-effort; some operations may require manual Landscape Mode).',
|
|
551
664
|
inputSchema: {
|
|
552
665
|
type: 'object',
|
|
553
666
|
properties: {
|
|
@@ -569,7 +682,10 @@ export const toolDefinitions = [
|
|
|
569
682
|
// Foliage Tools
|
|
570
683
|
{
|
|
571
684
|
name: 'add_foliage_type',
|
|
572
|
-
description:
|
|
685
|
+
description: `Create or load a FoliageType asset for instanced foliage workflows.
|
|
686
|
+
|
|
687
|
+
Example:
|
|
688
|
+
- {'name':'FT_Grass','meshPath':'/Game/Foliage/SM_Grass','density':300}`,
|
|
573
689
|
inputSchema: {
|
|
574
690
|
type: 'object',
|
|
575
691
|
properties: {
|
|
@@ -32,15 +32,23 @@ export async function handleToolCall(name, args, tools) {
|
|
|
32
32
|
message = 'Failed to list assets: directory path cannot be empty';
|
|
33
33
|
break;
|
|
34
34
|
}
|
|
35
|
+
// Normalize virtual content path: map /Content -> /Game (case-insensitive)
|
|
36
|
+
const rawDir = String(args.directory).trim();
|
|
37
|
+
let normDir = rawDir.replace(/^\/?content(\/|$)/i, '/Game$1');
|
|
38
|
+
// Ensure leading slash
|
|
39
|
+
if (!normDir.startsWith('/'))
|
|
40
|
+
normDir = '/' + normDir;
|
|
41
|
+
// Collapse duplicate slashes
|
|
42
|
+
normDir = normDir.replace(/\\+/g, '/').replace(/\/+/g, '/');
|
|
35
43
|
// Try multiple approaches to list assets
|
|
36
44
|
try {
|
|
37
|
-
console.log('[list_assets] Starting asset listing for directory:',
|
|
38
|
-
// First try: Use Python for most reliable listing
|
|
45
|
+
console.log('[list_assets] Starting asset listing for directory:', normDir);
|
|
46
|
+
// First try: Use Python for most reliable listing (recursive if /Game)
|
|
39
47
|
const pythonCode = `
|
|
40
48
|
import unreal
|
|
41
49
|
import json
|
|
42
50
|
|
|
43
|
-
directory = '${
|
|
51
|
+
directory = '${normDir || '/Game'}'
|
|
44
52
|
# Use recursive for /Game to find assets in subdirectories, but limit depth
|
|
45
53
|
recursive = True if directory == '/Game' else False
|
|
46
54
|
|
|
@@ -141,8 +149,9 @@ except Exception as e:
|
|
|
141
149
|
const searchResult = await tools.bridge.httpCall('/remote/search/assets', 'PUT', {
|
|
142
150
|
Query: '', // Empty query to match all (wildcard doesn't work)
|
|
143
151
|
Filter: {
|
|
144
|
-
PackagePaths: [
|
|
145
|
-
|
|
152
|
+
PackagePaths: [normDir || '/Game'],
|
|
153
|
+
// Recursively search so we actually find assets in subfolders
|
|
154
|
+
RecursivePaths: true,
|
|
146
155
|
ClassNames: [], // Empty to get all types
|
|
147
156
|
RecursiveClasses: true
|
|
148
157
|
},
|
|
@@ -165,7 +174,7 @@ except Exception as e:
|
|
|
165
174
|
functionName: 'ExecuteConsoleCommand',
|
|
166
175
|
parameters: {
|
|
167
176
|
WorldContextObject: null,
|
|
168
|
-
Command: `AssetRegistry.DumpAssets ${
|
|
177
|
+
Command: `AssetRegistry.DumpAssets ${normDir || '/Game'}`,
|
|
169
178
|
SpecificPlayer: null
|
|
170
179
|
},
|
|
171
180
|
generateTransaction: false
|
|
@@ -186,6 +195,24 @@ except Exception as e:
|
|
|
186
195
|
}
|
|
187
196
|
// Include full asset list with details in the message
|
|
188
197
|
console.log('[list_assets] Formatting message - result has assets?', !!(result && result.assets), 'count:', result?.assets?.length);
|
|
198
|
+
if (result && result.assets && result.assets.length > 0) {
|
|
199
|
+
// If Python/HTTP gives only paths, generate Name from path
|
|
200
|
+
result.assets = result.assets.map((asset) => {
|
|
201
|
+
if (!asset.Name && asset.Path) {
|
|
202
|
+
const base = String(asset.Path).split('/').pop() || '';
|
|
203
|
+
const name = base.includes('.') ? base.split('.').pop() : base;
|
|
204
|
+
return { ...asset, Name: name };
|
|
205
|
+
}
|
|
206
|
+
return asset;
|
|
207
|
+
});
|
|
208
|
+
// Group assets by type for better organization
|
|
209
|
+
result.assets = result.assets.map((a) => {
|
|
210
|
+
if (a && !a.Path && a.AssetPath) {
|
|
211
|
+
return { ...a, Path: a.AssetPath, PackagePath: a.AssetPath.split('/').slice(0, -1).join('/') };
|
|
212
|
+
}
|
|
213
|
+
return a;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
189
216
|
if (result && result.assets && result.assets.length > 0) {
|
|
190
217
|
// Group assets by type for better organization
|
|
191
218
|
const assetsByType = {};
|
|
@@ -197,7 +224,7 @@ except Exception as e:
|
|
|
197
224
|
assetsByType[type].push(asset);
|
|
198
225
|
});
|
|
199
226
|
// Format output with proper structure
|
|
200
|
-
let assetDetails = `📁 Asset Directory: ${
|
|
227
|
+
let assetDetails = `📁 Asset Directory: ${normDir || '/Game'}\n`;
|
|
201
228
|
assetDetails += `📊 Total Assets: ${result.assets.length}\n\n`;
|
|
202
229
|
// Sort types alphabetically
|
|
203
230
|
const sortedTypes = Object.keys(assetsByType).sort();
|
|
@@ -223,7 +250,7 @@ except Exception as e:
|
|
|
223
250
|
// Also keep the structured data in the result for programmatic access
|
|
224
251
|
}
|
|
225
252
|
else {
|
|
226
|
-
message = `No assets found in ${
|
|
253
|
+
message = `No assets found in ${normDir || '/Game'}`;
|
|
227
254
|
}
|
|
228
255
|
break;
|
|
229
256
|
case 'import_asset':
|
|
@@ -529,7 +556,13 @@ print(f"RESULT:{json.dumps(result)}")
|
|
|
529
556
|
// Landscape Tools
|
|
530
557
|
case 'create_landscape':
|
|
531
558
|
result = await tools.landscapeTools.createLandscape(args);
|
|
532
|
-
|
|
559
|
+
// Never claim success unless the tool says so; prefer error/message from UE/Python
|
|
560
|
+
if (result && typeof result === 'object') {
|
|
561
|
+
message = result.message || result.error || (result.success ? `Landscape ${args.name} created` : `Failed to create landscape ${args.name}`);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
message = `Failed to create landscape ${args.name}`;
|
|
565
|
+
}
|
|
533
566
|
break;
|
|
534
567
|
case 'sculpt_landscape':
|
|
535
568
|
result = await tools.landscapeTools.sculptLandscape(args);
|
package/dist/unreal-bridge.js
CHANGED
|
@@ -191,7 +191,7 @@ print(f"RESULT:{{'success': {saved}, 'message': 'All dirty packages saved'}}")
|
|
|
191
191
|
try {
|
|
192
192
|
this.ws.terminate(); // Use terminate instead of close for immediate cleanup
|
|
193
193
|
}
|
|
194
|
-
catch (
|
|
194
|
+
catch (_e) {
|
|
195
195
|
// Ignore close errors
|
|
196
196
|
}
|
|
197
197
|
}
|
|
@@ -218,7 +218,7 @@ print(f"RESULT:{{'success': {saved}, 'message': 'All dirty packages saved'}}")
|
|
|
218
218
|
this.ws.terminate();
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
catch (
|
|
221
|
+
catch (_e) {
|
|
222
222
|
// Ignore close errors
|
|
223
223
|
}
|
|
224
224
|
this.ws = undefined;
|
|
@@ -29,8 +29,8 @@ export class ResponseValidator {
|
|
|
29
29
|
this.validators.set(toolName, validator);
|
|
30
30
|
log.info(`Registered output schema for tool: ${toolName}`);
|
|
31
31
|
}
|
|
32
|
-
catch (
|
|
33
|
-
log.error(`Failed to compile output schema for ${toolName}:`,
|
|
32
|
+
catch (_error) {
|
|
33
|
+
log.error(`Failed to compile output schema for ${toolName}:`, _error);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
@@ -86,7 +86,7 @@ export class ResponseValidator {
|
|
|
86
86
|
JSON.stringify(response);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
catch (
|
|
89
|
+
catch (_error) {
|
|
90
90
|
log.error(`Response for ${toolName} contains circular references, cleaning...`);
|
|
91
91
|
response = cleanObject(response);
|
|
92
92
|
}
|
package/dist/utils/safe-json.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Utility to safely serialize objects with circular references
|
|
2
2
|
export function safeStringify(obj, space) {
|
|
3
3
|
const seen = new WeakSet();
|
|
4
|
-
return JSON.stringify(obj, (
|
|
4
|
+
return JSON.stringify(obj, (_key, value) => {
|
|
5
5
|
// Handle undefined, functions, symbols
|
|
6
6
|
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
|
|
7
7
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unreal-engine-mcp-server",
|
|
3
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Production-ready MCP server for Unreal Engine integration with consolidated and individual tool modes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,16 +11,11 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
|
-
"test:live": "node test-introspection-live.js",
|
|
15
14
|
"dev": "ts-node-esm src/index.ts",
|
|
16
15
|
"lint": "eslint . --ext .ts",
|
|
17
16
|
"lint:fix": "eslint . --ext .ts --fix",
|
|
18
17
|
"clean": "rimraf dist",
|
|
19
|
-
"prepare": "npm run build"
|
|
20
|
-
"test": "vitest run",
|
|
21
|
-
"test:watch": "vitest",
|
|
22
|
-
"test:coverage": "vitest run --coverage",
|
|
23
|
-
"test:tool4": "vitest run src/tests/consolidated-tool-4.test.ts"
|
|
18
|
+
"prepare": "npm run build"
|
|
24
19
|
},
|
|
25
20
|
"engines": {
|
|
26
21
|
"node": ">=18"
|
|
@@ -52,12 +47,10 @@
|
|
|
52
47
|
"@types/ws": "^8.5.10",
|
|
53
48
|
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
54
49
|
"@typescript-eslint/parser": "^8.43.0",
|
|
55
|
-
"@vitest/coverage-v8": "^1.6.0",
|
|
56
50
|
"cross-env": "^10.0.0",
|
|
57
51
|
"eslint": "^8.57.0",
|
|
58
52
|
"rimraf": "^6.0.1",
|
|
59
53
|
"ts-node": "^10.9.2",
|
|
60
|
-
"typescript": "^5.4.5"
|
|
61
|
-
"vitest": "^1.6.0"
|
|
54
|
+
"typescript": "^5.4.5"
|
|
62
55
|
}
|
|
63
56
|
}
|
package/server.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
3
3
|
"name": "io.github.ChiR24/unreal-engine-mcp",
|
|
4
4
|
"description": "Production-ready MCP server for Unreal Engine with comprehensive game development tools",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"packages": [
|
|
7
7
|
{
|
|
8
8
|
"registry_type": "npm",
|
|
9
9
|
"registry_base_url": "https://registry.npmjs.org",
|
|
10
10
|
"identifier": "unreal-engine-mcp-server",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.3.0",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|
package/src/index.ts
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import { responseValidator } from './utils/response-validator.js';
|
|
44
44
|
import { ErrorHandler } from './utils/error-handler.js';
|
|
45
45
|
import { routeStdoutLogsToStderr } from './utils/stdio-redirect.js';
|
|
46
|
-
import {
|
|
46
|
+
import { cleanObject } from './utils/safe-json.js';
|
|
47
47
|
|
|
48
48
|
const log = new Logger('UE-MCP');
|
|
49
49
|
|
|
@@ -77,14 +77,14 @@ const metrics: PerformanceMetrics = {
|
|
|
77
77
|
|
|
78
78
|
// Configuration
|
|
79
79
|
const CONFIG = {
|
|
80
|
-
// Tool mode: true = consolidated (
|
|
80
|
+
// Tool mode: true = consolidated (13 tools), false = individual (36+ tools)
|
|
81
81
|
USE_CONSOLIDATED_TOOLS: process.env.USE_CONSOLIDATED_TOOLS !== 'false',
|
|
82
82
|
// Connection retry settings
|
|
83
83
|
MAX_RETRY_ATTEMPTS: 3,
|
|
84
84
|
RETRY_DELAY_MS: 2000,
|
|
85
85
|
// Server info
|
|
86
86
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
87
|
-
SERVER_VERSION: '0.
|
|
87
|
+
SERVER_VERSION: '0.3.0',
|
|
88
88
|
// Monitoring
|
|
89
89
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
90
90
|
};
|
|
@@ -382,7 +382,7 @@ export async function createServer() {
|
|
|
382
382
|
throw new Error(`Unknown resource: ${uri}`);
|
|
383
383
|
});
|
|
384
384
|
|
|
385
|
-
// Handle tool listing - switch between consolidated (
|
|
385
|
+
// Handle tool listing - switch between consolidated (13) or individual (36) tools
|
|
386
386
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
387
387
|
log.info(`Serving ${CONFIG.USE_CONSOLIDATED_TOOLS ? 'consolidated' : 'individual'} tools`);
|
|
388
388
|
return {
|
|
@@ -390,7 +390,7 @@ export async function createServer() {
|
|
|
390
390
|
};
|
|
391
391
|
});
|
|
392
392
|
|
|
393
|
-
// Handle tool calls - switch between consolidated (
|
|
393
|
+
// Handle tool calls - switch between consolidated (13) or individual (36) tools
|
|
394
394
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
395
395
|
const { name, arguments: args } = request.params;
|
|
396
396
|
const startTime = Date.now();
|
package/src/resources/assets.ts
CHANGED
|
@@ -10,10 +10,10 @@ export class AssetResources {
|
|
|
10
10
|
return page !== undefined ? `${dir}::${recursive ? 1 : 0}::${page}` : `${dir}::${recursive ? 1 : 0}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
async list(dir = '/Game',
|
|
13
|
+
async list(dir = '/Game', _recursive = false, limit = 50) {
|
|
14
14
|
// ALWAYS use non-recursive listing to show only immediate children
|
|
15
15
|
// This prevents timeouts and makes navigation clearer
|
|
16
|
-
|
|
16
|
+
_recursive = false; // Force non-recursive
|
|
17
17
|
|
|
18
18
|
// Cache fast-path
|
|
19
19
|
try {
|
|
@@ -105,7 +105,7 @@ export class AssetResources {
|
|
|
105
105
|
* Directory-based listing for paths with too many assets
|
|
106
106
|
* Shows only immediate children (folders and files) to avoid timeouts
|
|
107
107
|
*/
|
|
108
|
-
private async listDirectoryOnly(dir: string,
|
|
108
|
+
private async listDirectoryOnly(dir: string, _recursive: boolean, limit: number) {
|
|
109
109
|
// Always return only immediate children to avoid timeout and improve navigation
|
|
110
110
|
try {
|
|
111
111
|
const py = `
|