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.
@@ -86,16 +86,24 @@ export async function handleToolCall(
86
86
  break;
87
87
  }
88
88
 
89
+ // Normalize virtual content path: map /Content -> /Game (case-insensitive)
90
+ const rawDir: string = String(args.directory).trim();
91
+ let normDir = rawDir.replace(/^\/?content(\/|$)/i, '/Game$1');
92
+ // Ensure leading slash
93
+ if (!normDir.startsWith('/')) normDir = '/' + normDir;
94
+ // Collapse duplicate slashes
95
+ normDir = normDir.replace(/\\+/g, '/').replace(/\/+/g, '/');
96
+
89
97
  // Try multiple approaches to list assets
90
98
  try {
91
- console.log('[list_assets] Starting asset listing for directory:', args.directory);
99
+ console.log('[list_assets] Starting asset listing for directory:', normDir);
92
100
 
93
- // First try: Use Python for most reliable listing
101
+ // First try: Use Python for most reliable listing (recursive if /Game)
94
102
  const pythonCode = `
95
103
  import unreal
96
104
  import json
97
105
 
98
- directory = '${args.directory || '/Game'}'
106
+ directory = '${normDir || '/Game'}'
99
107
  # Use recursive for /Game to find assets in subdirectories, but limit depth
100
108
  recursive = True if directory == '/Game' else False
101
109
 
@@ -197,8 +205,9 @@ except Exception as e:
197
205
  const searchResult = await tools.bridge.httpCall('/remote/search/assets', 'PUT', {
198
206
  Query: '', // Empty query to match all (wildcard doesn't work)
199
207
  Filter: {
200
- PackagePaths: [args.directory || '/Game'],
201
- RecursivePaths: false, // Always non-recursive
208
+ PackagePaths: [normDir || '/Game'],
209
+ // Recursively search so we actually find assets in subfolders
210
+ RecursivePaths: true,
202
211
  ClassNames: [], // Empty to get all types
203
212
  RecursiveClasses: true
204
213
  },
@@ -222,7 +231,7 @@ except Exception as e:
222
231
  functionName: 'ExecuteConsoleCommand',
223
232
  parameters: {
224
233
  WorldContextObject: null,
225
- Command: `AssetRegistry.DumpAssets ${args.directory || '/Game'}`,
234
+ Command: `AssetRegistry.DumpAssets ${normDir || '/Game'}`,
226
235
  SpecificPlayer: null
227
236
  },
228
237
  generateTransaction: false
@@ -244,6 +253,24 @@ except Exception as e:
244
253
 
245
254
  // Include full asset list with details in the message
246
255
  console.log('[list_assets] Formatting message - result has assets?', !!(result && result.assets), 'count:', result?.assets?.length);
256
+ if (result && result.assets && result.assets.length > 0) {
257
+ // If Python/HTTP gives only paths, generate Name from path
258
+ result.assets = result.assets.map((asset: any) => {
259
+ if (!asset.Name && asset.Path) {
260
+ const base = String(asset.Path).split('/').pop() || '';
261
+ const name = base.includes('.') ? base.split('.').pop() : base;
262
+ return { ...asset, Name: name };
263
+ }
264
+ return asset;
265
+ });
266
+ // Group assets by type for better organization
267
+ result.assets = result.assets.map((a: any) => {
268
+ if (a && !a.Path && a.AssetPath) {
269
+ return { ...a, Path: a.AssetPath, PackagePath: a.AssetPath.split('/').slice(0, -1).join('/') };
270
+ }
271
+ return a;
272
+ });
273
+ }
247
274
  if (result && result.assets && result.assets.length > 0) {
248
275
  // Group assets by type for better organization
249
276
  const assetsByType: { [key: string]: any[] } = {};
@@ -257,7 +284,7 @@ except Exception as e:
257
284
  });
258
285
 
259
286
  // Format output with proper structure
260
- let assetDetails = `📁 Asset Directory: ${args.directory || '/Game'}\n`;
287
+ let assetDetails = `📁 Asset Directory: ${normDir || '/Game'}\n`;
261
288
  assetDetails += `📊 Total Assets: ${result.assets.length}\n\n`;
262
289
 
263
290
  // Sort types alphabetically
@@ -287,7 +314,7 @@ except Exception as e:
287
314
 
288
315
  // Also keep the structured data in the result for programmatic access
289
316
  } else {
290
- message = `No assets found in ${args.directory || '/Game'}`;
317
+ message = `No assets found in ${normDir || '/Game'}`;
291
318
  }
292
319
  break;
293
320
 
@@ -611,7 +638,12 @@ print(f"RESULT:{json.dumps(result)}")
611
638
  // Landscape Tools
612
639
  case 'create_landscape':
613
640
  result = await tools.landscapeTools.createLandscape(args);
614
- message = result.message || `Landscape ${args.name} created`;
641
+ // Never claim success unless the tool says so; prefer error/message from UE/Python
642
+ if (result && typeof result === 'object') {
643
+ message = result.message || result.error || (result.success ? `Landscape ${args.name} created` : `Failed to create landscape ${args.name}`);
644
+ } else {
645
+ message = `Failed to create landscape ${args.name}`;
646
+ }
615
647
  break;
616
648
 
617
649
  case 'sculpt_landscape':
@@ -226,7 +226,7 @@ print(f"RESULT:{{'success': {saved}, 'message': 'All dirty packages saved'}}")
226
226
  if (this.ws.readyState === WebSocket.CONNECTING) {
227
227
  try {
228
228
  this.ws.terminate(); // Use terminate instead of close for immediate cleanup
229
- } catch (e) {
229
+ } catch (_e) {
230
230
  // Ignore close errors
231
231
  }
232
232
  }
@@ -254,7 +254,7 @@ print(f"RESULT:{{'success': {saved}, 'message': 'All dirty packages saved'}}")
254
254
  if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
255
255
  this.ws.terminate();
256
256
  }
257
- } catch (e) {
257
+ } catch (_e) {
258
258
  // Ignore close errors
259
259
  }
260
260
  this.ws = undefined;
@@ -33,8 +33,8 @@ export class ResponseValidator {
33
33
  const validator = this.ajv.compile(outputSchema);
34
34
  this.validators.set(toolName, validator);
35
35
  log.info(`Registered output schema for tool: ${toolName}`);
36
- } catch (error) {
37
- log.error(`Failed to compile output schema for ${toolName}:`, error);
36
+ } catch (_error) {
37
+ log.error(`Failed to compile output schema for ${toolName}:`, _error);
38
38
  }
39
39
  }
40
40
 
@@ -104,7 +104,7 @@ export class ResponseValidator {
104
104
  // Make sure we can serialize it
105
105
  JSON.stringify(response);
106
106
  }
107
- } catch (error) {
107
+ } catch (_error) {
108
108
  log.error(`Response for ${toolName} contains circular references, cleaning...`);
109
109
  response = cleanObject(response);
110
110
  }
@@ -2,7 +2,7 @@
2
2
  export function safeStringify(obj: any, space?: number): string {
3
3
  const seen = new WeakSet();
4
4
 
5
- return JSON.stringify(obj, (key, value) => {
5
+ return JSON.stringify(obj, (_key, value) => {
6
6
  // Handle undefined, functions, symbols
7
7
  if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
8
8
  return undefined;