blueprint-extractor-mcp 1.4.1 → 1.6.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/compactor.js +33 -4
- package/dist/index.js +160 -4
- package/package.json +1 -1
package/dist/compactor.js
CHANGED
|
@@ -16,11 +16,17 @@ export function compactBlueprint(data) {
|
|
|
16
16
|
const bp = root.blueprint;
|
|
17
17
|
if (!bp)
|
|
18
18
|
return data;
|
|
19
|
+
// Branch 1: Compact graphs (if present)
|
|
19
20
|
const functions = bp.functions;
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (Array.isArray(functions)) {
|
|
22
|
+
for (const graph of functions) {
|
|
23
|
+
compactGraph(graph);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Branch 2: Compact widget tree (if present)
|
|
27
|
+
const wt = bp.widgetTree;
|
|
28
|
+
if (wt && typeof wt === 'object' && wt.rootWidget) {
|
|
29
|
+
compactWidgetNode(wt.rootWidget);
|
|
24
30
|
}
|
|
25
31
|
return data;
|
|
26
32
|
}
|
|
@@ -60,6 +66,29 @@ function compactGraph(graph) {
|
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
}
|
|
69
|
+
function compactWidgetNode(node) {
|
|
70
|
+
if (!node)
|
|
71
|
+
return;
|
|
72
|
+
// Remove displayLabel when it equals name or is empty (redundant)
|
|
73
|
+
if (node.displayLabel === node.name || node.displayLabel === '') {
|
|
74
|
+
delete node.displayLabel;
|
|
75
|
+
}
|
|
76
|
+
// Remove default visibility
|
|
77
|
+
if (node.visibility === 'Visible') {
|
|
78
|
+
delete node.visibility;
|
|
79
|
+
}
|
|
80
|
+
// Remove empty properties objects
|
|
81
|
+
if (node.properties && typeof node.properties === 'object' && Object.keys(node.properties).length === 0) {
|
|
82
|
+
delete node.properties;
|
|
83
|
+
}
|
|
84
|
+
// Recurse into children
|
|
85
|
+
const children = node.children;
|
|
86
|
+
if (Array.isArray(children)) {
|
|
87
|
+
for (const child of children) {
|
|
88
|
+
compactWidgetNode(child);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
63
92
|
function compactPin(pin, guidToShortId) {
|
|
64
93
|
// Remove pinId
|
|
65
94
|
delete pin.pinId;
|
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.6.0',
|
|
11
11
|
});
|
|
12
12
|
// Shared scope enum with detailed descriptions
|
|
13
13
|
const scopeEnum = z.enum([
|
|
@@ -34,7 +34,7 @@ server.resource('extraction-scopes', 'blueprint://scopes', {
|
|
|
34
34
|
'|-------------------|---------------------------------------------------|---------------|-----------------------------------------------|',
|
|
35
35
|
'| ClassLevel | Parent class, interfaces, class flags, metadata | 1-2 KB | Checking inheritance or interface list |',
|
|
36
36
|
'| Variables | All variables with types, defaults, flags | 2-10 KB | Understanding data model (DEFAULT) |',
|
|
37
|
-
'| Components | SCS component tree with property overrides vs CDO | 5-20 KB | Analyzing component composition |',
|
|
37
|
+
'| Components | SCS component tree with property overrides vs CDO. For WidgetBlueprints: widget tree hierarchy with layout and bindings | 5-20 KB | Analyzing component composition |',
|
|
38
38
|
'| FunctionsShallow | Function and event graph names only | 5-25 KB | Listing available functions before deep dive |',
|
|
39
39
|
'| Full | Complete graph nodes, pins, and connections | 20-500+ KB | Understanding graph logic and execution flow |',
|
|
40
40
|
'| FullWithBytecode | Raw bytecode hex dump per function | Largest | Low-level analysis (rarely needed) |',
|
|
@@ -57,7 +57,7 @@ USAGE GUIDELINES:
|
|
|
57
57
|
- Start with the narrowest scope that answers your question — each level includes everything from the previous:
|
|
58
58
|
* ClassLevel — parent class, interfaces, class flags, metadata (~1-2KB)
|
|
59
59
|
* Variables — + all variables with types, defaults, flags (~2-10KB)
|
|
60
|
-
* Components — + SCS component tree with property overrides (~5-20KB)
|
|
60
|
+
* Components — + SCS component tree with property overrides (~5-20KB). For WidgetBlueprints, also includes the widget tree hierarchy with bindings.
|
|
61
61
|
* FunctionsShallow — + function/event graph names only (~5-25KB)
|
|
62
62
|
* Full — + complete graph nodes, pins, and connections (~20-500KB+)
|
|
63
63
|
* FullWithBytecode — + raw bytecode hex dump (largest, rarely needed)
|
|
@@ -294,7 +294,9 @@ server.registerTool('list_assets', {
|
|
|
294
294
|
title: 'List Assets',
|
|
295
295
|
description: `List UE5 assets under a package path. Use this to browse directory contents when you don't know asset names. If you know (part of) the asset name, prefer search_assets instead — it's faster and doesn't require knowing the directory.
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
When recursive=false, subdirectories are included in the results with class "Folder" — use this to browse the content tree structure.
|
|
298
|
+
|
|
299
|
+
RETURNS: JSON array of objects with path, name, and class for each asset (and subfolder when non-recursive) in the directory.`,
|
|
298
300
|
inputSchema: {
|
|
299
301
|
package_path: z.string().describe('UE package path to list (e.g. /Game/Blueprints, /Game/AI). Use /Game to list from the Content root.'),
|
|
300
302
|
recursive: z.boolean().default(true).describe('Whether to include assets in subdirectories.'),
|
|
@@ -324,6 +326,160 @@ RETURNS: JSON array of objects with path, name, and class for each asset in the
|
|
|
324
326
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
325
327
|
}
|
|
326
328
|
});
|
|
329
|
+
// Recursive schema for widget tree nodes (used by build_widget_tree)
|
|
330
|
+
const WidgetNodeSchema = z.lazy(() => z.object({
|
|
331
|
+
class: z.string().describe('Widget class name (e.g. CanvasPanel, TextBlock, CommonButtonBase, VerticalBox)'),
|
|
332
|
+
name: z.string().describe('Widget instance name (used for BindWidget matching)'),
|
|
333
|
+
is_variable: z.boolean().default(false).describe('Mark as variable for BindWidget access from C++'),
|
|
334
|
+
slot: z.record(z.string(), z.unknown()).optional().describe('Slot properties (type depends on parent panel)'),
|
|
335
|
+
properties: z.record(z.string(), z.unknown()).optional().describe('Widget UPROPERTY values to set'),
|
|
336
|
+
children: z.array(WidgetNodeSchema).optional().describe('Child widgets (only valid for panel widgets)'),
|
|
337
|
+
}));
|
|
338
|
+
// Tool 8: create_widget_blueprint
|
|
339
|
+
server.registerTool('create_widget_blueprint', {
|
|
340
|
+
title: 'Create Widget Blueprint',
|
|
341
|
+
description: `Create a new UE5 WidgetBlueprint asset with a specified parent class.
|
|
342
|
+
|
|
343
|
+
USAGE: Provide the content path where the asset should be created and optionally a parent class.
|
|
344
|
+
Default parent is UserWidget. For CommonUI widgets use CommonActivatableWidget, CommonButtonBase, etc.
|
|
345
|
+
|
|
346
|
+
RETURNS: JSON with success status, asset path, and parent class name.`,
|
|
347
|
+
inputSchema: {
|
|
348
|
+
asset_path: z.string().describe('UE content path for the new WidgetBlueprint (e.g. /Game/UI/WBP_MyWidget)'),
|
|
349
|
+
parent_class: z.string().default('UserWidget').describe('Parent class name (e.g. UserWidget, CommonActivatableWidget, CommonButtonBase)'),
|
|
350
|
+
},
|
|
351
|
+
annotations: {
|
|
352
|
+
title: 'Create Widget Blueprint',
|
|
353
|
+
readOnlyHint: false,
|
|
354
|
+
destructiveHint: false,
|
|
355
|
+
idempotentHint: true,
|
|
356
|
+
openWorldHint: false,
|
|
357
|
+
},
|
|
358
|
+
}, async ({ asset_path, parent_class }) => {
|
|
359
|
+
try {
|
|
360
|
+
const result = await client.callSubsystem('CreateWidgetBlueprint', {
|
|
361
|
+
AssetPath: asset_path,
|
|
362
|
+
ParentClass: parent_class,
|
|
363
|
+
});
|
|
364
|
+
const parsed = JSON.parse(result);
|
|
365
|
+
if (parsed.error) {
|
|
366
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
367
|
+
}
|
|
368
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
// Tool 9: build_widget_tree
|
|
375
|
+
server.registerTool('build_widget_tree', {
|
|
376
|
+
title: 'Build Widget Tree',
|
|
377
|
+
description: `Build or replace the entire widget hierarchy of an existing WidgetBlueprint from a JSON tree description.
|
|
378
|
+
|
|
379
|
+
WARNING: This REPLACES the existing widget tree — all current widgets will be removed.
|
|
380
|
+
|
|
381
|
+
USAGE: Provide the asset path and a root_widget object describing the full tree recursively.
|
|
382
|
+
Each widget node has: class, name, is_variable, slot (optional), properties (optional), children (optional).
|
|
383
|
+
|
|
384
|
+
RETURNS: JSON with success status, widget count, and any errors.`,
|
|
385
|
+
inputSchema: {
|
|
386
|
+
asset_path: z.string().describe('UE content path to an existing WidgetBlueprint'),
|
|
387
|
+
root_widget: WidgetNodeSchema.describe('Root widget of the tree hierarchy'),
|
|
388
|
+
},
|
|
389
|
+
annotations: {
|
|
390
|
+
title: 'Build Widget Tree',
|
|
391
|
+
readOnlyHint: false,
|
|
392
|
+
destructiveHint: true,
|
|
393
|
+
idempotentHint: true,
|
|
394
|
+
openWorldHint: false,
|
|
395
|
+
},
|
|
396
|
+
}, async ({ asset_path, root_widget }) => {
|
|
397
|
+
try {
|
|
398
|
+
const result = await client.callSubsystem('BuildWidgetTree', {
|
|
399
|
+
AssetPath: asset_path,
|
|
400
|
+
WidgetTreeJson: JSON.stringify(root_widget),
|
|
401
|
+
});
|
|
402
|
+
const parsed = JSON.parse(result);
|
|
403
|
+
if (parsed.error) {
|
|
404
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
405
|
+
}
|
|
406
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
407
|
+
}
|
|
408
|
+
catch (e) {
|
|
409
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
// Tool 10: modify_widget
|
|
413
|
+
server.registerTool('modify_widget', {
|
|
414
|
+
title: 'Modify Widget',
|
|
415
|
+
description: `Modify properties and/or slot configuration of an existing widget within a WidgetBlueprint.
|
|
416
|
+
|
|
417
|
+
USAGE: Specify the asset path, widget name, and the properties/slot values to change.
|
|
418
|
+
Only specified properties are modified — others remain unchanged.
|
|
419
|
+
|
|
420
|
+
RETURNS: JSON with success status, widget name, class, and any errors.`,
|
|
421
|
+
inputSchema: {
|
|
422
|
+
asset_path: z.string().describe('UE content path to the WidgetBlueprint'),
|
|
423
|
+
widget_name: z.string().describe('Name of the widget to modify (as shown in the widget tree)'),
|
|
424
|
+
properties: z.record(z.string(), z.unknown()).optional().describe('Widget UPROPERTY values to set'),
|
|
425
|
+
slot: z.record(z.string(), z.unknown()).optional().describe('Slot properties to set'),
|
|
426
|
+
},
|
|
427
|
+
annotations: {
|
|
428
|
+
title: 'Modify Widget',
|
|
429
|
+
readOnlyHint: false,
|
|
430
|
+
destructiveHint: false,
|
|
431
|
+
idempotentHint: true,
|
|
432
|
+
openWorldHint: false,
|
|
433
|
+
},
|
|
434
|
+
}, async ({ asset_path, widget_name, properties, slot }) => {
|
|
435
|
+
try {
|
|
436
|
+
const result = await client.callSubsystem('ModifyWidget', {
|
|
437
|
+
AssetPath: asset_path,
|
|
438
|
+
WidgetName: widget_name,
|
|
439
|
+
PropertiesJson: JSON.stringify(properties ?? {}),
|
|
440
|
+
SlotJson: JSON.stringify(slot ?? {}),
|
|
441
|
+
});
|
|
442
|
+
const parsed = JSON.parse(result);
|
|
443
|
+
if (parsed.error) {
|
|
444
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
445
|
+
}
|
|
446
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
447
|
+
}
|
|
448
|
+
catch (e) {
|
|
449
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
// Tool 11: compile_widget_blueprint
|
|
453
|
+
server.registerTool('compile_widget_blueprint', {
|
|
454
|
+
title: 'Compile Widget Blueprint',
|
|
455
|
+
description: `Compile a WidgetBlueprint and return any errors or warnings.
|
|
456
|
+
|
|
457
|
+
USAGE: Call after building or modifying a widget tree to verify compilation.
|
|
458
|
+
|
|
459
|
+
RETURNS: JSON with success status, compilation status, error count, warning count, and error details.`,
|
|
460
|
+
inputSchema: {
|
|
461
|
+
asset_path: z.string().describe('UE content path to the WidgetBlueprint to compile'),
|
|
462
|
+
},
|
|
463
|
+
annotations: {
|
|
464
|
+
title: 'Compile Widget Blueprint',
|
|
465
|
+
readOnlyHint: false,
|
|
466
|
+
destructiveHint: false,
|
|
467
|
+
idempotentHint: true,
|
|
468
|
+
openWorldHint: false,
|
|
469
|
+
},
|
|
470
|
+
}, async ({ asset_path }) => {
|
|
471
|
+
try {
|
|
472
|
+
const result = await client.callSubsystem('CompileWidgetBlueprint', { AssetPath: asset_path });
|
|
473
|
+
const parsed = JSON.parse(result);
|
|
474
|
+
if (parsed.error) {
|
|
475
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
476
|
+
}
|
|
477
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
481
|
+
}
|
|
482
|
+
});
|
|
327
483
|
// Start server
|
|
328
484
|
async function main() {
|
|
329
485
|
const transport = new StdioServerTransport();
|