blueprint-extractor-mcp 1.2.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 +104 -14
- 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([
|
|
@@ -40,6 +41,9 @@ server.resource('extraction-scopes', 'blueprint://scopes', {
|
|
|
40
41
|
'',
|
|
41
42
|
'Start with the narrowest scope that answers your question.',
|
|
42
43
|
'Full scope on complex Blueprints can exceed 200KB and will be truncated.',
|
|
44
|
+
'',
|
|
45
|
+
'Note: Scopes only apply to Blueprint extraction (extract_blueprint and extract_cascade).',
|
|
46
|
+
'DataAsset, DataTable, and StateTree always extract fully — no scope parameter is needed.',
|
|
43
47
|
].join('\n'),
|
|
44
48
|
}],
|
|
45
49
|
}));
|
|
@@ -59,11 +63,15 @@ USAGE GUIDELINES:
|
|
|
59
63
|
* FullWithBytecode — + raw bytecode hex dump (largest, rarely needed)
|
|
60
64
|
- Only escalate to Full when you need to understand graph logic (node connections, pin values, execution flow).
|
|
61
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.
|
|
62
68
|
|
|
63
69
|
RETURNS: JSON object with the extracted Blueprint data at the requested scope level.`,
|
|
64
70
|
inputSchema: {
|
|
65
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.'),
|
|
66
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".'),
|
|
67
75
|
},
|
|
68
76
|
annotations: {
|
|
69
77
|
title: 'Extract Blueprint',
|
|
@@ -72,14 +80,21 @@ RETURNS: JSON object with the extracted Blueprint data at the requested scope le
|
|
|
72
80
|
idempotentHint: true,
|
|
73
81
|
openWorldHint: false,
|
|
74
82
|
},
|
|
75
|
-
}, async ({ asset_path, scope }) => {
|
|
83
|
+
}, async ({ asset_path, scope, graph_filter, compact }) => {
|
|
76
84
|
try {
|
|
77
|
-
const result = await client.callSubsystem('ExtractBlueprint', {
|
|
78
|
-
|
|
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);
|
|
79
91
|
if (parsed.error) {
|
|
80
92
|
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
81
93
|
}
|
|
82
|
-
|
|
94
|
+
if (compact) {
|
|
95
|
+
parsed = compactBlueprint(parsed);
|
|
96
|
+
}
|
|
97
|
+
const text = compact ? JSON.stringify(parsed) : JSON.stringify(parsed, null, 2);
|
|
83
98
|
if (text.length > 200_000) {
|
|
84
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]` }] };
|
|
85
100
|
}
|
|
@@ -123,10 +138,82 @@ RETURNS: JSON object with schema, state hierarchy, tasks, conditions, transition
|
|
|
123
138
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
124
139
|
}
|
|
125
140
|
});
|
|
126
|
-
// Tool 3:
|
|
141
|
+
// Tool 3: extract_dataasset
|
|
142
|
+
server.registerTool('extract_dataasset', {
|
|
143
|
+
title: 'Extract DataAsset',
|
|
144
|
+
description: `Extract a UE5 DataAsset to structured JSON. Serializes all user-defined UPROPERTY fields using UE reflection.
|
|
145
|
+
|
|
146
|
+
USAGE GUIDELINES:
|
|
147
|
+
- Use search_assets first with class_filter "DataAsset" or a specific DataAsset subclass name to find the asset path.
|
|
148
|
+
- Returns all user-defined properties with their types and current values.
|
|
149
|
+
- Works with any UDataAsset or UPrimaryDataAsset subclass.
|
|
150
|
+
|
|
151
|
+
RETURNS: JSON object with the DataAsset's class info and all property values.`,
|
|
152
|
+
inputSchema: {
|
|
153
|
+
asset_path: z.string().describe('UE content path to the DataAsset (e.g. /Game/Data/DA_ItemDatabase). Use search_assets to find paths.'),
|
|
154
|
+
},
|
|
155
|
+
annotations: {
|
|
156
|
+
title: 'Extract DataAsset',
|
|
157
|
+
readOnlyHint: true,
|
|
158
|
+
destructiveHint: false,
|
|
159
|
+
idempotentHint: true,
|
|
160
|
+
openWorldHint: false,
|
|
161
|
+
},
|
|
162
|
+
}, async ({ asset_path }) => {
|
|
163
|
+
try {
|
|
164
|
+
const result = await client.callSubsystem('ExtractDataAsset', { AssetPath: asset_path });
|
|
165
|
+
const parsed = JSON.parse(result);
|
|
166
|
+
if (parsed.error) {
|
|
167
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
168
|
+
}
|
|
169
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Tool 4: extract_datatable
|
|
176
|
+
server.registerTool('extract_datatable', {
|
|
177
|
+
title: 'Extract DataTable',
|
|
178
|
+
description: `Extract a UE5 DataTable asset to structured JSON. Includes the row struct schema and all row data.
|
|
179
|
+
|
|
180
|
+
USAGE GUIDELINES:
|
|
181
|
+
- Use search_assets first with class_filter "DataTable" to find the asset path.
|
|
182
|
+
- Returns the row struct type, property schema, row count, and all row data with names and values.
|
|
183
|
+
- Useful for understanding game data tables (items, abilities, stats, etc.).
|
|
184
|
+
|
|
185
|
+
RETURNS: JSON object with row struct info, schema, and all rows.`,
|
|
186
|
+
inputSchema: {
|
|
187
|
+
asset_path: z.string().describe('UE content path to the DataTable (e.g. /Game/Data/DT_WeaponStats). Use search_assets to find paths.'),
|
|
188
|
+
},
|
|
189
|
+
annotations: {
|
|
190
|
+
title: 'Extract DataTable',
|
|
191
|
+
readOnlyHint: true,
|
|
192
|
+
destructiveHint: false,
|
|
193
|
+
idempotentHint: true,
|
|
194
|
+
openWorldHint: false,
|
|
195
|
+
},
|
|
196
|
+
}, async ({ asset_path }) => {
|
|
197
|
+
try {
|
|
198
|
+
const result = await client.callSubsystem('ExtractDataTable', { AssetPath: asset_path });
|
|
199
|
+
const parsed = JSON.parse(result);
|
|
200
|
+
if (parsed.error) {
|
|
201
|
+
return { content: [{ type: 'text', text: `Error: ${parsed.error}` }], isError: true };
|
|
202
|
+
}
|
|
203
|
+
const text = JSON.stringify(parsed, null, 2);
|
|
204
|
+
if (text.length > 200_000) {
|
|
205
|
+
return { content: [{ type: 'text', text: `Warning: Response is ${(text.length / 1024).toFixed(0)}KB — large DataTable.\n\n${text.substring(0, 200_000)}...\n[TRUNCATED]` }] };
|
|
206
|
+
}
|
|
207
|
+
return { content: [{ type: 'text', text }] };
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Tool 5: extract_cascade
|
|
127
214
|
server.registerTool('extract_cascade', {
|
|
128
215
|
title: 'Extract Cascade',
|
|
129
|
-
description: `Extract multiple Blueprint
|
|
216
|
+
description: `Extract multiple assets (Blueprint, AnimBlueprint, StateTree, DataAsset, DataTable) with automatic reference following for Blueprints and StateTrees. Follows parent classes, interfaces, component classes, and other Blueprint references up to max_depth levels deep.
|
|
130
217
|
|
|
131
218
|
USAGE GUIDELINES:
|
|
132
219
|
- Use when you need to understand an asset AND its dependencies (parent Blueprints, referenced Blueprints, etc.).
|
|
@@ -139,6 +226,8 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
139
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"])'),
|
|
140
227
|
scope: scopeEnum.default('Full').describe('Extraction depth applied to all assets. Full is the default since cascade is typically used for deep analysis.'),
|
|
141
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".'),
|
|
142
231
|
},
|
|
143
232
|
annotations: {
|
|
144
233
|
title: 'Extract Cascade',
|
|
@@ -147,12 +236,13 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
147
236
|
idempotentHint: true,
|
|
148
237
|
openWorldHint: false,
|
|
149
238
|
},
|
|
150
|
-
}, async ({ asset_paths, scope, max_depth }) => {
|
|
239
|
+
}, async ({ asset_paths, scope, max_depth, graph_filter, compact }) => {
|
|
151
240
|
try {
|
|
152
241
|
const result = await client.callSubsystem('ExtractCascade', {
|
|
153
242
|
AssetPathsJson: JSON.stringify(asset_paths),
|
|
154
243
|
Scope: scope,
|
|
155
244
|
MaxDepth: max_depth,
|
|
245
|
+
GraphFilter: graph_filter ? graph_filter.join(',') : '',
|
|
156
246
|
});
|
|
157
247
|
const parsed = JSON.parse(result);
|
|
158
248
|
if (parsed.error) {
|
|
@@ -164,20 +254,20 @@ RETURNS: Summary with extracted_count and output_directory path. Read the output
|
|
|
164
254
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
165
255
|
}
|
|
166
256
|
});
|
|
167
|
-
// Tool
|
|
257
|
+
// Tool 6: search_assets
|
|
168
258
|
server.registerTool('search_assets', {
|
|
169
259
|
title: 'Search Assets',
|
|
170
|
-
description: `Search for UE5 assets by name. This is a lightweight lookup — use it FIRST to find correct asset paths before calling extract_blueprint or
|
|
260
|
+
description: `Search for UE5 assets by name. This is a lightweight lookup — use it FIRST to find correct asset paths before calling extract_blueprint, extract_statetree, extract_dataasset, or extract_datatable.
|
|
171
261
|
|
|
172
262
|
USAGE GUIDELINES:
|
|
173
|
-
- Always call this before
|
|
263
|
+
- Always call this before any extract_* tool if you don't already have the exact asset path.
|
|
174
264
|
- Searches asset names (not full paths) — partial matches work (e.g. "Character" finds "BP_Character").
|
|
175
|
-
- Filter by class to narrow results: "Blueprint" (default), "StateTree", "WidgetBlueprint", "DataAsset", or empty string for all.
|
|
265
|
+
- Filter by class to narrow results: "Blueprint" (default), "AnimBlueprint", "StateTree", "DataTable", "WidgetBlueprint", "DataAsset", or empty string for all.
|
|
176
266
|
|
|
177
267
|
RETURNS: JSON array of objects with path, name, and class for each matching asset.`,
|
|
178
268
|
inputSchema: {
|
|
179
269
|
query: z.string().describe('Search term to match against asset names. Partial matches work (e.g. "Player" finds "BP_PlayerCharacter").'),
|
|
180
|
-
class_filter: z.string().default('Blueprint').describe('Filter by asset class. Common values: "Blueprint", "WidgetBlueprint", "StateTree", "DataAsset", or "" for all asset types.'),
|
|
270
|
+
class_filter: z.string().default('Blueprint').describe('Filter by asset class. Common values: "Blueprint", "AnimBlueprint", "WidgetBlueprint", "StateTree", "DataTable", "DataAsset", or "" for all asset types.'),
|
|
181
271
|
},
|
|
182
272
|
annotations: {
|
|
183
273
|
title: 'Search Assets',
|
|
@@ -199,7 +289,7 @@ RETURNS: JSON array of objects with path, name, and class for each matching asse
|
|
|
199
289
|
return { content: [{ type: 'text', text: `Error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
|
|
200
290
|
}
|
|
201
291
|
});
|
|
202
|
-
// Tool
|
|
292
|
+
// Tool 7: list_assets
|
|
203
293
|
server.registerTool('list_assets', {
|
|
204
294
|
title: 'List Assets',
|
|
205
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.
|