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.
@@ -3,7 +3,18 @@ export const toolDefinitions = [
3
3
  // Asset Tools
4
4
  {
5
5
  name: 'list_assets',
6
- description: 'List all assets in a directory',
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: 'Import an asset from file system',
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: 'Spawn a new actor in the level',
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: 'Delete an actor from the level',
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: 'Create a new material asset',
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: 'Apply a material to an actor in the level',
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: 'Start Play In Editor (PIE) mode',
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: 'Stop Play In Editor (PIE) mode',
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: 'Set viewport camera position and rotation',
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: 'Create an animation blueprint',
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: 'Play an animation montage on an actor',
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: 'Setup ragdoll physics for a skeletal mesh',
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: 'Apply force to an actor',
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: 'Create a Niagara particle effect',
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: 'Spawn a Niagara system in the level',
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: 'Create a new blueprint',
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: 'Add a component to a blueprint',
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: 'Load a level',
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: 'Save the current level',
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: 'Stream a level in or out',
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: 'Create a light in the level',
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: 'Build lighting for the current level',
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: 'Create a new landscape',
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 the landscape',
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: 'Add a foliage type',
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:', args.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 = '${args.directory || '/Game'}'
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: [args.directory || '/Game'],
145
- RecursivePaths: false, // Always non-recursive
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 ${args.directory || '/Game'}`,
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: ${args.directory || '/Game'}\n`;
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 ${args.directory || '/Game'}`;
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
- message = result.message || `Landscape ${args.name} created`;
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);
@@ -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 (e) {
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 (e) {
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 (error) {
33
- log.error(`Failed to compile output schema for ${toolName}:`, error);
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 (error) {
89
+ catch (_error) {
90
90
  log.error(`Response for ${toolName} contains circular references, cleaning...`);
91
91
  response = cleanObject(response);
92
92
  }
@@ -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, (key, value) => {
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
- "version": "0.2.1",
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.2.1",
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.2.1",
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 { sanitizeResponse, cleanObject } from './utils/safe-json.js';
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 (10 tools), false = individual (36+ tools)
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.2.1',
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 (10) or individual (36) tools
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 (10) or individual (36) tools
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();
@@ -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', recursive = false, limit = 50) {
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
- recursive = false; // Force non-recursive
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, recursive: boolean, limit: number) {
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 = `