blueprint-extractor-mcp 1.5.0 → 1.8.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/dist/index.js +546 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { compactBlueprint } from './compactor.js';
|
|
|
7
7
|
const client = new UEClient();
|
|
8
8
|
const server = new McpServer({
|
|
9
9
|
name: 'blueprint-extractor',
|
|
10
|
-
version: '1.
|
|
10
|
+
version: '1.8.0',
|
|
11
11
|
});
|
|
12
12
|
// Shared scope enum with detailed descriptions
|
|
13
13
|
const scopeEnum = z.enum([
|
|
@@ -18,6 +18,14 @@ const scopeEnum = z.enum([
|
|
|
18
18
|
'Full',
|
|
19
19
|
'FullWithBytecode',
|
|
20
20
|
]);
|
|
21
|
+
const cascadeManifestEntrySchema = z.object({
|
|
22
|
+
assetPath: z.string(),
|
|
23
|
+
assetType: z.string(),
|
|
24
|
+
outputFile: z.string().optional(),
|
|
25
|
+
depth: z.number().int().min(0),
|
|
26
|
+
status: z.string(),
|
|
27
|
+
error: z.string().optional(),
|
|
28
|
+
});
|
|
21
29
|
// Resource: extraction scope reference (static docs — app-controlled read-only context)
|
|
22
30
|
server.resource('extraction-scopes', 'blueprint://scopes', {
|
|
23
31
|
description: 'Reference for Blueprint extraction scopes: what each level includes, typical sizes, and when to use.',
|
|
@@ -210,24 +218,370 @@ RETURNS: JSON object with row struct info, schema, and all rows.`,
|
|
|
210
218
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
211
219
|
}
|
|
212
220
|
});
|
|
213
|
-
// Tool 5:
|
|
221
|
+
// Tool 5: extract_behavior_tree
|
|
222
|
+
server.registerTool('extract_behavior_tree', {
|
|
223
|
+
title: 'Extract BehaviorTree',
|
|
224
|
+
description: `Extract a UE5 BehaviorTree asset to structured JSON.
|
|
225
|
+
|
|
226
|
+
USAGE GUIDELINES:
|
|
227
|
+
- Use search_assets first with class_filter "BehaviorTree" if you need to discover the asset path.
|
|
228
|
+
- Returns the full node hierarchy including root decorators, child decorators, decorator logic, services, task/composite nodes, and the linked blackboard asset.
|
|
229
|
+
- Useful for understanding AI decision flow without opening the editor graph.
|
|
230
|
+
|
|
231
|
+
RETURNS: JSON object with the BehaviorTree hierarchy, node properties, and blackboard reference.`,
|
|
232
|
+
inputSchema: {
|
|
233
|
+
asset_path: z.string().describe('UE content path to the BehaviorTree asset (e.g. /Game/AI/BT_MainAI). Use search_assets to find paths.'),
|
|
234
|
+
},
|
|
235
|
+
annotations: {
|
|
236
|
+
title: 'Extract BehaviorTree',
|
|
237
|
+
readOnlyHint: true,
|
|
238
|
+
destructiveHint: false,
|
|
239
|
+
idempotentHint: true,
|
|
240
|
+
openWorldHint: false,
|
|
241
|
+
},
|
|
242
|
+
}, async ({ asset_path }) => {
|
|
243
|
+
try {
|
|
244
|
+
const result = await client.callSubsystem('ExtractBehaviorTree', { AssetPath: asset_path });
|
|
245
|
+
const parsed = JSON.parse(result);
|
|
246
|
+
if (parsed.error) {
|
|
247
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
248
|
+
}
|
|
249
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
// Tool 6: extract_blackboard
|
|
256
|
+
server.registerTool('extract_blackboard', {
|
|
257
|
+
title: 'Extract Blackboard',
|
|
258
|
+
description: `Extract a UE5 Blackboard asset to structured JSON.
|
|
259
|
+
|
|
260
|
+
USAGE GUIDELINES:
|
|
261
|
+
- Use search_assets first with class_filter "Blackboard" or "BlackboardData" to find the asset path.
|
|
262
|
+
- Returns the effective key list, including inherited parent keys and local overrides.
|
|
263
|
+
- Key entries include type information and key-type-specific properties such as base class or enum binding.
|
|
264
|
+
|
|
265
|
+
RETURNS: JSON object with parent blackboard info and effective key definitions.`,
|
|
266
|
+
inputSchema: {
|
|
267
|
+
asset_path: z.string().describe('UE content path to the Blackboard asset (e.g. /Game/AI/BB_MainAI). Use search_assets to find paths.'),
|
|
268
|
+
},
|
|
269
|
+
annotations: {
|
|
270
|
+
title: 'Extract Blackboard',
|
|
271
|
+
readOnlyHint: true,
|
|
272
|
+
destructiveHint: false,
|
|
273
|
+
idempotentHint: true,
|
|
274
|
+
openWorldHint: false,
|
|
275
|
+
},
|
|
276
|
+
}, async ({ asset_path }) => {
|
|
277
|
+
try {
|
|
278
|
+
const result = await client.callSubsystem('ExtractBlackboard', { AssetPath: asset_path });
|
|
279
|
+
const parsed = JSON.parse(result);
|
|
280
|
+
if (parsed.error) {
|
|
281
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
282
|
+
}
|
|
283
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// Tool 7: extract_user_defined_struct
|
|
290
|
+
server.registerTool('extract_user_defined_struct', {
|
|
291
|
+
title: 'Extract UserDefinedStruct',
|
|
292
|
+
description: `Extract a UE5 UserDefinedStruct asset to structured JSON.
|
|
293
|
+
|
|
294
|
+
USAGE GUIDELINES:
|
|
295
|
+
- Use search_assets first with class_filter "UserDefinedStruct" to find the asset path.
|
|
296
|
+
- Returns field metadata, pin types, struct status, GUID, and typed default values from the struct default instance.
|
|
297
|
+
- Useful when DataTables or Blueprint variables depend on project-defined struct schemas.
|
|
298
|
+
|
|
299
|
+
RETURNS: JSON object with struct metadata and field definitions.`,
|
|
300
|
+
inputSchema: {
|
|
301
|
+
asset_path: z.string().describe('UE content path to the UserDefinedStruct asset (e.g. /Game/Data/S_ItemData). Use search_assets to find paths.'),
|
|
302
|
+
},
|
|
303
|
+
annotations: {
|
|
304
|
+
title: 'Extract UserDefinedStruct',
|
|
305
|
+
readOnlyHint: true,
|
|
306
|
+
destructiveHint: false,
|
|
307
|
+
idempotentHint: true,
|
|
308
|
+
openWorldHint: false,
|
|
309
|
+
},
|
|
310
|
+
}, async ({ asset_path }) => {
|
|
311
|
+
try {
|
|
312
|
+
const result = await client.callSubsystem('ExtractUserDefinedStruct', { AssetPath: asset_path });
|
|
313
|
+
const parsed = JSON.parse(result);
|
|
314
|
+
if (parsed.error) {
|
|
315
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
316
|
+
}
|
|
317
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// Tool 8: extract_user_defined_enum
|
|
324
|
+
server.registerTool('extract_user_defined_enum', {
|
|
325
|
+
title: 'Extract UserDefinedEnum',
|
|
326
|
+
description: `Extract a UE5 UserDefinedEnum asset to structured JSON.
|
|
327
|
+
|
|
328
|
+
USAGE GUIDELINES:
|
|
329
|
+
- Use search_assets first with class_filter "UserDefinedEnum" to find the asset path.
|
|
330
|
+
- Returns the enum entries, display names, and numeric values, excluding the auto-generated MAX sentinel.
|
|
331
|
+
- Useful when gameplay data, DataAssets, or Blueprint logic refer to project enums.
|
|
332
|
+
|
|
333
|
+
RETURNS: JSON object with enum metadata and entry list.`,
|
|
334
|
+
inputSchema: {
|
|
335
|
+
asset_path: z.string().describe('UE content path to the UserDefinedEnum asset (e.g. /Game/Data/E_ItemRarity). Use search_assets to find paths.'),
|
|
336
|
+
},
|
|
337
|
+
annotations: {
|
|
338
|
+
title: 'Extract UserDefinedEnum',
|
|
339
|
+
readOnlyHint: true,
|
|
340
|
+
destructiveHint: false,
|
|
341
|
+
idempotentHint: true,
|
|
342
|
+
openWorldHint: false,
|
|
343
|
+
},
|
|
344
|
+
}, async ({ asset_path }) => {
|
|
345
|
+
try {
|
|
346
|
+
const result = await client.callSubsystem('ExtractUserDefinedEnum', { AssetPath: asset_path });
|
|
347
|
+
const parsed = JSON.parse(result);
|
|
348
|
+
if (parsed.error) {
|
|
349
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
350
|
+
}
|
|
351
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
// Tool 9: extract_curve
|
|
358
|
+
server.registerTool('extract_curve', {
|
|
359
|
+
title: 'Extract Curve',
|
|
360
|
+
description: `Extract a UE5 curve asset to structured JSON.
|
|
361
|
+
|
|
362
|
+
USAGE GUIDELINES:
|
|
363
|
+
- Use search_assets first with class_filter "Curve" to find curve assets such as CurveFloat, CurveVector, or CurveLinearColor.
|
|
364
|
+
- Returns per-channel keys, tangents, interpolation modes, and default/extrapolation settings.
|
|
365
|
+
- Useful for gameplay tuning curves, UI animation curves, and authored scalar/vector ramps.
|
|
366
|
+
|
|
367
|
+
RETURNS: JSON object with curve type and channel key data.`,
|
|
368
|
+
inputSchema: {
|
|
369
|
+
asset_path: z.string().describe('UE content path to the curve asset (e.g. /Game/Data/C_DamageOverTime). Use search_assets to find paths.'),
|
|
370
|
+
},
|
|
371
|
+
annotations: {
|
|
372
|
+
title: 'Extract Curve',
|
|
373
|
+
readOnlyHint: true,
|
|
374
|
+
destructiveHint: false,
|
|
375
|
+
idempotentHint: true,
|
|
376
|
+
openWorldHint: false,
|
|
377
|
+
},
|
|
378
|
+
}, async ({ asset_path }) => {
|
|
379
|
+
try {
|
|
380
|
+
const result = await client.callSubsystem('ExtractCurve', { AssetPath: asset_path });
|
|
381
|
+
const parsed = JSON.parse(result);
|
|
382
|
+
if (parsed.error) {
|
|
383
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
384
|
+
}
|
|
385
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
// Tool 10: extract_curvetable
|
|
392
|
+
server.registerTool('extract_curvetable', {
|
|
393
|
+
title: 'Extract CurveTable',
|
|
394
|
+
description: `Extract a UE5 CurveTable asset to structured JSON.
|
|
395
|
+
|
|
396
|
+
USAGE GUIDELINES:
|
|
397
|
+
- Use search_assets first with class_filter "CurveTable" to find the asset path.
|
|
398
|
+
- Returns row names plus the per-row curve data for rich or simple curve tables.
|
|
399
|
+
- Useful for difficulty scaling, balance curves, and time/value tables authored in spreadsheet form.
|
|
400
|
+
|
|
401
|
+
RETURNS: JSON object with curve table mode and all rows.`,
|
|
402
|
+
inputSchema: {
|
|
403
|
+
asset_path: z.string().describe('UE content path to the CurveTable asset (e.g. /Game/Data/CT_DifficultyScaling). Use search_assets to find paths.'),
|
|
404
|
+
},
|
|
405
|
+
annotations: {
|
|
406
|
+
title: 'Extract CurveTable',
|
|
407
|
+
readOnlyHint: true,
|
|
408
|
+
destructiveHint: false,
|
|
409
|
+
idempotentHint: true,
|
|
410
|
+
openWorldHint: false,
|
|
411
|
+
},
|
|
412
|
+
}, async ({ asset_path }) => {
|
|
413
|
+
try {
|
|
414
|
+
const result = await client.callSubsystem('ExtractCurveTable', { AssetPath: asset_path });
|
|
415
|
+
const parsed = JSON.parse(result);
|
|
416
|
+
if (parsed.error) {
|
|
417
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
418
|
+
}
|
|
419
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
// Tool 11: extract_material_instance
|
|
426
|
+
server.registerTool('extract_material_instance', {
|
|
427
|
+
title: 'Extract MaterialInstance',
|
|
428
|
+
description: `Extract a UE5 MaterialInstance asset to structured JSON.
|
|
429
|
+
|
|
430
|
+
USAGE GUIDELINES:
|
|
431
|
+
- Use search_assets first with class_filter "MaterialInstance" to find the asset path.
|
|
432
|
+
- Returns the parent material chain, base material, scalar/vector/texture parameters, runtime virtual texture parameters, font parameters, and static switch states.
|
|
433
|
+
- Useful for understanding authored look-dev overrides without opening the material editor.
|
|
434
|
+
|
|
435
|
+
RETURNS: JSON object with effective MaterialInstance parameter values.`,
|
|
436
|
+
inputSchema: {
|
|
437
|
+
asset_path: z.string().describe('UE content path to the MaterialInstance asset (e.g. /Game/Materials/MI_Character_Skin). Use search_assets to find paths.'),
|
|
438
|
+
},
|
|
439
|
+
annotations: {
|
|
440
|
+
title: 'Extract MaterialInstance',
|
|
441
|
+
readOnlyHint: true,
|
|
442
|
+
destructiveHint: false,
|
|
443
|
+
idempotentHint: true,
|
|
444
|
+
openWorldHint: false,
|
|
445
|
+
},
|
|
446
|
+
}, async ({ asset_path }) => {
|
|
447
|
+
try {
|
|
448
|
+
const result = await client.callSubsystem('ExtractMaterialInstance', { AssetPath: asset_path });
|
|
449
|
+
const parsed = JSON.parse(result);
|
|
450
|
+
if (parsed.error) {
|
|
451
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
452
|
+
}
|
|
453
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
// Tool 12: extract_anim_sequence
|
|
460
|
+
server.registerTool('extract_anim_sequence', {
|
|
461
|
+
title: 'Extract AnimSequence',
|
|
462
|
+
description: `Extract a UE5 AnimSequence asset to structured JSON.
|
|
463
|
+
|
|
464
|
+
USAGE GUIDELINES:
|
|
465
|
+
- Use search_assets first with class_filter "AnimSequence" to find the asset path.
|
|
466
|
+
- Returns runtime-stable animation data: length, sample count, sampling rate, additive settings, notifies, authored sync markers, and runtime curve tracks.
|
|
467
|
+
- Useful for inspecting authored animation events and metadata without touching editor-only data models.
|
|
468
|
+
|
|
469
|
+
RETURNS: JSON object with AnimSequence metadata, notifies, sync markers, and curves.`,
|
|
470
|
+
inputSchema: {
|
|
471
|
+
asset_path: z.string().describe('UE content path to the AnimSequence asset (e.g. /Game/Animations/AS_Walk). Use search_assets to find paths.'),
|
|
472
|
+
},
|
|
473
|
+
annotations: {
|
|
474
|
+
title: 'Extract AnimSequence',
|
|
475
|
+
readOnlyHint: true,
|
|
476
|
+
destructiveHint: false,
|
|
477
|
+
idempotentHint: true,
|
|
478
|
+
openWorldHint: false,
|
|
479
|
+
},
|
|
480
|
+
}, async ({ asset_path }) => {
|
|
481
|
+
try {
|
|
482
|
+
const result = await client.callSubsystem('ExtractAnimSequence', { AssetPath: asset_path });
|
|
483
|
+
const parsed = JSON.parse(result);
|
|
484
|
+
if (parsed.error) {
|
|
485
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
486
|
+
}
|
|
487
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
488
|
+
}
|
|
489
|
+
catch (e) {
|
|
490
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
// Tool 13: extract_anim_montage
|
|
494
|
+
server.registerTool('extract_anim_montage', {
|
|
495
|
+
title: 'Extract AnimMontage',
|
|
496
|
+
description: `Extract a UE5 AnimMontage asset to structured JSON.
|
|
497
|
+
|
|
498
|
+
USAGE GUIDELINES:
|
|
499
|
+
- Use search_assets first with class_filter "AnimMontage" to find the asset path.
|
|
500
|
+
- Returns slot tracks, animation segments, montage sections, branching-point notifies, and standard notifies.
|
|
501
|
+
- Useful for understanding combat, traversal, and layered animation sequencing.
|
|
502
|
+
|
|
503
|
+
RETURNS: JSON object with montage structure, slots, sections, and notify data.`,
|
|
504
|
+
inputSchema: {
|
|
505
|
+
asset_path: z.string().describe('UE content path to the AnimMontage asset (e.g. /Game/Animations/AM_Attack). Use search_assets to find paths.'),
|
|
506
|
+
},
|
|
507
|
+
annotations: {
|
|
508
|
+
title: 'Extract AnimMontage',
|
|
509
|
+
readOnlyHint: true,
|
|
510
|
+
destructiveHint: false,
|
|
511
|
+
idempotentHint: true,
|
|
512
|
+
openWorldHint: false,
|
|
513
|
+
},
|
|
514
|
+
}, async ({ asset_path }) => {
|
|
515
|
+
try {
|
|
516
|
+
const result = await client.callSubsystem('ExtractAnimMontage', { AssetPath: asset_path });
|
|
517
|
+
const parsed = JSON.parse(result);
|
|
518
|
+
if (parsed.error) {
|
|
519
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
520
|
+
}
|
|
521
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
// Tool 14: extract_blend_space
|
|
528
|
+
server.registerTool('extract_blend_space', {
|
|
529
|
+
title: 'Extract BlendSpace',
|
|
530
|
+
description: `Extract a UE5 BlendSpace asset to structured JSON.
|
|
531
|
+
|
|
532
|
+
USAGE GUIDELINES:
|
|
533
|
+
- Use search_assets first with class_filter "BlendSpace" to find the asset path.
|
|
534
|
+
- Returns axis definitions, sample count, sample coordinates, and referenced animations for 1D or 2D blend spaces.
|
|
535
|
+
- Useful for locomotion, aim offset, and directional blending analysis.
|
|
536
|
+
|
|
537
|
+
RETURNS: JSON object with BlendSpace axes and sample definitions.`,
|
|
538
|
+
inputSchema: {
|
|
539
|
+
asset_path: z.string().describe('UE content path to the BlendSpace asset (e.g. /Game/Animations/BS_Locomotion). Use search_assets to find paths.'),
|
|
540
|
+
},
|
|
541
|
+
annotations: {
|
|
542
|
+
title: 'Extract BlendSpace',
|
|
543
|
+
readOnlyHint: true,
|
|
544
|
+
destructiveHint: false,
|
|
545
|
+
idempotentHint: true,
|
|
546
|
+
openWorldHint: false,
|
|
547
|
+
},
|
|
548
|
+
}, async ({ asset_path }) => {
|
|
549
|
+
try {
|
|
550
|
+
const result = await client.callSubsystem('ExtractBlendSpace', { AssetPath: asset_path });
|
|
551
|
+
const parsed = JSON.parse(result);
|
|
552
|
+
if (parsed.error) {
|
|
553
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
554
|
+
}
|
|
555
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
// Tool 15: extract_cascade
|
|
214
562
|
server.registerTool('extract_cascade', {
|
|
215
563
|
title: 'Extract Cascade',
|
|
216
|
-
description: `Extract multiple assets (Blueprint, AnimBlueprint, StateTree, DataAsset, DataTable) with automatic reference following for
|
|
564
|
+
description: `Extract multiple assets (Blueprint, AnimBlueprint, StateTree, BehaviorTree, Blackboard, DataAsset, DataTable, UserDefinedStruct, UserDefinedEnum, Curve, CurveTable, MaterialInstance, AnimSequence, AnimMontage, BlendSpace) with automatic reference following for supported dependency chains. Follows parent classes, interfaces, component classes, Blueprint references, blackboard links, material instance parents, and animation references up to max_depth levels deep.
|
|
217
565
|
|
|
218
566
|
USAGE GUIDELINES:
|
|
219
567
|
- Use when you need to understand an asset AND its dependencies (parent Blueprints, referenced Blueprints, etc.).
|
|
220
|
-
- Results are written to files on disk (in the project's configured output directory), NOT returned inline — the response
|
|
221
|
-
- For a single asset without dependencies, prefer
|
|
568
|
+
- Results are written to files on disk (in the project's configured output directory), NOT returned inline — the response contains a manifest summary with output filenames.
|
|
569
|
+
- For a single asset without dependencies, prefer the specific extract_* tool for that asset type.
|
|
222
570
|
- Cycle-safe: won't extract the same asset twice.
|
|
223
571
|
|
|
224
|
-
RETURNS: Summary with extracted_count and
|
|
572
|
+
RETURNS: Summary with extracted_count, output_directory path, and a per-asset manifest. Read the output files to inspect the data.`,
|
|
225
573
|
inputSchema: {
|
|
226
574
|
asset_paths: z.array(z.string()).describe('Array of UE content paths to extract (e.g. ["/Game/Blueprints/BP_Character", "/Game/Blueprints/BP_Weapon"])'),
|
|
227
575
|
scope: scopeEnum.default('Full').describe('Extraction depth applied to all assets. Full is the default since cascade is typically used for deep analysis.'),
|
|
228
576
|
max_depth: z.number().int().min(0).max(10).default(3).describe('How many levels deep to follow references (0 = only the listed assets, 3 = default)'),
|
|
229
577
|
graph_filter: z.array(z.string()).optional().describe('Filter to specific graphs by name. Use FunctionsShallow scope first to discover graph names, then pass the names you want here. Empty/omitted = extract all graphs. Example: ["EventGraph", "CalculateDamage"]'),
|
|
230
|
-
|
|
578
|
+
},
|
|
579
|
+
outputSchema: {
|
|
580
|
+
extracted_count: z.number().int().min(0),
|
|
581
|
+
skipped_count: z.number().int().min(0),
|
|
582
|
+
total_count: z.number().int().min(0),
|
|
583
|
+
output_directory: z.string(),
|
|
584
|
+
manifest: z.array(cascadeManifestEntrySchema),
|
|
231
585
|
},
|
|
232
586
|
annotations: {
|
|
233
587
|
title: 'Extract Cascade',
|
|
@@ -236,7 +590,7 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
236
590
|
idempotentHint: true,
|
|
237
591
|
openWorldHint: false,
|
|
238
592
|
},
|
|
239
|
-
}, async ({ asset_paths, scope, max_depth, graph_filter
|
|
593
|
+
}, async ({ asset_paths, scope, max_depth, graph_filter }) => {
|
|
240
594
|
try {
|
|
241
595
|
const result = await client.callSubsystem('ExtractCascade', {
|
|
242
596
|
AssetPathsJson: JSON.stringify(asset_paths),
|
|
@@ -248,7 +602,29 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
248
602
|
if (parsed.error) {
|
|
249
603
|
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
250
604
|
}
|
|
251
|
-
|
|
605
|
+
const manifest = Array.isArray(parsed.manifest)
|
|
606
|
+
? parsed.manifest
|
|
607
|
+
: Array.isArray(parsed.assets)
|
|
608
|
+
? parsed.assets
|
|
609
|
+
: [];
|
|
610
|
+
const totalCount = typeof parsed.total_count === 'number' ? parsed.total_count : manifest.length;
|
|
611
|
+
const extractedCount = typeof parsed.extracted_count === 'number'
|
|
612
|
+
? parsed.extracted_count
|
|
613
|
+
: manifest.filter((asset) => asset?.status === 'extracted').length;
|
|
614
|
+
const skippedCount = typeof parsed.skipped_count === 'number'
|
|
615
|
+
? parsed.skipped_count
|
|
616
|
+
: manifest.filter((asset) => asset?.status === 'skipped').length;
|
|
617
|
+
const structuredContent = {
|
|
618
|
+
extracted_count: extractedCount,
|
|
619
|
+
skipped_count: skippedCount,
|
|
620
|
+
total_count: totalCount,
|
|
621
|
+
output_directory: typeof parsed.output_directory === 'string' ? parsed.output_directory : '',
|
|
622
|
+
manifest,
|
|
623
|
+
};
|
|
624
|
+
return {
|
|
625
|
+
content: [{ type: 'text', text: JSON.stringify(structuredContent, null, 2) }],
|
|
626
|
+
structuredContent,
|
|
627
|
+
};
|
|
252
628
|
}
|
|
253
629
|
catch (e) {
|
|
254
630
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
@@ -257,17 +633,18 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
257
633
|
// Tool 6: search_assets
|
|
258
634
|
server.registerTool('search_assets', {
|
|
259
635
|
title: 'Search Assets',
|
|
260
|
-
description: `Search for UE5 assets by name. This is a lightweight lookup — use it FIRST to find correct asset paths before calling
|
|
636
|
+
description: `Search for UE5 assets by name. This is a lightweight lookup — use it FIRST to find correct asset paths before calling any extract_* tool.
|
|
261
637
|
|
|
262
638
|
USAGE GUIDELINES:
|
|
263
639
|
- Always call this before any extract_* tool if you don't already have the exact asset path.
|
|
264
640
|
- Searches asset names (not full paths) — partial matches work (e.g. "Character" finds "BP_Character").
|
|
265
|
-
- Filter by class to narrow results: "Blueprint" (default), "AnimBlueprint", "StateTree", "
|
|
641
|
+
- Filter by class to narrow results: "Blueprint" (default), "AnimBlueprint", "WidgetBlueprint", "StateTree", "BehaviorTree", "Blackboard", "DataAsset", "DataTable", "UserDefinedStruct", "UserDefinedEnum", "Curve", "CurveTable", "MaterialInstance", "AnimSequence", "AnimMontage", "BlendSpace", or empty string for all.
|
|
266
642
|
|
|
267
643
|
RETURNS: JSON array of objects with path, name, and class for each matching asset.`,
|
|
268
644
|
inputSchema: {
|
|
269
645
|
query: z.string().describe('Search term to match against asset names. Partial matches work (e.g. "Player" finds "BP_PlayerCharacter").'),
|
|
270
|
-
class_filter: z.string().default('Blueprint').describe('Filter by asset class. Common values: "Blueprint", "AnimBlueprint", "WidgetBlueprint", "StateTree", "
|
|
646
|
+
class_filter: z.string().default('Blueprint').describe('Filter by asset class. Common values: "Blueprint", "AnimBlueprint", "WidgetBlueprint", "StateTree", "BehaviorTree", "Blackboard", "DataAsset", "DataTable", "UserDefinedStruct", "UserDefinedEnum", "Curve", "CurveTable", "MaterialInstance", "AnimSequence", "AnimMontage", "BlendSpace", or "" for all asset types.'),
|
|
647
|
+
max_results: z.number().int().min(1).max(200).default(50).describe('Maximum number of results to return. Lower values keep the response small and the query fast.'),
|
|
271
648
|
},
|
|
272
649
|
annotations: {
|
|
273
650
|
title: 'Search Assets',
|
|
@@ -276,14 +653,14 @@ RETURNS: JSON array of objects with path, name, and class for each matching asse
|
|
|
276
653
|
idempotentHint: true,
|
|
277
654
|
openWorldHint: false,
|
|
278
655
|
},
|
|
279
|
-
}, async ({ query, class_filter }) => {
|
|
656
|
+
}, async ({ query, class_filter, max_results }) => {
|
|
280
657
|
try {
|
|
281
|
-
const result = await client.callSubsystem('SearchAssets', { Query: query, ClassFilter: class_filter });
|
|
658
|
+
const result = await client.callSubsystem('SearchAssets', { Query: query, ClassFilter: class_filter, MaxResults: max_results });
|
|
282
659
|
const parsed = JSON.parse(result);
|
|
283
660
|
if (parsed.error) {
|
|
284
661
|
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
285
662
|
}
|
|
286
|
-
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
663
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed.results ?? [], null, 2) }] };
|
|
287
664
|
}
|
|
288
665
|
catch (e) {
|
|
289
666
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
@@ -326,6 +703,160 @@ RETURNS: JSON array of objects with path, name, and class for each asset (and su
|
|
|
326
703
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
327
704
|
}
|
|
328
705
|
});
|
|
706
|
+
// Recursive schema for widget tree nodes (used by build_widget_tree)
|
|
707
|
+
const WidgetNodeSchema = z.lazy(() => z.object({
|
|
708
|
+
class: z.string().describe('Widget class name (e.g. CanvasPanel, TextBlock, CommonButtonBase, VerticalBox)'),
|
|
709
|
+
name: z.string().describe('Widget instance name (used for BindWidget matching)'),
|
|
710
|
+
is_variable: z.boolean().default(false).describe('Mark as variable for BindWidget access from C++'),
|
|
711
|
+
slot: z.record(z.string(), z.unknown()).optional().describe('Slot properties (type depends on parent panel)'),
|
|
712
|
+
properties: z.record(z.string(), z.unknown()).optional().describe('Widget UPROPERTY values to set'),
|
|
713
|
+
children: z.array(WidgetNodeSchema).optional().describe('Child widgets (only valid for panel widgets)'),
|
|
714
|
+
}));
|
|
715
|
+
// Tool 8: create_widget_blueprint
|
|
716
|
+
server.registerTool('create_widget_blueprint', {
|
|
717
|
+
title: 'Create Widget Blueprint',
|
|
718
|
+
description: `Create a new UE5 WidgetBlueprint asset with a specified parent class.
|
|
719
|
+
|
|
720
|
+
USAGE: Provide the content path where the asset should be created and optionally a parent class.
|
|
721
|
+
Default parent is UserWidget. For CommonUI widgets use CommonActivatableWidget, CommonButtonBase, etc.
|
|
722
|
+
|
|
723
|
+
RETURNS: JSON with success status, asset path, and parent class name.`,
|
|
724
|
+
inputSchema: {
|
|
725
|
+
asset_path: z.string().describe('UE content path for the new WidgetBlueprint (e.g. /Game/UI/WBP_MyWidget)'),
|
|
726
|
+
parent_class: z.string().default('UserWidget').describe('Parent class name (e.g. UserWidget, CommonActivatableWidget, CommonButtonBase)'),
|
|
727
|
+
},
|
|
728
|
+
annotations: {
|
|
729
|
+
title: 'Create Widget Blueprint',
|
|
730
|
+
readOnlyHint: false,
|
|
731
|
+
destructiveHint: false,
|
|
732
|
+
idempotentHint: true,
|
|
733
|
+
openWorldHint: false,
|
|
734
|
+
},
|
|
735
|
+
}, async ({ asset_path, parent_class }) => {
|
|
736
|
+
try {
|
|
737
|
+
const result = await client.callSubsystem('CreateWidgetBlueprint', {
|
|
738
|
+
AssetPath: asset_path,
|
|
739
|
+
ParentClass: parent_class,
|
|
740
|
+
});
|
|
741
|
+
const parsed = JSON.parse(result);
|
|
742
|
+
if (parsed.error) {
|
|
743
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
744
|
+
}
|
|
745
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
746
|
+
}
|
|
747
|
+
catch (e) {
|
|
748
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
// Tool 9: build_widget_tree
|
|
752
|
+
server.registerTool('build_widget_tree', {
|
|
753
|
+
title: 'Build Widget Tree',
|
|
754
|
+
description: `Build or replace the entire widget hierarchy of an existing WidgetBlueprint from a JSON tree description.
|
|
755
|
+
|
|
756
|
+
WARNING: This REPLACES the existing widget tree — all current widgets will be removed.
|
|
757
|
+
|
|
758
|
+
USAGE: Provide the asset path and a root_widget object describing the full tree recursively.
|
|
759
|
+
Each widget node has: class, name, is_variable, slot (optional), properties (optional), children (optional).
|
|
760
|
+
|
|
761
|
+
RETURNS: JSON with success status, widget count, and any errors.`,
|
|
762
|
+
inputSchema: {
|
|
763
|
+
asset_path: z.string().describe('UE content path to an existing WidgetBlueprint'),
|
|
764
|
+
root_widget: WidgetNodeSchema.describe('Root widget of the tree hierarchy'),
|
|
765
|
+
},
|
|
766
|
+
annotations: {
|
|
767
|
+
title: 'Build Widget Tree',
|
|
768
|
+
readOnlyHint: false,
|
|
769
|
+
destructiveHint: true,
|
|
770
|
+
idempotentHint: true,
|
|
771
|
+
openWorldHint: false,
|
|
772
|
+
},
|
|
773
|
+
}, async ({ asset_path, root_widget }) => {
|
|
774
|
+
try {
|
|
775
|
+
const result = await client.callSubsystem('BuildWidgetTree', {
|
|
776
|
+
AssetPath: asset_path,
|
|
777
|
+
WidgetTreeJson: JSON.stringify(root_widget),
|
|
778
|
+
});
|
|
779
|
+
const parsed = JSON.parse(result);
|
|
780
|
+
if (parsed.error) {
|
|
781
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
782
|
+
}
|
|
783
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
784
|
+
}
|
|
785
|
+
catch (e) {
|
|
786
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
// Tool 10: modify_widget
|
|
790
|
+
server.registerTool('modify_widget', {
|
|
791
|
+
title: 'Modify Widget',
|
|
792
|
+
description: `Modify properties and/or slot configuration of an existing widget within a WidgetBlueprint.
|
|
793
|
+
|
|
794
|
+
USAGE: Specify the asset path, widget name, and the properties/slot values to change.
|
|
795
|
+
Only specified properties are modified — others remain unchanged.
|
|
796
|
+
|
|
797
|
+
RETURNS: JSON with success status, widget name, class, and any errors.`,
|
|
798
|
+
inputSchema: {
|
|
799
|
+
asset_path: z.string().describe('UE content path to the WidgetBlueprint'),
|
|
800
|
+
widget_name: z.string().describe('Name of the widget to modify (as shown in the widget tree)'),
|
|
801
|
+
properties: z.record(z.string(), z.unknown()).optional().describe('Widget UPROPERTY values to set'),
|
|
802
|
+
slot: z.record(z.string(), z.unknown()).optional().describe('Slot properties to set'),
|
|
803
|
+
},
|
|
804
|
+
annotations: {
|
|
805
|
+
title: 'Modify Widget',
|
|
806
|
+
readOnlyHint: false,
|
|
807
|
+
destructiveHint: false,
|
|
808
|
+
idempotentHint: true,
|
|
809
|
+
openWorldHint: false,
|
|
810
|
+
},
|
|
811
|
+
}, async ({ asset_path, widget_name, properties, slot }) => {
|
|
812
|
+
try {
|
|
813
|
+
const result = await client.callSubsystem('ModifyWidget', {
|
|
814
|
+
AssetPath: asset_path,
|
|
815
|
+
WidgetName: widget_name,
|
|
816
|
+
PropertiesJson: JSON.stringify(properties ?? {}),
|
|
817
|
+
SlotJson: JSON.stringify(slot ?? {}),
|
|
818
|
+
});
|
|
819
|
+
const parsed = JSON.parse(result);
|
|
820
|
+
if (parsed.error) {
|
|
821
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
822
|
+
}
|
|
823
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
824
|
+
}
|
|
825
|
+
catch (e) {
|
|
826
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
// Tool 11: compile_widget_blueprint
|
|
830
|
+
server.registerTool('compile_widget_blueprint', {
|
|
831
|
+
title: 'Compile Widget Blueprint',
|
|
832
|
+
description: `Compile a WidgetBlueprint and return any errors or warnings.
|
|
833
|
+
|
|
834
|
+
USAGE: Call after building or modifying a widget tree to verify compilation.
|
|
835
|
+
|
|
836
|
+
RETURNS: JSON with success status, compilation status, error count, warning count, and error details.`,
|
|
837
|
+
inputSchema: {
|
|
838
|
+
asset_path: z.string().describe('UE content path to the WidgetBlueprint to compile'),
|
|
839
|
+
},
|
|
840
|
+
annotations: {
|
|
841
|
+
title: 'Compile Widget Blueprint',
|
|
842
|
+
readOnlyHint: false,
|
|
843
|
+
destructiveHint: false,
|
|
844
|
+
idempotentHint: true,
|
|
845
|
+
openWorldHint: false,
|
|
846
|
+
},
|
|
847
|
+
}, async ({ asset_path }) => {
|
|
848
|
+
try {
|
|
849
|
+
const result = await client.callSubsystem('CompileWidgetBlueprint', { AssetPath: asset_path });
|
|
850
|
+
const parsed = JSON.parse(result);
|
|
851
|
+
if (parsed.error) {
|
|
852
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
853
|
+
}
|
|
854
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
855
|
+
}
|
|
856
|
+
catch (e) {
|
|
857
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
858
|
+
}
|
|
859
|
+
});
|
|
329
860
|
// Start server
|
|
330
861
|
async function main() {
|
|
331
862
|
const transport = new StdioServerTransport();
|