blueprint-extractor-mcp 1.3.0 → 1.4.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.d.ts +12 -0
- package/dist/compactor.js +100 -0
- package/dist/index.js +21 -6
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compacts Blueprint extraction JSON by stripping low-value fields and minifying.
|
|
3
|
+
* Reduces size by ~50-70% for LLM consumption.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compacts a parsed Blueprint extraction result.
|
|
7
|
+
* - Removes positional data (posX, posY), GUIDs, empty fields
|
|
8
|
+
* - Replaces nodeGuid with sequential short IDs (n0, n1, ...)
|
|
9
|
+
* - Rewrites connection references to use short IDs
|
|
10
|
+
* - Replaces exec pin type objects with the string "exec"
|
|
11
|
+
*/
|
|
12
|
+
export declare function compactBlueprint(data: unknown): unknown;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compacts Blueprint extraction JSON by stripping low-value fields and minifying.
|
|
3
|
+
* Reduces size by ~50-70% for LLM consumption.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compacts a parsed Blueprint extraction result.
|
|
7
|
+
* - Removes positional data (posX, posY), GUIDs, empty fields
|
|
8
|
+
* - Replaces nodeGuid with sequential short IDs (n0, n1, ...)
|
|
9
|
+
* - Rewrites connection references to use short IDs
|
|
10
|
+
* - Replaces exec pin type objects with the string "exec"
|
|
11
|
+
*/
|
|
12
|
+
export function compactBlueprint(data) {
|
|
13
|
+
if (!data || typeof data !== 'object')
|
|
14
|
+
return data;
|
|
15
|
+
const root = data;
|
|
16
|
+
const bp = root.blueprint;
|
|
17
|
+
if (!bp)
|
|
18
|
+
return data;
|
|
19
|
+
const functions = bp.functions;
|
|
20
|
+
if (!Array.isArray(functions))
|
|
21
|
+
return data;
|
|
22
|
+
for (const graph of functions) {
|
|
23
|
+
compactGraph(graph);
|
|
24
|
+
}
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
function compactGraph(graph) {
|
|
28
|
+
// Remove graphGuid at graph level
|
|
29
|
+
delete graph.graphGuid;
|
|
30
|
+
const nodes = graph.nodes;
|
|
31
|
+
if (!Array.isArray(nodes))
|
|
32
|
+
return;
|
|
33
|
+
// Build guidToShortId map
|
|
34
|
+
const guidToShortId = new Map();
|
|
35
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
36
|
+
const guid = nodes[i].nodeGuid;
|
|
37
|
+
if (guid) {
|
|
38
|
+
guidToShortId.set(guid, `n${i}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Process each node
|
|
42
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
43
|
+
const node = nodes[i];
|
|
44
|
+
// Replace nodeGuid with short ID
|
|
45
|
+
delete node.nodeGuid;
|
|
46
|
+
node.id = `n${i}`;
|
|
47
|
+
// Remove positional data
|
|
48
|
+
delete node.posX;
|
|
49
|
+
delete node.posY;
|
|
50
|
+
// Remove empty nodeComment
|
|
51
|
+
if (node.nodeComment === '') {
|
|
52
|
+
delete node.nodeComment;
|
|
53
|
+
}
|
|
54
|
+
// Process pins
|
|
55
|
+
const pins = node.pins;
|
|
56
|
+
if (Array.isArray(pins)) {
|
|
57
|
+
for (const pin of pins) {
|
|
58
|
+
compactPin(pin, guidToShortId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function compactPin(pin, guidToShortId) {
|
|
64
|
+
// Remove pinId
|
|
65
|
+
delete pin.pinId;
|
|
66
|
+
// Remove autogeneratedDefaultValue
|
|
67
|
+
delete pin.autogeneratedDefaultValue;
|
|
68
|
+
// Remove empty defaultValue
|
|
69
|
+
if (pin.defaultValue === '') {
|
|
70
|
+
delete pin.defaultValue;
|
|
71
|
+
}
|
|
72
|
+
// Process type object
|
|
73
|
+
const type = pin.type;
|
|
74
|
+
if (type && typeof type === 'object') {
|
|
75
|
+
// Remove empty sub_category
|
|
76
|
+
if (type.sub_category === '') {
|
|
77
|
+
delete type.sub_category;
|
|
78
|
+
}
|
|
79
|
+
// Replace exec pin type with string "exec"
|
|
80
|
+
if (type.category === 'exec') {
|
|
81
|
+
pin.type = 'exec';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Rewrite connection nodeGuid references to short IDs
|
|
85
|
+
const connections = pin.connections;
|
|
86
|
+
if (Array.isArray(connections)) {
|
|
87
|
+
if (connections.length === 0) {
|
|
88
|
+
// Remove empty connections arrays
|
|
89
|
+
delete pin.connections;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
for (const conn of connections) {
|
|
93
|
+
const connGuid = conn.nodeGuid;
|
|
94
|
+
if (connGuid && guidToShortId.has(connGuid)) {
|
|
95
|
+
conn.nodeGuid = guidToShortId.get(connGuid);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,10 +3,11 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { UEClient } from './ue-client.js';
|
|
6
|
+
import { compactBlueprint } from './compactor.js';
|
|
6
7
|
const client = new UEClient();
|
|
7
8
|
const server = new McpServer({
|
|
8
9
|
name: 'blueprint-extractor',
|
|
9
|
-
version: '1.
|
|
10
|
+
version: '1.4.0',
|
|
10
11
|
});
|
|
11
12
|
// Shared scope enum with detailed descriptions
|
|
12
13
|
const scopeEnum = z.enum([
|
|
@@ -62,11 +63,15 @@ USAGE GUIDELINES:
|
|
|
62
63
|
* FullWithBytecode — + raw bytecode hex dump (largest, rarely needed)
|
|
63
64
|
- Only escalate to Full when you need to understand graph logic (node connections, pin values, execution flow).
|
|
64
65
|
- Full scope on complex Blueprints can exceed 200KB and will be truncated. If truncated, use a narrower scope or inspect specific functions via the graph names from FunctionsShallow.
|
|
66
|
+
- Use FunctionsShallow scope first to get graph names, then request specific graphs with graph_filter to reduce output size.
|
|
67
|
+
- Use compact=true to reduce JSON size by ~50-70% for LLM consumption.
|
|
65
68
|
|
|
66
69
|
RETURNS: JSON object with the extracted Blueprint data at the requested scope level.`,
|
|
67
70
|
inputSchema: {
|
|
68
71
|
asset_path: z.string().describe('UE content path to the Blueprint asset. Must start with /Game/ (e.g. /Game/Blueprints/BP_Character). Use search_assets to find paths.'),
|
|
69
72
|
scope: scopeEnum.default('Variables').describe('Extraction depth. Start with ClassLevel or Variables — only use Full when you need graph/node details.'),
|
|
73
|
+
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"]'),
|
|
74
|
+
compact: z.boolean().default(false).describe('When true, strips low-value fields and minifies JSON to reduce size by ~50-70%. Removes: pinId, posX/posY, graphGuid, autogeneratedDefaultValue, nodeComment (when empty), empty connections, empty default_value, empty sub_category. Replaces full exec pin type objects with the string "exec".'),
|
|
70
75
|
},
|
|
71
76
|
annotations: {
|
|
72
77
|
title: 'Extract Blueprint',
|
|
@@ -75,14 +80,21 @@ RETURNS: JSON object with the extracted Blueprint data at the requested scope le
|
|
|
75
80
|
idempotentHint: true,
|
|
76
81
|
openWorldHint: false,
|
|
77
82
|
},
|
|
78
|
-
}, async ({ asset_path, scope }) => {
|
|
83
|
+
}, async ({ asset_path, scope, graph_filter, compact }) => {
|
|
79
84
|
try {
|
|
80
|
-
const result = await client.callSubsystem('ExtractBlueprint', {
|
|
81
|
-
|
|
85
|
+
const result = await client.callSubsystem('ExtractBlueprint', {
|
|
86
|
+
AssetPath: asset_path,
|
|
87
|
+
Scope: scope,
|
|
88
|
+
GraphFilter: graph_filter ? graph_filter.join(',') : '',
|
|
89
|
+
});
|
|
90
|
+
let parsed = JSON.parse(result);
|
|
82
91
|
if (parsed.error) {
|
|
83
92
|
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
84
93
|
}
|
|
85
|
-
|
|
94
|
+
if (compact) {
|
|
95
|
+
parsed = compactBlueprint(parsed);
|
|
96
|
+
}
|
|
97
|
+
const text = compact ? JSON.stringify(parsed) : JSON.stringify(parsed, null, 2);
|
|
86
98
|
if (text.length > 200_000) {
|
|
87
99
|
return { content: [{ type: 'text', text: `Warning: Response is ${(text.length / 1024).toFixed(0)}KB — consider using a narrower scope (ClassLevel, Variables, or FunctionsShallow).\n\n${text.substring(0, 200_000)}...\n[TRUNCATED]` }] };
|
|
88
100
|
}
|
|
@@ -214,6 +226,8 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
214
226
|
asset_paths: z.array(z.string()).describe('Array of UE content paths to extract (e.g. ["/Game/Blueprints/BP_Character", "/Game/Blueprints/BP_Weapon"])'),
|
|
215
227
|
scope: scopeEnum.default('Full').describe('Extraction depth applied to all assets. Full is the default since cascade is typically used for deep analysis.'),
|
|
216
228
|
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
|
+
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
|
+
compact: z.boolean().default(false).describe('When true, strips low-value fields and minifies JSON to reduce size by ~50-70%. Removes: pinId, posX/posY, graphGuid, autogeneratedDefaultValue, nodeComment (when empty), empty connections, empty default_value, empty sub_category. Replaces full exec pin type objects with the string "exec".'),
|
|
217
231
|
},
|
|
218
232
|
annotations: {
|
|
219
233
|
title: 'Extract Cascade',
|
|
@@ -222,12 +236,13 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
222
236
|
idempotentHint: true,
|
|
223
237
|
openWorldHint: false,
|
|
224
238
|
},
|
|
225
|
-
}, async ({ asset_paths, scope, max_depth }) => {
|
|
239
|
+
}, async ({ asset_paths, scope, max_depth, graph_filter, compact }) => {
|
|
226
240
|
try {
|
|
227
241
|
const result = await client.callSubsystem('ExtractCascade', {
|
|
228
242
|
AssetPathsJson: JSON.stringify(asset_paths),
|
|
229
243
|
Scope: scope,
|
|
230
244
|
MaxDepth: max_depth,
|
|
245
|
+
GraphFilter: graph_filter ? graph_filter.join(',') : '',
|
|
231
246
|
});
|
|
232
247
|
const parsed = JSON.parse(result);
|
|
233
248
|
if (parsed.error) {
|