blueprint-extractor-mcp 6.0.3 → 6.0.5

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/README.md CHANGED
@@ -23,7 +23,7 @@ Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotoc
23
23
  AI Assistant stdio MCP Server HTTP :30010 Unreal Editor
24
24
  ───────────── ◄────────────► ───────────────── ◄──────────────────► ─────────────────
25
25
  Claude Code Node.js process Remote Control API
26
- Codex 89 tools BlueprintExtractor
26
+ Codex 95 tools BlueprintExtractor
27
27
  ... 4 resource templates plugin
28
28
  8 prompts
29
29
  ```
@@ -35,9 +35,10 @@ Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotoc
35
35
  | **Extract** | Read Blueprints, widgets, materials, animations, data assets, state trees, and more |
36
36
  | **Author** | Create and modify widgets, materials, Blueprints, input actions, AI assets, data tables |
37
37
  | **Build** | Compile project code, trigger Live Coding, restart the editor, sync changes |
38
+ | **PIE** | Start, stop, and relaunch Play-In-Editor sessions from the active editor |
38
39
  | **Import** | Bring in textures, meshes, and generic assets with async job polling |
39
40
  | **Test** | Run UE automation tests, collect results and artifacts |
40
- | **Verify** | Capture widget screenshots, compare against references, inspect motion checkpoints |
41
+ | **Verify** | Capture widget previews, editor screenshots, runtime screenshots, compare against references, inspect motion checkpoints |
41
42
 
42
43
  <br>
43
44
 
@@ -68,7 +69,7 @@ Connects to the editor at `127.0.0.1:30010` by default.
68
69
  ```bash
69
70
  claude mcp add -s user -t stdio blueprint-extractor \
70
71
  -e UE_REMOTE_CONTROL_PORT=30010 \
71
- -- npx -y blueprint-extractor-mcp@latest
72
+ -- npx -y blueprint-extractor-mcp@6.0.5
72
73
  ```
73
74
 
74
75
  </td></tr>
@@ -77,7 +78,7 @@ claude mcp add -s user -t stdio blueprint-extractor \
77
78
 
78
79
  ```bash
79
80
  codex mcp add --env UE_REMOTE_CONTROL_PORT=30010 \
80
- blueprint-extractor -- npx -y blueprint-extractor-mcp@latest
81
+ blueprint-extractor -- npx -y blueprint-extractor-mcp@6.0.5
81
82
  ```
82
83
 
83
84
  </td></tr>
@@ -101,8 +102,8 @@ Only **~13 core tools** are visible by default to keep the context window lean.
101
102
  | `animation_authoring` | 7 | Anim sequences, montages, blend spaces, widget motion |
102
103
  | `data_tables` | 7 | Data assets, data tables, curves, Enhanced Input actions & mappings |
103
104
  | `import` | 3 | `import_assets` with texture/mesh options, job polling |
104
- | `automation_testing` | 4 | `run_automation_tests` + run inspection and artifact retrieval |
105
- | `verification` | 7 | Widget captures, motion checkpoint bundles, reference comparisons |
105
+ | `automation_testing` | 7 | `run_automation_tests`, run inspection, project automation context, PIE lifecycle control |
106
+ | `verification` | 9 | Widget captures, editor/runtime screenshots, motion checkpoint bundles, reference comparisons |
106
107
 
107
108
  ### Contract Design
108
109
 
@@ -115,6 +116,8 @@ The tool contract is optimized for model reliability:
115
116
  - **Explicit-save semantics** &mdash; nothing persists until `save_assets` is called
116
117
  - **Next-step hints** guiding the assistant toward the logical follow-up action
117
118
 
119
+ See [../docs/CURRENT_STATUS.md](../docs/CURRENT_STATUS.md) for the current validation snapshot, normative docs, and the one-shot stabilization ledger.
120
+
118
121
  <br>
119
122
 
120
123
  ## Configuration
@@ -130,6 +133,8 @@ The tool contract is optimized for model reliability:
130
133
  | `UE_BUILD_PLATFORM` | &mdash; | e.g. `Win64` |
131
134
  | `UE_BUILD_CONFIGURATION` | &mdash; | e.g. `Development` |
132
135
 
136
+ `get_project_automation_context` surfaces the editor-derived `engineRoot`, `projectFilePath`, `editorTarget`, and `isPlayingInEditor` state that project-control and verification flows use for fallback or guard logic.
137
+
133
138
  <br>
134
139
 
135
140
  ## Resources & Prompts
@@ -175,7 +180,12 @@ npm test # unit + stdio integration
175
180
  | `npm run test:publish-gate` | Version consistency and publish readiness |
176
181
  | `BLUEPRINT_EXTRACTOR_LIVE_E2E=1 npm run test:live` | Full end-to-end against a running editor |
177
182
 
178
- The live suite exercises texture/mesh import via HTTP fixtures, material authoring workflows, Enhanced Input round-trips, and asset persistence.
183
+ The live suite exercises texture/mesh import via HTTP fixtures, material authoring workflows, Enhanced Input round-trips, widget authoring, and project-control round-trips.
184
+
185
+ The UE runner keeps two explicit lanes:
186
+
187
+ - headless/default: `BlueprintExtractor` with `-NullRHI`
188
+ - rendered verification: targeted filters with `-NoNullRHI` and optional `-AllowSoftwareRendering`
179
189
 
180
190
  <br>
181
191
 
@@ -4,18 +4,18 @@ export const exampleCatalog = {
4
4
  summary: 'Inspect the current widget, apply the smallest structural change that solves the layout problem, compile, visually confirm the rendered result, then save.',
5
5
  recommended_flow: [
6
6
  'extract_widget_blueprint',
7
- 'modify_widget_blueprint',
8
- 'compile_widget_blueprint',
7
+ 'patch_widget',
8
+ 'batch_widget_operations',
9
+ 'compile_widget',
9
10
  'capture_widget_preview',
10
11
  'save_assets',
11
12
  ],
12
13
  examples: [
13
14
  {
14
15
  title: 'patch_title_text',
15
- tool: 'modify_widget_blueprint',
16
+ tool: 'patch_widget',
16
17
  arguments: {
17
18
  asset_path: '/Game/UI/WBP_Window',
18
- operation: 'patch_widget',
19
19
  widget_path: 'WindowRoot/TitleBar/TitleText',
20
20
  properties: { Text: 'Window' },
21
21
  compile_after: true,
@@ -23,10 +23,9 @@ export const exampleCatalog = {
23
23
  },
24
24
  {
25
25
  title: 'insert_body_text',
26
- tool: 'modify_widget_blueprint',
26
+ tool: 'batch_widget_operations',
27
27
  arguments: {
28
28
  asset_path: '/Game/UI/WBP_Window',
29
- operation: 'batch',
30
29
  operations: [
31
30
  {
32
31
  operation: 'insert_child',
@@ -49,10 +48,11 @@ export const exampleCatalog = {
49
48
  'normalize_ui_design_input',
50
49
  'design_menu_from_design_spec',
51
50
  'extract_widget_blueprint',
52
- 'import_textures',
51
+ 'import_assets',
53
52
  'create_material_instance',
54
- 'modify_widget_blueprint',
55
- 'compile_widget_blueprint',
53
+ 'patch_widget',
54
+ 'batch_widget_operations',
55
+ 'compile_widget',
56
56
  'capture_widget_preview',
57
57
  'compare_capture_to_reference',
58
58
  'save_assets',
@@ -60,10 +60,9 @@ export const exampleCatalog = {
60
60
  examples: [
61
61
  {
62
62
  title: 'text_image_menu_screen',
63
- tool: 'modify_widget_blueprint',
63
+ tool: 'patch_widget',
64
64
  arguments: {
65
65
  asset_path: '/Game/UI/Screens/WBP_MainMenu',
66
- operation: 'patch_widget',
67
66
  widget_path: 'WindowRoot/TitleBar/TitleText',
68
67
  properties: { Text: 'Campaign' },
69
68
  compile_after: true,
@@ -432,6 +431,64 @@ export const exampleCatalog = {
432
431
  },
433
432
  ],
434
433
  },
434
+ data_asset_instanced_graph: {
435
+ summary: 'Use generic DataAsset reflection for asset-owned config data, including inline instanced UObject graphs for UPROPERTY(Instanced) and EditInlineNew values.',
436
+ recommended_flow: [
437
+ 'create_data_asset',
438
+ 'modify_data_asset',
439
+ 'extract_asset',
440
+ 'save_assets',
441
+ ],
442
+ examples: [
443
+ {
444
+ title: 'create_inline_object_graph',
445
+ tool: 'create_data_asset',
446
+ arguments: {
447
+ asset_class_path: '/Script/BlueprintExtractorFixture.BlueprintExtractorFixtureDataAsset',
448
+ asset_path: '/Game/Data/DA_MenuConfig',
449
+ properties: {
450
+ Count: 7,
451
+ InlineObject: {
452
+ classPath: '/Script/BlueprintExtractorFixture.BlueprintExtractorFixtureInlineObject',
453
+ properties: {
454
+ Label: 'Root',
455
+ Count: 11,
456
+ Child: {
457
+ classPath: '/Script/BlueprintExtractorFixture.BlueprintExtractorFixtureInlineObject',
458
+ properties: {
459
+ Label: 'Leaf',
460
+ Count: 12,
461
+ },
462
+ },
463
+ },
464
+ },
465
+ },
466
+ },
467
+ },
468
+ {
469
+ title: 'patch_inline_object_graph',
470
+ tool: 'modify_data_asset',
471
+ arguments: {
472
+ asset_path: '/Game/Data/DA_MenuConfig.DA_MenuConfig',
473
+ properties: {
474
+ Count: 8,
475
+ InlineObject: {
476
+ properties: {
477
+ Label: 'RootModified',
478
+ Count: 13,
479
+ Child: {
480
+ properties: {
481
+ Label: 'LeafModified',
482
+ Count: 14,
483
+ },
484
+ },
485
+ },
486
+ },
487
+ },
488
+ },
489
+ },
490
+ ],
491
+ },
435
492
  window_ui_polish: {
436
493
  summary: 'Use the thin sequencing helper when a screen change touches variable flags, class defaults, compile, and optional code sync in one flow, then gate persistence on visual confirmation.',
437
494
  recommended_flow: [
@@ -461,6 +518,46 @@ export const exampleCatalog = {
461
518
  },
462
519
  ],
463
520
  },
521
+ pie_and_screenshots: {
522
+ summary: 'Use explicit PIE lifecycle controls for live editor sessions, and use the editor/runtime screenshot tools when rendered evidence is needed in the shared verification-artifact format.',
523
+ recommended_flow: [
524
+ 'get_project_automation_context',
525
+ 'start_pie',
526
+ 'capture_editor_screenshot',
527
+ 'capture_runtime_screenshot',
528
+ 'stop_pie',
529
+ ],
530
+ examples: [
531
+ {
532
+ title: 'start_play_session',
533
+ tool: 'start_pie',
534
+ arguments: {
535
+ simulate: false,
536
+ },
537
+ },
538
+ {
539
+ title: 'capture_editor_viewport',
540
+ tool: 'capture_editor_screenshot',
541
+ arguments: {},
542
+ },
543
+ {
544
+ title: 'capture_runtime_frame_from_automation',
545
+ tool: 'capture_runtime_screenshot',
546
+ arguments: {
547
+ automation_filter: 'BlueprintExtractor.ProjectControl.PIEAndScreenshots',
548
+ engine_root: 'C:/Program Files/Epic Games/UE_5.7',
549
+ project_path: 'C:/Projects/MyGame/MyGame.uproject',
550
+ target: 'MyGameEditor',
551
+ null_rhi: false,
552
+ },
553
+ },
554
+ {
555
+ title: 'stop_play_session',
556
+ tool: 'stop_pie',
557
+ arguments: {},
558
+ },
559
+ ],
560
+ },
464
561
  project_code: {
465
562
  summary: 'Use explicit changed_paths so build-vs-live-coding decisions stay deterministic.',
466
563
  recommended_flow: [
@@ -0,0 +1,2 @@
1
+ export declare const packageName: string;
2
+ export declare const packageVersion: string;
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ const packageJson = require('../../package.json');
4
+ export const packageName = packageJson.name ?? 'blueprint-extractor-mcp';
5
+ export const packageVersion = packageJson.version ?? '0.0.0';
@@ -18,5 +18,6 @@ type ResolveProjectInputsDeps = {
18
18
  };
19
19
  export declare function rememberExternalBuild(result: CompileProjectCodeResult): Record<string, unknown>;
20
20
  export declare function getProjectAutomationContext(deps: GetProjectAutomationContextDeps): Promise<ProjectAutomationContext>;
21
+ export declare const HEURISTIC_ENGINE_CANDIDATES: string[];
21
22
  export declare function resolveProjectInputs(request: ProjectInputsRequest, deps: ResolveProjectInputsDeps): Promise<ResolvedProjectInputs>;
22
23
  export {};
@@ -31,7 +31,8 @@ export async function getProjectAutomationContext(deps) {
31
31
  return nextContext;
32
32
  }
33
33
  let cachedHeuristicEngineRoot;
34
- const HEURISTIC_ENGINE_CANDIDATES = [
34
+ export const HEURISTIC_ENGINE_CANDIDATES = [
35
+ 'C:/Program Files/Epic Games/UE_5.7',
35
36
  'C:/Program Files/Epic Games/UE_5.6',
36
37
  'C:/Program Files/Epic Games/UE_5.5',
37
38
  'C:/Program Files/Epic Games/UE_5.4',
@@ -62,6 +62,8 @@ function extractFailureMessage(parsed) {
62
62
  return parsed.errorMessage;
63
63
  if (typeof parsed.error === 'string' && parsed.error.length > 0)
64
64
  return parsed.error;
65
+ if (typeof parsed.errorSummary === 'string' && parsed.errorSummary.length > 0)
66
+ return parsed.errorSummary;
65
67
  if (Array.isArray(parsed.diagnostics) && parsed.diagnostics.length > 0) {
66
68
  const messages = parsed.diagnostics
67
69
  .filter((d) => typeof d === 'object' && d !== null && typeof d.message === 'string')
@@ -101,12 +101,29 @@ export function summarizeOutputSchema(schema) {
101
101
  return summary;
102
102
  }
103
103
  export function collectToolExampleFamilies(exampleCatalog, toolName) {
104
+ const equivalentToolNames = new Set([toolName]);
105
+ if (toolName === 'modify_widget_blueprint') {
106
+ [
107
+ 'patch_widget',
108
+ 'patch_widget_class_defaults',
109
+ 'insert_widget_child',
110
+ 'remove_widget',
111
+ 'move_widget',
112
+ 'wrap_widget',
113
+ 'replace_widget_class',
114
+ 'replace_widget_tree',
115
+ 'batch_widget_operations',
116
+ ].forEach((name) => equivalentToolNames.add(name));
117
+ }
118
+ if (toolName === 'compile_widget_blueprint') {
119
+ equivalentToolNames.add('compile_widget');
120
+ }
104
121
  return Object.entries(exampleCatalog)
105
122
  .flatMap(([family, entry]) => {
106
123
  const exampleTitles = entry.examples
107
- .filter((example) => example.tool === toolName)
124
+ .filter((example) => equivalentToolNames.has(example.tool))
108
125
  .map((example) => example.title);
109
- const usedInRecommendedFlow = entry.recommended_flow.includes(toolName);
126
+ const usedInRecommendedFlow = entry.recommended_flow.some((name) => equivalentToolNames.has(name));
110
127
  if (!usedInRecommendedFlow && exampleTitles.length === 0) {
111
128
  return [];
112
129
  }
@@ -136,6 +153,14 @@ export function collectRelatedResources(toolName) {
136
153
  resources.add('blueprint://widget-best-practices');
137
154
  resources.add('blueprint://verification-workflows');
138
155
  }
156
+ if (toolName === 'capture_editor_screenshot'
157
+ || toolName === 'capture_runtime_screenshot'
158
+ || toolName === 'start_pie'
159
+ || toolName === 'stop_pie'
160
+ || toolName === 'relaunch_pie') {
161
+ resources.add('blueprint://verification-workflows');
162
+ resources.add('blueprint://project-automation');
163
+ }
139
164
  if (toolName.includes('material')) {
140
165
  resources.add('blueprint://material-graph-guidance');
141
166
  }
@@ -147,7 +172,10 @@ export function collectRelatedResources(toolName) {
147
172
  || toolName === 'trigger_live_coding'
148
173
  || toolName === 'restart_editor'
149
174
  || toolName === 'wait_for_editor'
150
- || toolName === 'sync_project_code') {
175
+ || toolName === 'sync_project_code'
176
+ || toolName === 'start_pie'
177
+ || toolName === 'stop_pie'
178
+ || toolName === 'relaunch_pie') {
151
179
  resources.add('blueprint://project-automation');
152
180
  }
153
181
  if (toolName.includes('animation')
@@ -1,17 +1,36 @@
1
1
  import { buildCaptureResourceUri } from './capture.js';
2
2
  import { firstDefinedString, isPlainObject, } from './formatting.js';
3
- function isVerificationSurface(value) {
3
+ function normalizeVerificationSurface(value) {
4
+ if (value === 'editor_viewport') {
5
+ return 'editor_tool_viewport';
6
+ }
7
+ if (value === 'runtime_viewport') {
8
+ return 'pie_runtime';
9
+ }
4
10
  return value === 'editor_offscreen'
5
11
  || value === 'pie_runtime'
6
12
  || value === 'editor_tool_viewport'
7
13
  || value === 'external_packaged'
8
- || value === 'widget_motion_checkpoint';
14
+ || value === 'widget_motion_checkpoint'
15
+ ? value
16
+ : undefined;
17
+ }
18
+ function isVerificationSurface(value) {
19
+ return normalizeVerificationSurface(value) !== undefined;
9
20
  }
10
21
  function inferVerificationSurface(captureType) {
11
- if (isVerificationSurface(captureType)) {
12
- return captureType;
22
+ const normalizedSurface = normalizeVerificationSurface(captureType);
23
+ if (normalizedSurface) {
24
+ return normalizedSurface;
13
25
  }
14
26
  switch (captureType) {
27
+ case 'editor_screenshot':
28
+ return 'editor_tool_viewport';
29
+ case 'runtime_screenshot':
30
+ case 'automation_screenshot':
31
+ case 'automation_image_artifact':
32
+ case 'automation_diff':
33
+ return 'pie_runtime';
15
34
  case 'widget_motion_checkpoint':
16
35
  return 'widget_motion_checkpoint';
17
36
  case 'widget_preview':
@@ -54,6 +73,28 @@ function buildDefaultWorldContext(payload, surface) {
54
73
  }
55
74
  return context;
56
75
  }
76
+ if (surface === 'editor_tool_viewport') {
77
+ return {
78
+ contextType: 'editor_viewport',
79
+ renderLane: 'viewport',
80
+ ...(typeof payload.assetPath === 'string' && payload.assetPath.length > 0
81
+ ? { assetPath: payload.assetPath }
82
+ : {}),
83
+ ...(typeof payload.isPlayingInEditor === 'boolean'
84
+ ? { isPlayingInEditor: payload.isPlayingInEditor }
85
+ : { isPlayingInEditor: false }),
86
+ };
87
+ }
88
+ if (surface === 'pie_runtime') {
89
+ return {
90
+ contextType: 'runtime_viewport',
91
+ renderLane: 'viewport',
92
+ ...(typeof payload.assetPath === 'string' && payload.assetPath.length > 0
93
+ ? { assetPath: payload.assetPath }
94
+ : {}),
95
+ isPlayingInEditor: true,
96
+ };
97
+ }
57
98
  if (surface === 'widget_motion_checkpoint') {
58
99
  const context = {
59
100
  contextType: 'widget_motion',
@@ -91,6 +132,13 @@ function buildDefaultCameraContext(payload, surface) {
91
132
  ...(typeof height === 'number' ? { height } : {}),
92
133
  };
93
134
  }
135
+ if (surface === 'editor_tool_viewport' || surface === 'pie_runtime') {
136
+ return {
137
+ contextType: surface === 'editor_tool_viewport' ? 'editor_viewport' : 'runtime_viewport',
138
+ ...(typeof width === 'number' ? { width } : {}),
139
+ ...(typeof height === 'number' ? { height } : {}),
140
+ };
141
+ }
94
142
  if (surface === 'widget_motion_checkpoint') {
95
143
  return {
96
144
  contextType: 'motion_checkpoint',
@@ -127,7 +175,7 @@ function normalizeAutomationVerificationArtifacts(payload) {
127
175
  return existing.map((artifact) => {
128
176
  const normalized = normalizeVerificationArtifact({
129
177
  ...artifact,
130
- surface: isVerificationSurface(artifact.surface) ? artifact.surface : 'pie_runtime',
178
+ surface: normalizeVerificationSurface(artifact.surface) ?? 'pie_runtime',
131
179
  captureType: typeof artifact.captureType === 'string' && artifact.captureType.length > 0
132
180
  ? artifact.captureType
133
181
  : 'automation_image_artifact',
@@ -198,9 +246,11 @@ export function normalizeVerificationArtifact(payload) {
198
246
  : assetPath
199
247
  ? [assetPath]
200
248
  : [];
201
- const surface = isVerificationSurface(basePayload.surface)
202
- ? basePayload.surface
203
- : inferVerificationSurface(basePayload.captureType);
249
+ const surface = normalizeVerificationSurface(basePayload.surface)
250
+ ?? (isVerificationSurface(basePayload.surface)
251
+ ? basePayload.surface
252
+ : undefined)
253
+ ?? inferVerificationSurface(basePayload.captureType);
204
254
  const worldContext = buildDefaultWorldContext(basePayload, surface);
205
255
  const cameraContext = buildDefaultCameraContext(basePayload, surface);
206
256
  return {
@@ -260,7 +260,8 @@ export function registerExampleAndCaptureResources({ server, automationControlle
260
260
  'Blueprint Extractor Unsupported Surfaces',
261
261
  '',
262
262
  '- Generic create_data_asset and modify_data_asset reject Enhanced Input asset classes. Use the dedicated InputAction/InputMappingContext tools instead.',
263
- '- modify_material and modify_material_function remain available but are advanced escape hatches, not the primary authoring workflow.',
263
+ '- Generic DataAsset reflection does support inline instanced UObject graphs for asset-owned config, but it does not support literal embedded AActor instances.',
264
+ '- modify_material remains available as an advanced escape hatch, but the canonical authoring path is create_material plus material_graph_operation for settings, node creation, wiring, and root-property binding.',
264
265
  '- There is still no first-class Substrate graph DSL.',
265
266
  '- CommonUI wrapper widgets are not a backdoor into internal Slate/UButton background or style fields. For CommonButtonBase-family widgets, treat raw UButton background/style properties as unsupported and use extract_commonui_button_style, create_commonui_button_style, modify_commonui_button_style, or apply_commonui_button_style.',
266
267
  '- Dedicated widget animation authoring is supported only for the constrained supported track subset. Unsupported track families and broader arbitrary MovieScene synthesis remain outside the public contract.',
@@ -281,9 +282,9 @@ export function registerExampleAndCaptureResources({ server, automationControlle
281
282
  '2. search_assets and extract the current HUD, transition widgets, and target screen widgets.',
282
283
  '3. Inspect class defaults, BindWidget names, and current activatable-window flow before replacing any widget tree.',
283
284
  '4. Choose a preset layout pattern such as centered_overlay, common_menu_shell, activatable_window, or list_detail.',
284
- '5. Apply the smallest modify_widget_blueprint patch possible. Only use build_widget_tree or replace_tree when broad structure must change.',
285
+ '5. Apply the smallest widget operation possible: patch_widget, insert_widget_child, remove_widget, move_widget, wrap_widget, replace_widget_class, or batch_widget_operations. Only use replace_widget_tree when broad structure must change.',
285
286
  '6. If the redesign includes authored motion on the supported track subset, use create_widget_animation or modify_widget_animation instead of trying to encode that work through generic widget patches.',
286
- '7. Compile immediately after structural or animation changes. If compile fails, inspect compile diagnostics and rerun the smallest recovery patch first.',
287
+ '7. Compile immediately after structural or animation changes with compile_widget. If compile fails, inspect compile diagnostics and rerun the smallest recovery patch first.',
287
288
  '8. Run capture_widget_preview or capture_widget_motion_checkpoints after the compile result is clean so the rendered result is visually confirmed for each required checkpoint.',
288
289
  '9. If reference images or checkpoint frames exist, run compare_capture_to_reference or compare_motion_capture_bundle for key states before save_assets.',
289
290
  '10. Save after capture or compare succeeds, or report lower-confidence / partial verification explicitly when the visual checkpoint is blocked.',
@@ -38,13 +38,13 @@ export function registerStaticDocResources(server) {
38
38
  '- Write responses include: success, operation, assetPath, assetClass, changedObjects, dirtyPackages, diagnostics, and optional validation/compile summaries.',
39
39
  '',
40
40
  'Current write-capable families:',
41
- '- WidgetBlueprint: create_widget_blueprint, build_widget_tree, modify_widget, modify_widget_blueprint, compile_widget_blueprint',
41
+ '- WidgetBlueprint: create_widget_blueprint, replace_widget_tree, patch_widget, insert_widget_child, remove_widget, move_widget, wrap_widget, replace_widget_class, batch_widget_operations, compile_widget. Deprecated compatibility aliases remain available for modify_widget_blueprint, build_widget_tree, and compile_widget_blueprint.',
42
42
  '- CommonUI button style Blueprints: extract_commonui_button_style, create_commonui_button_style, modify_commonui_button_style, apply_commonui_button_style',
43
- '- DataAsset: create_data_asset, modify_data_asset',
43
+ '- DataAsset: create_data_asset, modify_data_asset. Inline instanced UObject graphs are supported for UPROPERTY(Instanced) / EditInlineNew values.',
44
44
  '- DataTable: create_data_table, modify_data_table',
45
45
  '- Curve: create_curve, modify_curve',
46
46
  '- CurveTable: create_curve_table, modify_curve_table',
47
- '- Material graph assets: extract_material, create_material, modify_material, extract_material_function, create_material_function, modify_material_function, compile_material_asset',
47
+ '- Material graph assets: extract_material, create_material, modify_material, compile_material_asset. Use asset_kind="function", "layer", or "layer_blend" for material-family variants.',
48
48
  '- MaterialInstance: create_material_instance, modify_material_instance',
49
49
  '- UserDefinedStruct: create_user_defined_struct, modify_user_defined_struct',
50
50
  '- UserDefinedEnum: create_user_defined_enum, modify_user_defined_enum',
@@ -73,11 +73,11 @@ export function registerStaticDocResources(server) {
73
73
  '- Blueprint graphs: operation upsert_function_graphs preserves unrelated graphs; append_function_call_to_sequence patches an existing sequence-style initializer without replacing the whole graph.',
74
74
  '',
75
75
  'WidgetBlueprint guidance:',
76
- '- build_widget_tree is the destructive bootstrap path for whole-tree replacement.',
76
+ '- replace_widget_tree is the destructive bootstrap path for whole-tree replacement.',
77
77
  '- extract_widget_blueprint returns a compact authoring snapshot with widgetPath annotations and additive packagePath/objectPath fields.',
78
78
  '- modify_widget supports direct widget_name or widget_path patches for one widget.',
79
- '- modify_widget_blueprint is the primary structural API: replace_tree, patch_widget, patch_class_defaults, insert_child, remove_widget, move_widget, wrap_widget, replace_widget_class, batch, or compile.',
80
- '- compile_widget_blueprint validates the asset but still does not save it.',
79
+ '- Prefer operation-specific widget tools such as patch_widget, patch_widget_class_defaults, insert_widget_child, remove_widget, move_widget, wrap_widget, replace_widget_class, and batch_widget_operations.',
80
+ '- compile_widget validates the asset but still does not save it.',
81
81
  '- apply_window_ui_changes is a thin MCP helper that sequences variable-flag updates, class defaults, optional font work, compile, optional save, and optional code sync.',
82
82
  '',
83
83
  'Explicit deferrals:',
@@ -130,7 +130,7 @@ export function registerStaticDocResources(server) {
130
130
  '- Keep payloads small by sending only changed fields, not full extracted objects, unless the tool explicitly expects a full replacement payload.',
131
131
  '- Re-extract after mutation when you need confirmation; do not assume UE normalized fields exactly as sent.',
132
132
  '- For fidelity-sensitive menu work, normalize text, image, Figma-export, or HTML/CSS inputs into design_spec_json before authoring assets.',
133
- '- For multi-step widget work, prefer extract_widget_blueprint -> modify_widget_blueprint -> compile_widget_blueprint -> capture_widget_preview -> save_assets.',
133
+ '- For multi-step widget work, prefer extract_widget_blueprint -> patch_widget or batch_widget_operations -> compile_widget -> capture_widget_preview -> save_assets.',
134
134
  '- For authored widget motion, prefer extract_widget_animation -> create_widget_animation or modify_widget_animation -> capture_widget_motion_checkpoints -> compare_motion_capture_bundle before save_assets.',
135
135
  '- When reference frames exist, extend the widget flow to compare_capture_to_reference before save_assets. Without reference frames, capture and report lower-confidence or partial verification explicitly.',
136
136
  '- If widget preview capture is blocked, report partial verification explicitly instead of treating compile/save as visual proof.',
@@ -218,12 +218,13 @@ export function registerStaticDocResources(server) {
218
218
  registerStaticTextResource(server, 'project-automation-guidance', 'blueprint://project-automation', 'Host/editor project automation guidance for build, Live Coding, restart, and reconnect flows.', [
219
219
  'Blueprint Extractor Project Automation',
220
220
  '',
221
- '- get_project_automation_context returns the editor-derived engine root, project file path, and editor target that project-control tools use as their first fallback.',
221
+ '- get_project_automation_context returns the editor-derived engine root, project file path, editor target, and isPlayingInEditor state that project-control tools use as their first fallback.',
222
222
  '- compile_project_code runs an external UBT build from the MCP host.',
223
223
  '- compile_project_code and sync_project_code resolve engine_root, project_path, and target in this order: explicit args -> editor context -> environment.',
224
224
  '- trigger_live_coding requests an editor-side Live Coding compile and is only supported on Windows-focused setups. changed_paths remains an accepted compatibility input but the current editor-side trigger ignores it. When Live Coding reports NoChanges or another fallback state, the result includes fallbackRecommended, reason, and the last external build context when available.',
225
225
  '- restart_editor requests an editor restart, then waits for Remote Control to disconnect and reconnect. When save_dirty_assets is true, all dirty packages are saved before the restart to prevent modal save dialogs.',
226
226
  '- wait_for_editor polls Remote Control once per second and returns a normalized readiness result that callers can use after restart windows or transient disconnects.',
227
+ '- start_pie, stop_pie, and relaunch_pie are the explicit live-editor PIE lifecycle controls.',
227
228
  '- sync_project_code requires explicit changed_paths and chooses Live Coding vs build_and_restart deterministically.',
228
229
  '- sync_project_code.restart_first now means shutdown-first: save/checkpoint if requested, ask the editor to close without relaunching, build with the DLL unlocked, then launch the editor from the MCP host and wait for reconnect.',
229
230
  '- apply_window_ui_changes can checkpoint after each mutation step without changing the low-level explicit-save contract. Use that when debugging editor ensures or breakpoint-heavy UI iterations.',
@@ -239,8 +240,8 @@ export function registerStaticDocResources(server) {
239
240
  'Blueprint Extractor Verification Workflows',
240
241
  '',
241
242
  'Three verification lanes work together:',
242
- '- Semantic verification: use extract_blueprint, extract_widget_blueprint, extract_asset, compile_widget_blueprint, compile_material_asset, and compile/save diagnostics to verify authored data, graph wiring, widget hierarchy, class defaults, and graph layout.',
243
- '- Visual verification: use capture_widget_preview and compare_capture_to_reference when structure can compile but the rendered result may still be wrong.',
243
+ '- Semantic verification: use extract_blueprint, extract_widget_blueprint, extract_asset, compile_widget, compile_material_asset, and compile/save diagnostics to verify authored data, graph wiring, widget hierarchy, class defaults, and graph layout.',
244
+ '- Visual verification: use capture_widget_preview, capture_editor_screenshot, capture_runtime_screenshot, and compare_capture_to_reference when structure can compile but the rendered result may still be wrong.',
244
245
  '- Gameplay/runtime verification: use run_automation_tests for mechanics, behavior, interactions, and scenario validation through Automation Specs or Functional Tests.',
245
246
  '',
246
247
  'Recommended mapping:',
@@ -287,7 +288,7 @@ export function registerStaticDocResources(server) {
287
288
  '1. Normalize raw text, image, PNG/Figma, or HTML/CSS inputs into design_spec_json.',
288
289
  '2. Inspect the current widget, HUD, transition widgets, and class defaults before rewriting structure.',
289
290
  '3. Author reusable foundation assets first under /Game/UI/Foundation/Materials, /Game/UI/Foundation/MaterialInstances, /Game/UI/Foundation/Styles, and /Game/UI/Foundation/Widgets.',
290
- '4. Assemble the menu screen under /Game/UI/Screens with modify_widget_blueprint and related existing tools.',
291
+ '4. Assemble the menu screen under /Game/UI/Screens with patch_widget, batch_widget_operations, and related widget tools.',
291
292
  '5. If motion is part of the spec, author or patch widget timelines with create_widget_animation or modify_widget_animation using the supported track subset.',
292
293
  '6. Compile, then capture key visual checkpoints such as open, focused, or pressed.',
293
294
  '7. If reference frames exist, compare captures with compare_capture_to_reference or compare_motion_capture_bundle before save_assets.',
@@ -847,17 +847,17 @@ export declare const MeshImportOptionsSchema: z.ZodObject<{
847
847
  generate_collision: z.ZodOptional<z.ZodBoolean>;
848
848
  skeleton_path: z.ZodOptional<z.ZodString>;
849
849
  }, "strip", z.ZodTypeAny, {
850
- import_textures?: boolean | undefined;
851
850
  mesh_type?: string | undefined;
852
851
  import_materials?: boolean | undefined;
852
+ import_textures?: boolean | undefined;
853
853
  import_animations?: boolean | undefined;
854
854
  combine_meshes?: boolean | undefined;
855
855
  generate_collision?: boolean | undefined;
856
856
  skeleton_path?: string | undefined;
857
857
  }, {
858
- import_textures?: boolean | undefined;
859
858
  mesh_type?: string | undefined;
860
859
  import_materials?: boolean | undefined;
860
+ import_textures?: boolean | undefined;
861
861
  import_animations?: boolean | undefined;
862
862
  combine_meshes?: boolean | undefined;
863
863
  generate_collision?: boolean | undefined;
@@ -1053,17 +1053,17 @@ export declare const MeshImportPayloadSchema: z.ZodObject<{
1053
1053
  generate_collision: z.ZodOptional<z.ZodBoolean>;
1054
1054
  skeleton_path: z.ZodOptional<z.ZodString>;
1055
1055
  }, "strip", z.ZodTypeAny, {
1056
- import_textures?: boolean | undefined;
1057
1056
  mesh_type?: string | undefined;
1058
1057
  import_materials?: boolean | undefined;
1058
+ import_textures?: boolean | undefined;
1059
1059
  import_animations?: boolean | undefined;
1060
1060
  combine_meshes?: boolean | undefined;
1061
1061
  generate_collision?: boolean | undefined;
1062
1062
  skeleton_path?: string | undefined;
1063
1063
  }, {
1064
- import_textures?: boolean | undefined;
1065
1064
  mesh_type?: string | undefined;
1066
1065
  import_materials?: boolean | undefined;
1066
+ import_textures?: boolean | undefined;
1067
1067
  import_animations?: boolean | undefined;
1068
1068
  combine_meshes?: boolean | undefined;
1069
1069
  generate_collision?: boolean | undefined;
@@ -1072,9 +1072,9 @@ export declare const MeshImportPayloadSchema: z.ZodObject<{
1072
1072
  }, "strip", z.ZodTypeAny, {
1073
1073
  url?: string | undefined;
1074
1074
  options?: {
1075
- import_textures?: boolean | undefined;
1076
1075
  mesh_type?: string | undefined;
1077
1076
  import_materials?: boolean | undefined;
1077
+ import_textures?: boolean | undefined;
1078
1078
  import_animations?: boolean | undefined;
1079
1079
  combine_meshes?: boolean | undefined;
1080
1080
  generate_collision?: boolean | undefined;
@@ -1091,9 +1091,9 @@ export declare const MeshImportPayloadSchema: z.ZodObject<{
1091
1091
  }, {
1092
1092
  url?: string | undefined;
1093
1093
  options?: {
1094
- import_textures?: boolean | undefined;
1095
1094
  mesh_type?: string | undefined;
1096
1095
  import_materials?: boolean | undefined;
1096
+ import_textures?: boolean | undefined;
1097
1097
  import_animations?: boolean | undefined;
1098
1098
  combine_meshes?: boolean | undefined;
1099
1099
  generate_collision?: boolean | undefined;
@@ -1112,9 +1112,9 @@ export declare const MeshImportPayloadSchema: z.ZodObject<{
1112
1112
  items: {
1113
1113
  url?: string | undefined;
1114
1114
  options?: {
1115
- import_textures?: boolean | undefined;
1116
1115
  mesh_type?: string | undefined;
1117
1116
  import_materials?: boolean | undefined;
1117
+ import_textures?: boolean | undefined;
1118
1118
  import_animations?: boolean | undefined;
1119
1119
  combine_meshes?: boolean | undefined;
1120
1120
  generate_collision?: boolean | undefined;
@@ -1133,9 +1133,9 @@ export declare const MeshImportPayloadSchema: z.ZodObject<{
1133
1133
  items: {
1134
1134
  url?: string | undefined;
1135
1135
  options?: {
1136
- import_textures?: boolean | undefined;
1137
1136
  mesh_type?: string | undefined;
1138
1137
  import_materials?: boolean | undefined;
1138
+ import_textures?: boolean | undefined;
1139
1139
  import_animations?: boolean | undefined;
1140
1140
  combine_meshes?: boolean | undefined;
1141
1141
  generate_collision?: boolean | undefined;
@@ -2,13 +2,13 @@ const EDITOR_UNAVAILABLE_MESSAGE_FRAGMENT = 'UE Editor not running or Remote Con
2
2
  const SUBSYSTEM_UNAVAILABLE_MESSAGE_FRAGMENT = 'BlueprintExtractor subsystem not found';
3
3
  export const EDITOR_POLL_INTERVAL_MS = 1_000;
4
4
  export const serverInstructions = [
5
- 'Blueprint Extractor MCP v5 uses workflow-scoped tool surfaces, snake_case arguments, prompt workflows, and structured JSON results.',
5
+ 'Blueprint Extractor MCP uses a v2 public contract with workflow-scoped tool surfaces, snake_case arguments, prompt workflows, and structured JSON results.',
6
6
  // Tool discovery
7
7
  'Only ~13 core tools are visible by default. Use activate_workflow_scope to load specialized tool families: widget_authoring (or sub-scopes: widget_authoring_structure, widget_authoring_visual, widget_verification), material_authoring, blueprint_authoring, schema_ai_authoring, animation_authoring, data_tables, import, automation_testing, verification.',
8
8
  'Use find_and_extract for search+extract in one call (activate any authoring scope to access it). Use search_assets when you only need to locate assets.',
9
9
  'Call get_tool_help before the first use of a complex or polymorphic tool when you need operation-specific payload guidance. This may also auto-activate the relevant workflow scope.',
10
10
  // Deferred tool directory (tools available via activate_workflow_scope)
11
- 'Deferred tool families — widget_authoring_structure: create/replace/patch/insert/remove/move/wrap widgets. widget_authoring_visual: CommonUI styles, widget animations, compile_widget, extraction. widget_verification: captures and comparisons. widget_authoring: activates all three widget sub-scopes. material_authoring: create/modify material, material_graph_operation, material instances. blueprint_authoring: create/modify blueprint members and graphs, trigger_live_coding. schema_ai_authoring: structs, enums, blackboards, behavior trees, state trees. animation_authoring: anim sequences, montages, blend spaces, widget animations. data_tables: data assets, input actions, tables, curves. import: import_assets, job tracking. automation_testing: run/get/list automation tests, project automation context. verification: widget captures and comparisons.',
11
+ 'Deferred tool families — widget_authoring_structure: create/replace/patch/insert/remove/move/wrap widgets. widget_authoring_visual: CommonUI styles, widget animations, compile_widget, extraction. widget_verification: captures and comparisons. widget_authoring: activates all three widget sub-scopes. material_authoring: create/modify material, material_graph_operation, material instances. blueprint_authoring: create/modify blueprint members and graphs, trigger_live_coding. schema_ai_authoring: structs, enums, blackboards, behavior trees, state trees. animation_authoring: anim sequences, montages, blend spaces, widget animations. data_tables: data assets, input actions, tables, curves. import: import_assets, job tracking. automation_testing: run/get/list automation tests, project automation context, and PIE lifecycle control. verification: widget captures, editor screenshots, runtime screenshots, and comparisons.',
12
12
  // Extraction
13
13
  'All extract_* tools default to compact: true. Pass compact: false for verbose output.',
14
14
  // Search
@@ -34,6 +34,7 @@ export const serverInstructions = [
34
34
  'Motion support includes dedicated widget animation authoring for render_opacity, render_transform translation/scale/angle, and color_and_opacity tracks. Treat broader arbitrary MovieScene track synthesis as deferred_to_future or unsupported when it exceeds that subset.',
35
35
  // Testing
36
36
  'Use run_automation_tests for gameplay or runtime verification. If no Automation Spec or Functional Test exists for a mechanic, report verification as partial instead of inferring success from structure alone.',
37
+ 'Use capture_runtime_screenshot when a runtime verification lane already exports screenshot artifacts through automation. Use capture_editor_screenshot for the active editor viewport when a rendered editor reference is needed.',
37
38
  // Results format
38
39
  'Successful tool results use structuredContent as the canonical JSON payload. Recoverable execution failures return isError=true with code, message, recoverable, and next_steps.',
39
40
  ].join('\n');
@@ -46,6 +47,7 @@ export const taskAwareTools = new Set([
46
47
  'get_import_job',
47
48
  'list_import_jobs',
48
49
  'run_automation_tests',
50
+ 'capture_runtime_screenshot',
49
51
  'get_automation_test_run',
50
52
  'list_automation_test_runs',
51
53
  ]);
@@ -142,12 +144,17 @@ export const TOOL_MODE_ANNOTATIONS = new Map([
142
144
  ['trigger_live_coding', 'editor_only'],
143
145
  ['sync_project_code', 'editor_only'],
144
146
  ['wait_for_editor', 'editor_only'],
147
+ ['start_pie', 'editor_only'],
148
+ ['stop_pie', 'editor_only'],
149
+ ['relaunch_pie', 'editor_only'],
145
150
  // ── Import (editor_only) ──
146
151
  ['import_assets', 'editor_only'],
147
152
  // ── Automation tests (editor_only) ──
148
153
  ['run_automation_tests', 'editor_only'],
149
154
  // ── Verification / captures (editor_only) ──
150
155
  ['capture_widget_preview', 'editor_only'],
156
+ ['capture_editor_screenshot', 'editor_only'],
157
+ ['capture_runtime_screenshot', 'editor_only'],
151
158
  ['capture_widget_motion_checkpoints', 'editor_only'],
152
159
  ['compare_capture_to_reference', 'editor_only'],
153
160
  ['compare_motion_capture_bundle', 'editor_only'],
@@ -1,6 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import { UEClient } from './ue-client.js';
4
+ import { packageVersion } from './helpers/package-metadata.js';
4
5
  import { ProjectController, } from './project-controller.js';
5
6
  import { AutomationController, } from './automation-controller.js';
6
7
  import { firstDefinedString } from './helpers/formatting.js';
@@ -23,7 +24,7 @@ export function createBlueprintExtractorServer(client = new UEClient(), projectC
23
24
  const registeredToolMap = new Map();
24
25
  const server = new McpServer({
25
26
  name: 'blueprint-extractor',
26
- version: '6.0.3',
27
+ version: packageVersion,
27
28
  }, {
28
29
  instructions: serverInstructions,
29
30
  });
@@ -7,6 +7,7 @@ export type ProjectAutomationContext = {
7
7
  engineDir?: string;
8
8
  engineRoot?: string;
9
9
  editorTarget?: string;
10
+ isPlayingInEditor?: boolean;
10
11
  hostPlatform?: string;
11
12
  supportsLiveCoding?: boolean;
12
13
  liveCodingAvailable?: boolean;
@@ -153,7 +153,7 @@ const SCOPE_DEFINITIONS = {
153
153
  prompts: [],
154
154
  tools: [
155
155
  'run_automation_tests', 'get_automation_test_run', 'list_automation_test_runs',
156
- 'get_project_automation_context',
156
+ 'get_project_automation_context', 'start_pie', 'stop_pie', 'relaunch_pie',
157
157
  ],
158
158
  },
159
159
  verification: {
@@ -161,6 +161,7 @@ const SCOPE_DEFINITIONS = {
161
161
  description: 'Widget verification and capture tools',
162
162
  prompts: [],
163
163
  tools: [
164
+ 'capture_editor_screenshot', 'capture_runtime_screenshot',
164
165
  'capture_widget_preview', 'capture_widget_motion_checkpoints',
165
166
  'compare_capture_to_reference', 'list_captures', 'cleanup_captures',
166
167
  'compare_motion_capture_bundle',
@@ -143,7 +143,10 @@ export function registerExtractionTools({ server, callSubsystemJson, scopeEnum,
143
143
  if (compact) {
144
144
  parsed = compactMaterial(parsed);
145
145
  }
146
- return jsonToolSuccess(parsed);
146
+ return jsonToolSuccess({
147
+ ...parsed,
148
+ operation: 'extract_material',
149
+ });
147
150
  }
148
151
  catch (error) {
149
152
  return jsonToolError(error);
@@ -66,7 +66,10 @@ export function registerImportJobTools({ server, callSubsystemJson, importJobSch
66
66
  PayloadJson: JSON.stringify(transformedPayload),
67
67
  bValidateOnly: validate_only,
68
68
  });
69
- return jsonToolSuccess(parsed);
69
+ return jsonToolSuccess({
70
+ ...parsed,
71
+ operation: 'import_assets',
72
+ });
70
73
  }
71
74
  catch (error) {
72
75
  return jsonToolError(error);
@@ -15,6 +15,12 @@ function structuredToolError(message, options = {}) {
15
15
  isError: true,
16
16
  };
17
17
  }
18
+ function normalizeMaterialToolOperation(parsed, operation) {
19
+ return {
20
+ ...parsed,
21
+ operation,
22
+ };
23
+ }
18
24
  export function registerMaterialAuthoringTools({ server, callSubsystemJson, jsonObjectSchema, materialNodePositionSchema, materialConnectionSelectorFieldsSchema, materialGraphOperationKindSchema, materialGraphOperationSchema, }) {
19
25
  server.registerTool('create_material', {
20
26
  title: 'Create Material',
@@ -42,7 +48,7 @@ export function registerMaterialAuthoringTools({ server, callSubsystemJson, json
42
48
  SettingsJson: JSON.stringify(settings ?? {}),
43
49
  bValidateOnly: validate_only,
44
50
  });
45
- return jsonToolSuccess(parsed);
51
+ return jsonToolSuccess(normalizeMaterialToolOperation(parsed, 'create_material'));
46
52
  }
47
53
  const parsed = await callSubsystemJson('CreateMaterialFunction', {
48
54
  AssetPath: asset_path,
@@ -50,7 +56,7 @@ export function registerMaterialAuthoringTools({ server, callSubsystemJson, json
50
56
  SettingsJson: JSON.stringify(settings ?? {}),
51
57
  bValidateOnly: validate_only,
52
58
  });
53
- return jsonToolSuccess(parsed);
59
+ return jsonToolSuccess(normalizeMaterialToolOperation(parsed, 'create_material'));
54
60
  }
55
61
  catch (error) {
56
62
  return jsonToolError(error);
@@ -171,7 +177,7 @@ export function registerMaterialAuthoringTools({ server, callSubsystemJson, json
171
177
  PayloadJson: JSON.stringify(payload),
172
178
  bValidateOnly: validate_only,
173
179
  });
174
- return jsonToolSuccess(parsed);
180
+ return jsonToolSuccess(normalizeMaterialToolOperation(parsed, 'modify_material'));
175
181
  }
176
182
  catch (error) {
177
183
  return jsonToolError(error);
@@ -43,6 +43,74 @@ export function registerProjectControlTools({ server, client, projectController,
43
43
  return jsonToolError(error);
44
44
  }
45
45
  });
46
+ server.registerTool('start_pie', {
47
+ title: 'Start PIE',
48
+ description: 'Request a Play-In-Editor session from the active editor.',
49
+ inputSchema: {
50
+ simulate: z.boolean().default(false).describe('When true, start Simulate-In-Editor instead of Play-In-Editor.'),
51
+ },
52
+ annotations: {
53
+ title: 'Start PIE',
54
+ readOnlyHint: false,
55
+ destructiveHint: false,
56
+ idempotentHint: false,
57
+ openWorldHint: false,
58
+ },
59
+ }, async ({ simulate }) => {
60
+ try {
61
+ const parsed = await callSubsystemJson('StartPIE', {
62
+ bSimulateInEditor: simulate,
63
+ });
64
+ return jsonToolSuccess(parsed);
65
+ }
66
+ catch (error) {
67
+ return jsonToolError(error);
68
+ }
69
+ });
70
+ server.registerTool('stop_pie', {
71
+ title: 'Stop PIE',
72
+ description: 'Stop the current Play-In-Editor session if one is active.',
73
+ inputSchema: {},
74
+ annotations: {
75
+ title: 'Stop PIE',
76
+ readOnlyHint: false,
77
+ destructiveHint: false,
78
+ idempotentHint: false,
79
+ openWorldHint: false,
80
+ },
81
+ }, async () => {
82
+ try {
83
+ const parsed = await callSubsystemJson('StopPIE', {});
84
+ return jsonToolSuccess(parsed);
85
+ }
86
+ catch (error) {
87
+ return jsonToolError(error);
88
+ }
89
+ });
90
+ server.registerTool('relaunch_pie', {
91
+ title: 'Relaunch PIE',
92
+ description: 'Restart the current Play-In-Editor session by stopping it and scheduling a fresh launch.',
93
+ inputSchema: {
94
+ simulate: z.boolean().default(false).describe('When true, relaunch into Simulate-In-Editor instead of Play-In-Editor.'),
95
+ },
96
+ annotations: {
97
+ title: 'Relaunch PIE',
98
+ readOnlyHint: false,
99
+ destructiveHint: false,
100
+ idempotentHint: false,
101
+ openWorldHint: false,
102
+ },
103
+ }, async ({ simulate }) => {
104
+ try {
105
+ const parsed = await callSubsystemJson('RelaunchPIE', {
106
+ bSimulateInEditor: simulate,
107
+ });
108
+ return jsonToolSuccess(parsed);
109
+ }
110
+ catch (error) {
111
+ return jsonToolError(error);
112
+ }
113
+ });
46
114
  server.registerTool('wait_for_editor', {
47
115
  title: 'Wait For Editor',
48
116
  description: 'Poll the editor connection once per second until Remote Control responds again or the timeout elapses.',
@@ -3,6 +3,23 @@ import { buildCaptureResourceUri, buildResourceLinkContent, maybeBuildInlineImag
3
3
  import { explainProjectResolutionFailure } from '../helpers/project-utils.js';
4
4
  import { jsonToolError, jsonToolSuccess } from '../helpers/subsystem.js';
5
5
  import { normalizeAutomationRunResult, normalizeVerificationArtifact, normalizeVerificationArtifactReference, normalizeVerificationComparison, } from '../helpers/verification.js';
6
+ async function buildCaptureExtraContent(artifact, label, description) {
7
+ const extraContent = [];
8
+ const resourceUri = typeof artifact.resourceUri === 'string'
9
+ ? artifact.resourceUri
10
+ : '';
11
+ const captureId = typeof artifact.captureId === 'string'
12
+ ? artifact.captureId
13
+ : '';
14
+ if (resourceUri) {
15
+ extraContent.push(buildResourceLinkContent(resourceUri, label || `Capture ${captureId || 'capture'}`, 'image/png', description));
16
+ }
17
+ const inlineImage = await maybeBuildInlineImageContent(typeof artifact.artifactPath === 'string' ? artifact.artifactPath : undefined);
18
+ if (inlineImage) {
19
+ extraContent.push(inlineImage);
20
+ }
21
+ return extraContent;
22
+ }
6
23
  export function registerWidgetVerificationTools({ server, callSubsystemJson, automationController, resolveProjectInputs, captureResultSchema, widgetAnimationCheckpointSchema, motionCaptureModeSchema, motionCaptureBundleResultSchema, compareCaptureResultSchema, listCapturesResultSchema, cleanupCapturesResultSchema, compareMotionCaptureBundleResultSchema, }) {
7
24
  server.registerTool('capture_widget_preview', {
8
25
  title: 'Capture Widget Preview',
@@ -47,6 +64,95 @@ export function registerWidgetVerificationTools({ server, callSubsystemJson, aut
47
64
  return jsonToolError(error);
48
65
  }
49
66
  });
67
+ server.registerTool('capture_editor_screenshot', {
68
+ title: 'Capture Editor Screenshot',
69
+ description: 'Capture the active editor viewport and return screenshot artifacts.',
70
+ inputSchema: {},
71
+ outputSchema: captureResultSchema,
72
+ annotations: {
73
+ title: 'Capture Editor Screenshot',
74
+ readOnlyHint: true,
75
+ destructiveHint: false,
76
+ idempotentHint: false,
77
+ openWorldHint: false,
78
+ },
79
+ }, async () => {
80
+ try {
81
+ const parsed = await callSubsystemJson('CaptureEditorScreenshot', {});
82
+ const artifact = normalizeVerificationArtifact(parsed);
83
+ const captureId = typeof artifact.captureId === 'string' ? artifact.captureId : '';
84
+ const resourceUri = buildCaptureResourceUri(captureId);
85
+ const normalizedArtifact = {
86
+ ...artifact,
87
+ resourceUri,
88
+ };
89
+ const extraContent = await buildCaptureExtraContent(normalizedArtifact, captureId ? `Editor ${captureId}` : 'Editor screenshot', 'Captured editor viewport screenshot.');
90
+ return jsonToolSuccess(normalizedArtifact, { extraContent });
91
+ }
92
+ catch (error) {
93
+ return jsonToolError(error);
94
+ }
95
+ });
96
+ server.registerTool('capture_runtime_screenshot', {
97
+ title: 'Capture Runtime Screenshot',
98
+ description: 'Run an automation scenario and return the first normalized runtime screenshot artifact.',
99
+ inputSchema: {
100
+ automation_filter: z.string().regex(/^[A-Za-z0-9_.+* -]+$/u, 'automation_filter must contain only alphanumeric, dots, underscores, plus, asterisk, hyphen, and spaces').describe('Automation test filter passed to run_automation_tests.'),
101
+ engine_root: z.string().optional().describe('Optional Unreal Engine root. Falls back to editor context or UE_ENGINE_ROOT.'),
102
+ project_path: z.string().optional().describe('Optional .uproject path. Falls back to editor context or UE_PROJECT_PATH.'),
103
+ target: z.string().optional().describe('Optional editor target name to keep in the run metadata.'),
104
+ report_output_dir: z.string().optional().describe('Optional host filesystem directory for this run.'),
105
+ timeout_seconds: z.number().int().positive().default(3600).describe('Maximum wall-clock time for the automation process before the host terminates it.'),
106
+ null_rhi: z.boolean().default(false).describe('Leave false for screenshot-backed runtime verification. Set true only when the automation path exports images without live rendering.'),
107
+ },
108
+ outputSchema: captureResultSchema,
109
+ annotations: {
110
+ title: 'Capture Runtime Screenshot',
111
+ readOnlyHint: true,
112
+ destructiveHint: false,
113
+ idempotentHint: false,
114
+ openWorldHint: false,
115
+ },
116
+ }, async ({ automation_filter, engine_root, project_path, target, report_output_dir, timeout_seconds, null_rhi }) => {
117
+ try {
118
+ const resolved = await resolveProjectInputs({ engine_root, project_path, target });
119
+ if (!resolved.engineRoot || !resolved.projectPath) {
120
+ throw explainProjectResolutionFailure('capture_runtime_screenshot requires engine_root and project_path', resolved);
121
+ }
122
+ const run = normalizeAutomationRunResult(await automationController.runAutomationTests({
123
+ automationFilter: automation_filter,
124
+ engineRoot: resolved.engineRoot,
125
+ projectPath: resolved.projectPath,
126
+ target: resolved.target,
127
+ reportOutputDir: report_output_dir,
128
+ timeoutMs: timeout_seconds * 1000,
129
+ nullRhi: null_rhi,
130
+ }));
131
+ const runtimeArtifact = (Array.isArray(run.verificationArtifacts) ? run.verificationArtifacts : [])
132
+ .map((artifact) => normalizeVerificationArtifactReference(artifact))
133
+ .find((artifact) => artifact.surface === 'pie_runtime');
134
+ if (!runtimeArtifact) {
135
+ return jsonToolError(new Error('capture_runtime_screenshot did not find a normalized pie_runtime artifact. Ensure the automation scenario exports a screenshot-backed artifact.'));
136
+ }
137
+ const extraContent = await buildCaptureExtraContent(runtimeArtifact, typeof runtimeArtifact.captureId === 'string' && runtimeArtifact.captureId.length > 0
138
+ ? `Runtime ${runtimeArtifact.captureId}`
139
+ : 'Runtime screenshot', 'Automation-exported runtime screenshot.');
140
+ return jsonToolSuccess({
141
+ ...runtimeArtifact,
142
+ operation: 'capture_runtime_screenshot',
143
+ automationRun: run,
144
+ inputResolution: {
145
+ engineRoot: resolved.sources.engineRoot,
146
+ projectPath: resolved.sources.projectPath,
147
+ target: resolved.sources.target,
148
+ contextError: resolved.contextError,
149
+ },
150
+ }, { extraContent });
151
+ }
152
+ catch (error) {
153
+ return jsonToolError(error);
154
+ }
155
+ });
50
156
  server.registerTool('capture_widget_motion_checkpoints', {
51
157
  title: 'Capture Widget Motion Checkpoints',
52
158
  description: 'Play a widget animation or automation scenario and capture named motion checkpoints.\n\n'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blueprint-extractor-mcp",
3
- "version": "6.0.3",
3
+ "version": "6.0.5",
4
4
  "description": "MCP server for the Unreal Engine BlueprintExtractor plugin over Remote Control",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",