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
|
@@ -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:',
|
|
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 = '${
|
|
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: [
|
|
201
|
-
|
|
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 ${
|
|
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: ${
|
|
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 ${
|
|
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
|
-
|
|
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':
|
package/src/unreal-bridge.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 (
|
|
37
|
-
log.error(`Failed to compile output schema for ${toolName}:`,
|
|
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 (
|
|
107
|
+
} catch (_error) {
|
|
108
108
|
log.error(`Response for ${toolName} contains circular references, cleaning...`);
|
|
109
109
|
response = cleanObject(response);
|
|
110
110
|
}
|
package/src/utils/safe-json.ts
CHANGED
|
@@ -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, (
|
|
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;
|