blueprint-extractor-mcp 7.0.8 → 8.0.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/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 / OpenCode 106 tools BlueprintExtractor
26
+ Codex / OpenCode 109 tools BlueprintExtractor
27
27
  ... 38 resources plugin
28
28
  4 resource templates
29
29
  12 prompts
@@ -112,22 +112,23 @@ codex mcp add --env UE_REMOTE_CONTROL_PORT=30010 \
112
112
 
113
113
  ## Tool Surface
114
114
 
115
- Only the compact core surface is visible by default to keep the context window lean. Specialized families are loaded on demand via `activate_workflow_scope`.
115
+ Use `activate_tool_profile` to switch between the compact `default` surface and the full `expert` surface. The default profile keeps the context window lean and loads specialized families on demand via `activate_workflow_scope`.
116
116
 
117
117
  | Scope | What It Unlocks |
118
118
  |:------|:----------------|
119
- | **Core** *(always on)* | Search, extraction, list/save/help, editor-session binding, and project-control entry points such as `extract_asset`, `search_assets`, `save_assets`, `get_tool_help`, and `activate_workflow_scope` |
119
+ | **Core** *(always on in `default` profile)* | Search, extraction, `find_and_extract`, list/save/help, and the profile/scope switches such as `extract_asset`, `search_assets`, `save_assets`, `get_tool_help`, `activate_tool_profile`, and `activate_workflow_scope` |
120
120
  | `widget_authoring` | Parent scope that loads `widget_authoring_structure`, `widget_authoring_visual`, and `widget_verification` together |
121
- | `widget_authoring_structure` | Widget tree structure, hierarchy edits, wrapping, moving, replacement, and batch operations |
121
+ | `widget_authoring_structure` | Recipe-first widget authoring, tree replacement, unified-diff patching, and focused structure edits without the deprecated widget aliases |
122
122
  | `widget_authoring_visual` | Widget compile flows, CommonUI styles, widget animations, and widget preview capture |
123
123
  | `widget_verification` | Widget capture, checkpoint bundles, capture listing, cleanup, and reference comparison |
124
- | `material_authoring` | Material creation, `material_graph_operation`, compile, and material-instance edits |
125
- | `blueprint_authoring` | Blueprint creation, member edits, graph edits, and Live Coding trigger |
124
+ | `material_authoring` | `create_material_setup`, `modify_material`, `material_graph_operation`, compile, and material-instance edits |
125
+ | `blueprint_authoring` | `scaffold_blueprint`, graph/member edits, Blueprint creation, and Live Coding trigger |
126
126
  | `schema_ai_authoring` | Structs, enums, Blackboards, Behavior Trees, and State Trees |
127
127
  | `animation_authoring` | Anim sequences, montages, blend spaces, and widget motion authoring |
128
128
  | `data_tables` | Data assets, data tables, curves, Input Actions, and Input Mapping Contexts |
129
129
  | `import` | Async asset import and import-job polling |
130
- | `automation_testing` | Host-side automation runs, coarse project automation context, and PIE lifecycle control |
130
+ | `project_control` | Editor-session binding, launch/wait, project automation context, PIE lifecycle control, host build/restart/sync, and `apply_window_ui_changes` |
131
+ | `automation_testing` | Host-side automation runs and automation-run polling |
131
132
  | `analysis` | Deterministic Blueprint review and low-noise project asset audits |
132
133
  | `project_intelligence` | Bounded editor context, project indexing, freshness status, and snippet-first context search |
133
134
  | `verification` | Editor/runtime screenshots, capture comparison, motion verification, and artifact inspection |
@@ -254,7 +254,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
254
254
  ['extract_widget_blueprint', extractionHints('extract_widget_blueprint', {
255
255
  on_success: [
256
256
  'Use patch_widget or replace_widget_tree to modify the widget.',
257
- 'Use compile_widget_blueprint to check for errors.',
257
+ 'Use compile_widget to check for errors.',
258
258
  'Use capture_widget_preview for visual verification.',
259
259
  ],
260
260
  scope_suggestion: 'widget_authoring',
@@ -276,25 +276,17 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
276
276
  // =========================================================================
277
277
  // Widget structure tools
278
278
  // =========================================================================
279
- ['create_widget_blueprint', creationHints('extract_widget_blueprint', 'build_widget_tree', {
279
+ ['create_widget_blueprint', creationHints('extract_widget_blueprint', 'replace_widget_tree', {
280
280
  on_success: [
281
- 'Use build_widget_tree to populate the widget tree.',
281
+ 'Use replace_widget_tree to populate the widget tree.',
282
282
  'Use extract_widget_blueprint to verify.',
283
283
  'Use save_assets to persist.',
284
284
  ],
285
285
  scope_suggestion: 'widget_authoring',
286
286
  })],
287
- ['build_widget_tree', modificationHints('extract_widget_blueprint', {
288
- on_success: [
289
- 'Use compile_widget_blueprint to verify the tree compiles.',
290
- 'Use extract_widget_blueprint to inspect the result.',
291
- 'Use save_assets to persist.',
292
- ],
293
- scope_suggestion: 'widget_authoring',
294
- })],
295
287
  ['replace_widget_tree', modificationHints('extract_widget_blueprint', {
296
288
  on_success: [
297
- 'Use compile_widget_blueprint to verify the new tree.',
289
+ 'Use compile_widget to verify the new tree.',
298
290
  'Use extract_widget_blueprint to inspect the result.',
299
291
  'Use save_assets to persist.',
300
292
  ],
@@ -344,7 +336,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
344
336
  })],
345
337
  ['replace_widget_class', modificationHints('extract_widget_blueprint', {
346
338
  on_success: [
347
- 'Use compile_widget_blueprint to verify the class change.',
339
+ 'Use compile_widget to verify the class change.',
348
340
  'Use extract_widget_blueprint to inspect the result.',
349
341
  'Use save_assets to persist.',
350
342
  ],
@@ -352,7 +344,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
352
344
  })],
353
345
  ['batch_widget_operations', modificationHints('extract_widget_blueprint', {
354
346
  on_success: [
355
- 'Use compile_widget_blueprint to verify the batch result.',
347
+ 'Use compile_widget to verify the batch result.',
356
348
  'Use extract_widget_blueprint to inspect changes.',
357
349
  'Use save_assets to persist.',
358
350
  ],
@@ -370,36 +362,6 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
370
362
  ],
371
363
  scope_suggestion: 'widget_authoring',
372
364
  }],
373
- ['modify_widget_blueprint', modificationHints('extract_widget_blueprint', {
374
- on_success: [
375
- 'Use operation-specific tools (patch_widget, replace_widget_tree, etc.) instead.',
376
- 'Use save_assets to persist changes.',
377
- ],
378
- on_error: [
379
- 'Use extract_widget_blueprint to inspect current state.',
380
- 'Consider using operation-specific tools for clearer errors.',
381
- ],
382
- scope_suggestion: 'widget_authoring',
383
- })],
384
- ['modify_widget', modificationHints('extract_widget_blueprint', {
385
- on_success: [
386
- 'Use save_assets to persist changes.',
387
- 'Use extract_widget_blueprint to verify.',
388
- ],
389
- scope_suggestion: 'widget_authoring',
390
- })],
391
- ['compile_widget_blueprint', {
392
- on_success: [
393
- 'Inspect compile.messages and diagnostics for warnings.',
394
- 'Re-extract the widget blueprint before applying the next patch.',
395
- 'Check BindWidget names/types if there are abstract class references.',
396
- ],
397
- on_error: [
398
- 'Use extract_widget_blueprint to inspect the current state.',
399
- 'Fix reported issues and recompile.',
400
- ],
401
- scope_suggestion: 'widget_authoring',
402
- }],
403
365
  // =========================================================================
404
366
  // Widget verification tools
405
367
  // =========================================================================
@@ -464,7 +426,7 @@ export const NEXT_STEP_HINTS_REGISTRY = new Map([
464
426
  ['apply_commonui_button_style', {
465
427
  on_success: [
466
428
  'Use extract_widget_blueprint to verify the style was applied.',
467
- 'Use compile_widget_blueprint to verify compilation.',
429
+ 'Use compile_widget to verify compilation.',
468
430
  'Use save_assets to persist.',
469
431
  ],
470
432
  on_error: [
@@ -1,7 +1,7 @@
1
1
  import { access } from 'node:fs/promises';
2
2
  import { constants as fsConstants } from 'node:fs';
3
- import path, { resolve } from 'node:path';
4
- import { buildEngineAssociationCandidates, readProjectEngineAssociation } from './workspace-project.js';
3
+ import path, { posix as posixPath, win32 as win32Path } from 'node:path';
4
+ import { buildEngineAssociationCandidates, isWindowsStylePath, isWslMountedWindowsPath, readProjectEngineAssociation, toHostFilesystemPath, toWindowsStylePath, } from './workspace-project.js';
5
5
  export function rememberExternalBuild(result) {
6
6
  return {
7
7
  success: result.success === true,
@@ -32,6 +32,9 @@ export async function getProjectAutomationContext(deps) {
32
32
  return nextContext;
33
33
  }
34
34
  const cachedHeuristicEngineRoots = new Map();
35
+ function getPathModule(platform) {
36
+ return platform === 'win32' ? win32Path : posixPath;
37
+ }
35
38
  export function getHeuristicEngineCandidates(platform = process.platform) {
36
39
  if (platform === 'win32') {
37
40
  return [
@@ -74,10 +77,13 @@ function getEngineMarkers(platform) {
74
77
  'Engine/Build/BatchFiles/Build.sh',
75
78
  ];
76
79
  }
77
- async function accessFirstMatchingMarker(root, markers) {
80
+ async function accessFirstMatchingMarker(root, markers, targetPlatform, hostPlatform) {
81
+ const pathModule = getPathModule(targetPlatform);
82
+ const normalizedRoot = targetPlatform === 'win32' ? toWindowsStylePath(root) : root;
78
83
  for (const marker of markers) {
79
84
  try {
80
- await access(resolve(root, marker), fsConstants.F_OK);
85
+ const candidate = pathModule.resolve(normalizedRoot, marker);
86
+ await access(toHostFilesystemPath(candidate, targetPlatform, hostPlatform), fsConstants.F_OK);
81
87
  return true;
82
88
  }
83
89
  catch {
@@ -86,24 +92,24 @@ async function accessFirstMatchingMarker(root, markers) {
86
92
  }
87
93
  return false;
88
94
  }
89
- async function probeEngineRootHeuristic(platform) {
90
- if (cachedHeuristicEngineRoots.has(platform)) {
91
- return cachedHeuristicEngineRoots.get(platform) || undefined;
95
+ async function probeEngineRootHeuristic(targetPlatform, hostPlatform) {
96
+ const cacheKey = `${hostPlatform}:${targetPlatform}`;
97
+ if (cachedHeuristicEngineRoots.has(cacheKey)) {
98
+ return cachedHeuristicEngineRoots.get(cacheKey) || undefined;
92
99
  }
93
- for (const candidate of getHeuristicEngineCandidates(platform)) {
94
- if (await accessFirstMatchingMarker(candidate, getEngineMarkers(platform))) {
95
- cachedHeuristicEngineRoots.set(platform, candidate);
100
+ for (const candidate of getHeuristicEngineCandidates(targetPlatform)) {
101
+ if (await accessFirstMatchingMarker(candidate, getEngineMarkers(targetPlatform), targetPlatform, hostPlatform)) {
102
+ cachedHeuristicEngineRoots.set(cacheKey, candidate);
96
103
  return candidate;
97
104
  }
98
105
  }
99
- cachedHeuristicEngineRoots.set(platform, '');
106
+ cachedHeuristicEngineRoots.set(cacheKey, '');
100
107
  return undefined;
101
108
  }
102
- async function probePreferredEngineRoot(candidates, platform) {
103
- const markers = getEngineMarkers(platform);
109
+ async function probePreferredEngineRoot(candidates, hostPlatform) {
104
110
  for (const candidate of candidates) {
105
- if (await accessFirstMatchingMarker(candidate, markers)) {
106
- return candidate;
111
+ if (await accessFirstMatchingMarker(candidate.path, getEngineMarkers(candidate.platform), candidate.platform, hostPlatform)) {
112
+ return candidate.path;
107
113
  }
108
114
  }
109
115
  return undefined;
@@ -133,7 +139,16 @@ export async function resolveProjectInputs(request, deps) {
133
139
  const projectPath = firstDefinedString(request.project_path, projectPathFromContext, projectPathFromWorkspace, projectPathFromEnv);
134
140
  const target = firstDefinedString(request.target, targetFromContext, targetFromWorkspace, targetFromEnv);
135
141
  const engineAssociation = projectPath ? await readProjectEngineAssociation(projectPath) : undefined;
136
- const associationCandidates = buildEngineAssociationCandidates(engineAssociation, platform);
142
+ const windowsWorkspaceHint = [request.engine_root, request.project_path, projectPathFromWorkspace, projectPathFromEnv]
143
+ .filter((value) => typeof value === 'string' && value.length > 0)
144
+ .some((value) => isWindowsStylePath(value) || isWslMountedWindowsPath(value));
145
+ const heuristicPlatforms = windowsWorkspaceHint && platform !== 'win32'
146
+ ? ['win32', platform]
147
+ : [platform];
148
+ const associationCandidates = heuristicPlatforms.flatMap((candidatePlatform) => (buildEngineAssociationCandidates(engineAssociation, candidatePlatform).map((candidate) => ({
149
+ path: candidate,
150
+ platform: candidatePlatform,
151
+ }))));
137
152
  let engineRoot = firstDefinedString(request.engine_root, engineRootFromContext, engineRootFromEnv);
138
153
  let engineRootSource;
139
154
  if (request.engine_root) {
@@ -147,7 +162,15 @@ export async function resolveProjectInputs(request, deps) {
147
162
  }
148
163
  else {
149
164
  const preferredCandidate = await probePreferredEngineRoot(associationCandidates, platform);
150
- const heuristicRoot = preferredCandidate ?? await probeEngineRootHeuristic(platform);
165
+ let heuristicRoot = preferredCandidate;
166
+ if (!heuristicRoot) {
167
+ for (const candidatePlatform of heuristicPlatforms) {
168
+ heuristicRoot = await probeEngineRootHeuristic(candidatePlatform, platform);
169
+ if (heuristicRoot) {
170
+ break;
171
+ }
172
+ }
173
+ }
151
174
  if (heuristicRoot) {
152
175
  engineRoot = heuristicRoot;
153
176
  engineRootSource = 'filesystem_heuristic';
@@ -102,22 +102,6 @@ export function summarizeOutputSchema(schema) {
102
102
  }
103
103
  export function collectToolExampleFamilies(exampleCatalog, toolName) {
104
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
- }
121
105
  return Object.entries(exampleCatalog)
122
106
  .flatMap(([family, entry]) => {
123
107
  const exampleTitles = entry.examples
@@ -143,7 +127,6 @@ export function collectRelatedResources(toolName) {
143
127
  if (toolName.startsWith('create_')
144
128
  || toolName.startsWith('modify_')
145
129
  || toolName.startsWith('apply_')
146
- || toolName === 'build_widget_tree'
147
130
  || toolName === 'save_assets') {
148
131
  resources.add('blueprint://write-capabilities');
149
132
  resources.add('blueprint://authoring-conventions');
@@ -3,6 +3,10 @@ export type WorkspaceProjectResolution = {
3
3
  ambiguous: boolean;
4
4
  searchedFrom: string;
5
5
  };
6
+ export declare function isWindowsStylePath(input: string): boolean;
7
+ export declare function isWslMountedWindowsPath(input: string): boolean;
8
+ export declare function toWindowsStylePath(input: string): string;
9
+ export declare function toHostFilesystemPath(input: string, targetPlatform: NodeJS.Platform, hostPlatform?: NodeJS.Platform): string;
6
10
  export declare function normalizeFilesystemPath(input: string | undefined): string | undefined;
7
11
  export declare function filesystemPathsEqual(left: string | undefined, right: string | undefined): boolean;
8
12
  export declare function findNearestWorkspaceProject(startDir?: string): Promise<WorkspaceProjectResolution>;
@@ -2,9 +2,41 @@ import { readFile, readdir } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
4
4
  const WINDOWS_UNC_PATH = /^(?:\\\\|\/\/)[^\\/]+[\\/][^\\/]+/;
5
- function isWindowsStylePath(input) {
5
+ const WSL_MOUNT_PATH = /^\/mnt\/([a-zA-Z])(\/.*)?$/;
6
+ export function isWindowsStylePath(input) {
6
7
  return WINDOWS_DRIVE_PATH.test(input) || WINDOWS_UNC_PATH.test(input);
7
8
  }
9
+ export function isWslMountedWindowsPath(input) {
10
+ return WSL_MOUNT_PATH.test(input);
11
+ }
12
+ export function toWindowsStylePath(input) {
13
+ if (isWindowsStylePath(input)) {
14
+ return path.win32.normalize(input);
15
+ }
16
+ const match = input.replaceAll('\\', '/').match(WSL_MOUNT_PATH);
17
+ if (!match) {
18
+ return input;
19
+ }
20
+ const drive = match[1]?.toUpperCase();
21
+ const suffix = match[2] ?? '';
22
+ return path.win32.normalize(`${drive}:${suffix}`);
23
+ }
24
+ export function toHostFilesystemPath(input, targetPlatform, hostPlatform = process.platform) {
25
+ if (targetPlatform !== 'win32' || hostPlatform === 'win32') {
26
+ return input;
27
+ }
28
+ if (isWslMountedWindowsPath(input)) {
29
+ return path.posix.normalize(input.replaceAll('\\', '/'));
30
+ }
31
+ const normalized = input.replaceAll('\\', '/');
32
+ const match = normalized.match(/^([A-Za-z]):(\/.*)?$/);
33
+ if (!match) {
34
+ return input;
35
+ }
36
+ const drive = match[1]?.toLowerCase();
37
+ const suffix = match[2] ?? '';
38
+ return path.posix.normalize(`/mnt/${drive}${suffix}`);
39
+ }
8
40
  export function normalizeFilesystemPath(input) {
9
41
  if (!input) {
10
42
  return undefined;
@@ -89,6 +89,7 @@ export interface CommandRunner {
89
89
  cwd?: string;
90
90
  env?: NodeJS.ProcessEnv;
91
91
  timeoutMs: number;
92
+ platform?: NodeJS.Platform;
92
93
  }): Promise<{
93
94
  exitCode: number;
94
95
  stdout: string;
@@ -121,10 +122,11 @@ export interface ProjectControllerLike {
121
122
  waitForReconnect?: boolean;
122
123
  }): Promise<RestartReconnectResult>;
123
124
  }
125
+ export declare function inferExecutionPlatform(engineRoot: string | undefined, projectPath: string | undefined, hostPlatform?: NodeJS.Platform): NodeJS.Platform;
124
126
  export declare function resolveCommandInvocation(executable: string, args: string[], platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): CommandInvocation;
125
127
  export declare function getBuildScriptCandidates(engineRoot: string, platform: NodeJS.Platform): string[];
126
- export declare function resolveBuildScript(engineRoot: string, platform: NodeJS.Platform): Promise<string>;
127
- export declare function resolveEditorExecutable(engineRoot: string, platform: NodeJS.Platform, mode?: 'editor' | 'commandlet'): Promise<string>;
128
+ export declare function resolveBuildScript(engineRoot: string, platform: NodeJS.Platform, hostPlatform?: NodeJS.Platform): Promise<string>;
129
+ export declare function resolveEditorExecutable(engineRoot: string, platform: NodeJS.Platform, mode?: 'editor' | 'commandlet', hostPlatform?: NodeJS.Platform): Promise<string>;
128
130
  export declare function classifyChangedPaths(changedPaths: string[], forceRebuild?: boolean): SyncStrategyPlan;
129
131
  /** Classify UBT build output into an error category. */
130
132
  export declare function classifyBuildError(stdout: string, stderr: string, exitCode: number): {
@@ -1,7 +1,8 @@
1
1
  import { execSync, spawn } from 'node:child_process';
2
2
  import { access, readdir, unlink } from 'node:fs/promises';
3
3
  import { constants as fsConstants } from 'node:fs';
4
- import { dirname, resolve, win32 as win32Path } from 'node:path';
4
+ import { posix as posixPath, win32 as win32Path } from 'node:path';
5
+ import { isWindowsStylePath, isWslMountedWindowsPath, toHostFilesystemPath, toWindowsStylePath, } from './helpers/workspace-project.js';
5
6
  const DEFAULT_BUILD_TIMEOUT_MS = 30 * 60 * 1000;
6
7
  const DEFAULT_DISCONNECT_TIMEOUT_MS = 60 * 1000;
7
8
  const DEFAULT_RECONNECT_TIMEOUT_MS = 3 * 60 * 1000;
@@ -13,6 +14,27 @@ function defaultSleep(ms) {
13
14
  function isWindowsBatchScript(executable, platform) {
14
15
  return platform === 'win32' && /\.(bat|cmd)$/iu.test(executable);
15
16
  }
17
+ function getPlatformPath(platform) {
18
+ return platform === 'win32' ? win32Path : posixPath;
19
+ }
20
+ function resolvePathForPlatform(root, segments, platform) {
21
+ const normalizedRoot = platform === 'win32' ? toWindowsStylePath(root) : root;
22
+ return getPlatformPath(platform).resolve(normalizedRoot, ...segments);
23
+ }
24
+ function dirnameForPlatform(filePath, platform) {
25
+ const normalizedPath = platform === 'win32' ? toWindowsStylePath(filePath) : filePath;
26
+ return getPlatformPath(platform).dirname(normalizedPath);
27
+ }
28
+ export function inferExecutionPlatform(engineRoot, projectPath, hostPlatform = process.platform) {
29
+ if (hostPlatform !== 'linux') {
30
+ return hostPlatform;
31
+ }
32
+ const pathHints = [engineRoot, projectPath].filter((value) => typeof value === 'string' && value.length > 0);
33
+ if (pathHints.some((value) => isWindowsStylePath(value) || isWslMountedWindowsPath(value))) {
34
+ return 'win32';
35
+ }
36
+ return hostPlatform;
37
+ }
16
38
  function quoteWindowsCommandArg(value) {
17
39
  if (value.length === 0) {
18
40
  return '""';
@@ -45,24 +67,24 @@ export function resolveCommandInvocation(executable, args, platform, env = proce
45
67
  export function getBuildScriptCandidates(engineRoot, platform) {
46
68
  if (platform === 'win32') {
47
69
  return [
48
- resolve(engineRoot, 'Engine', 'Build', 'BatchFiles', 'Build.bat'),
70
+ resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.bat'], platform),
49
71
  ];
50
72
  }
51
73
  if (platform === 'darwin') {
52
74
  return [
53
- resolve(engineRoot, 'Engine', 'Build', 'BatchFiles', 'Mac', 'Build.sh'),
54
- resolve(engineRoot, 'Engine', 'Build', 'BatchFiles', 'Build.sh'),
75
+ resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Mac', 'Build.sh'], platform),
76
+ resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.sh'], platform),
55
77
  ];
56
78
  }
57
79
  return [
58
- resolve(engineRoot, 'Engine', 'Build', 'BatchFiles', 'Linux', 'Build.sh'),
59
- resolve(engineRoot, 'Engine', 'Build', 'BatchFiles', 'Build.sh'),
80
+ resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Linux', 'Build.sh'], platform),
81
+ resolvePathForPlatform(engineRoot, ['Engine', 'Build', 'BatchFiles', 'Build.sh'], platform),
60
82
  ];
61
83
  }
62
- export async function resolveBuildScript(engineRoot, platform) {
84
+ export async function resolveBuildScript(engineRoot, platform, hostPlatform = process.platform) {
63
85
  for (const candidate of getBuildScriptCandidates(engineRoot, platform)) {
64
86
  try {
65
- await access(candidate, fsConstants.F_OK);
87
+ await access(toHostFilesystemPath(candidate, platform, hostPlatform), fsConstants.F_OK);
66
88
  return candidate;
67
89
  }
68
90
  catch {
@@ -71,26 +93,27 @@ export async function resolveBuildScript(engineRoot, platform) {
71
93
  }
72
94
  throw new Error(`Unable to locate an Unreal build script under ${engineRoot} for platform ${platform}.`);
73
95
  }
74
- export async function resolveEditorExecutable(engineRoot, platform, mode = 'editor') {
96
+ export async function resolveEditorExecutable(engineRoot, platform, mode = 'editor', hostPlatform = process.platform) {
75
97
  const executableName = mode === 'commandlet'
76
98
  ? (platform === 'win32' ? 'UnrealEditor-Cmd.exe' : 'UnrealEditor-Cmd')
77
99
  : (platform === 'win32' ? 'UnrealEditor.exe' : 'UnrealEditor');
78
100
  const executable = platform === 'win32'
79
- ? resolve(engineRoot, 'Engine', 'Binaries', 'Win64', executableName)
101
+ ? resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Win64', executableName], platform)
80
102
  : platform === 'darwin'
81
- ? resolve(engineRoot, 'Engine', 'Binaries', 'Mac', executableName)
82
- : resolve(engineRoot, 'Engine', 'Binaries', 'Linux', executableName);
83
- await access(executable, fsConstants.F_OK);
103
+ ? resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Mac', executableName], platform)
104
+ : resolvePathForPlatform(engineRoot, ['Engine', 'Binaries', 'Linux', executableName], platform);
105
+ await access(toHostFilesystemPath(executable, platform, hostPlatform), fsConstants.F_OK);
84
106
  return executable;
85
107
  }
86
108
  async function defaultRunCommand(executable, args, options) {
87
109
  return await new Promise((resolveRun, rejectRun) => {
88
- const invocation = resolveCommandInvocation(executable, args, process.platform, options.env ?? process.env);
110
+ const commandPlatform = options.platform ?? process.platform;
111
+ const invocation = resolveCommandInvocation(executable, args, commandPlatform, options.env ?? process.env);
89
112
  const child = spawn(invocation.executable, invocation.args, {
90
113
  cwd: options.cwd,
91
114
  env: options.env,
92
115
  shell: false,
93
- windowsVerbatimArguments: isWindowsBatchScript(executable, process.platform),
116
+ windowsVerbatimArguments: isWindowsBatchScript(executable, commandPlatform),
94
117
  stdio: ['ignore', 'pipe', 'pipe'],
95
118
  });
96
119
  let stdout = '';
@@ -165,7 +188,7 @@ function fileName(path) {
165
188
  return lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized;
166
189
  }
167
190
  function normalizeFilesystemPathForCommand(path, platform) {
168
- return platform === 'win32' ? win32Path.normalize(path) : path;
191
+ return platform === 'win32' ? win32Path.normalize(toWindowsStylePath(path)) : path;
169
192
  }
170
193
  export function classifyChangedPaths(changedPaths, forceRebuild = false) {
171
194
  const reasons = new Set();
@@ -213,6 +236,7 @@ export function classifyChangedPaths(changedPaths, forceRebuild = false) {
213
236
  }
214
237
  const UHT_CACHE_PATTERN = /\.(uhtpath|uhtsettings)$/i;
215
238
  async function walkAndDeleteMatching(dir, pattern, deleted) {
239
+ const hostPath = getPlatformPath(process.platform);
216
240
  let entries;
217
241
  try {
218
242
  entries = await readdir(dir, { withFileTypes: true });
@@ -221,7 +245,7 @@ async function walkAndDeleteMatching(dir, pattern, deleted) {
221
245
  return;
222
246
  }
223
247
  for (const entry of entries) {
224
- const fullPath = resolve(dir, entry.name);
248
+ const fullPath = hostPath.resolve(dir, entry.name);
225
249
  if (entry.isDirectory()) {
226
250
  await walkAndDeleteMatching(fullPath, pattern, deleted);
227
251
  }
@@ -238,7 +262,7 @@ async function walkAndDeleteMatching(dir, pattern, deleted) {
238
262
  }
239
263
  async function clearUhtCacheFiles(projectDir) {
240
264
  const deleted = [];
241
- const intermediateDir = resolve(projectDir, 'Intermediate');
265
+ const intermediateDir = getPlatformPath(process.platform).resolve(projectDir, 'Intermediate');
242
266
  await walkAndDeleteMatching(intermediateDir, UHT_CACHE_PATTERN, deleted);
243
267
  return deleted;
244
268
  }
@@ -345,6 +369,7 @@ export class ProjectController {
345
369
  const projectPath = request.projectPath ?? this.env.UE_PROJECT_PATH;
346
370
  const target = request.target ?? this.env.UE_PROJECT_TARGET ?? this.env.UE_EDITOR_TARGET;
347
371
  const platform = parseBuildPlatform(request.platform ?? this.env.UE_BUILD_PLATFORM, this.platform);
372
+ const executionPlatform = inferExecutionPlatform(engineRoot, projectPath, this.platform);
348
373
  const configuration = parseConfiguration(request.configuration ?? this.env.UE_BUILD_CONFIGURATION);
349
374
  const includeOutput = request.includeOutput ?? false;
350
375
  const buildTimeoutMs = request.buildTimeoutMs ?? DEFAULT_BUILD_TIMEOUT_MS;
@@ -357,11 +382,13 @@ export class ProjectController {
357
382
  if (!target) {
358
383
  throw new Error('compile_project_code requires target or UE_PROJECT_TARGET/UE_EDITOR_TARGET');
359
384
  }
360
- const buildScript = await resolveBuildScript(engineRoot, this.platform);
361
- const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, this.platform);
385
+ const buildScript = await resolveBuildScript(engineRoot, executionPlatform, this.platform);
386
+ const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, executionPlatform);
387
+ const projectDir = dirnameForPlatform(projectPath, executionPlatform);
388
+ const hostProjectDir = toHostFilesystemPath(projectDir, executionPlatform, this.platform);
362
389
  let uhtCacheFilesDeleted;
363
390
  if (request.clearUhtCache) {
364
- uhtCacheFilesDeleted = await clearUhtCacheFiles(dirname(projectPath));
391
+ uhtCacheFilesDeleted = await clearUhtCacheFiles(hostProjectDir);
365
392
  }
366
393
  const args = [
367
394
  target,
@@ -373,9 +400,10 @@ export class ProjectController {
373
400
  ];
374
401
  const startedAt = Date.now();
375
402
  const completed = await this.runCommand(buildScript, args, {
376
- cwd: dirname(projectPath),
403
+ cwd: hostProjectDir,
377
404
  env: this.env,
378
405
  timeoutMs: buildTimeoutMs,
406
+ platform: executionPlatform,
379
407
  });
380
408
  const result = {
381
409
  success: completed.exitCode === 0,
@@ -383,7 +411,7 @@ export class ProjectController {
383
411
  strategy: 'external_build',
384
412
  engineRoot,
385
413
  projectPath,
386
- projectDir: dirname(projectPath),
414
+ projectDir,
387
415
  target,
388
416
  platform,
389
417
  configuration,
@@ -465,25 +493,30 @@ export class ProjectController {
465
493
  const engineRoot = request.engineRoot ?? this.env.UE_ENGINE_ROOT;
466
494
  const projectPath = request.projectPath ?? this.env.UE_PROJECT_PATH;
467
495
  const diagnostics = [];
496
+ const executionPlatform = inferExecutionPlatform(engineRoot, projectPath, this.platform);
468
497
  if (!engineRoot) {
469
498
  throw new Error('launch_editor requires engine_root or UE_ENGINE_ROOT');
470
499
  }
471
500
  if (!projectPath) {
472
501
  throw new Error('launch_editor requires project_path or UE_PROJECT_PATH');
473
502
  }
474
- const executable = await resolveEditorExecutable(engineRoot, this.platform, 'editor');
503
+ const executable = await resolveEditorExecutable(engineRoot, executionPlatform, 'editor', this.platform);
504
+ const hostExecutable = toHostFilesystemPath(executable, executionPlatform, this.platform);
475
505
  const requestedArgs = request.additionalArgs ?? [];
506
+ const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, executionPlatform);
476
507
  const args = [
477
- projectPath,
508
+ commandProjectPath,
478
509
  ...DEFAULT_EDITOR_LAUNCH_ARGS.filter((arg) => !requestedArgs.includes(arg)),
479
510
  ...requestedArgs,
480
511
  ];
481
- const invocation = resolveCommandInvocation(executable, args, this.platform, this.env);
512
+ const invocation = resolveCommandInvocation(hostExecutable, args, executionPlatform, this.env);
513
+ const projectDir = dirnameForPlatform(projectPath, executionPlatform);
514
+ const hostProjectDir = toHostFilesystemPath(projectDir, executionPlatform, this.platform);
482
515
  const child = this.spawnProcess(invocation.executable, invocation.args, {
483
- cwd: dirname(projectPath),
516
+ cwd: hostProjectDir,
484
517
  env: this.env,
485
518
  shell: false,
486
- windowsVerbatimArguments: isWindowsBatchScript(executable, this.platform),
519
+ windowsVerbatimArguments: isWindowsBatchScript(hostExecutable, executionPlatform),
487
520
  detached: true,
488
521
  stdio: 'ignore',
489
522
  });
@@ -494,7 +527,7 @@ export class ProjectController {
494
527
  operation: 'launch_editor',
495
528
  engineRoot,
496
529
  projectPath,
497
- projectDir: dirname(projectPath),
530
+ projectDir,
498
531
  processId: child.pid ?? undefined,
499
532
  command: {
500
533
  executable: invocation.executable,
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { exampleCatalog } from './catalogs/example-catalog.js';
3
3
  import { collectRelatedResources, collectToolExampleFamilies as collectToolExampleFamiliesFromCatalog, summarizeOutputSchema, summarizeSchemaFields, } from './helpers/tool-help.js';
4
- import { AnimMontageMutationOperationSchema, AnimationNotifySelectorSchema, AnimSequenceMutationOperationSchema, BehaviorTreeMutationOperationSchema, BehaviorTreeNodeSelectorSchema, BlackboardKeySchema, BlackboardMutationOperationSchema, BlendParameterSchema, BlendSpaceMutationOperationSchema, BlendSpaceSampleSchema, BlueprintGraphMutationOperationSchema, BlueprintMemberMutationOperationSchema, BuildConfigurationSchema, BuildPlatformSchema, CurveChannelSchema, CurveKeyDeleteSchema, CurveKeyUpsertSchema, CurveTableModeSchema, CurveTableRowSchema, CurveTypeSchema, DataTableRowSchema, ExtractAssetTypeSchema, EnhancedInputValueTypeSchema, FontImportItemSchema, ImportJobListSchema, ImportJobSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportOptionsSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, StateTreeBindingsObjectSchema, TextureImportOptionsSchema, UserDefinedEnumEntrySchema, UserDefinedEnumMutationOperationSchema, UserDefinedStructFieldSchema, UserDefinedStructMutationOperationSchema, WidgetBlueprintMutationOperationSchema, WidgetNodeSchema, WidgetSelectorFieldsSchema, WindowFontApplicationSchema, } from './schemas/tool-inputs.js';
4
+ import { AnimMontageMutationOperationSchema, AnimationNotifySelectorSchema, AnimSequenceMutationOperationSchema, BehaviorTreeMutationOperationSchema, BehaviorTreeNodeSelectorSchema, BlackboardKeySchema, BlackboardMutationOperationSchema, BlendParameterSchema, BlendSpaceMutationOperationSchema, BlendSpaceSampleSchema, BlueprintGraphMutationOperationSchema, BlueprintMemberMutationOperationSchema, BuildConfigurationSchema, BuildPlatformSchema, CurveChannelSchema, CurveKeyDeleteSchema, CurveKeyUpsertSchema, CurveTableModeSchema, CurveTableRowSchema, CurveTypeSchema, DataTableRowSchema, ExtractAssetTypeSchema, EnhancedInputValueTypeSchema, FontImportItemSchema, ImportJobListSchema, ImportJobSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportOptionsSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, StateTreeBindingsObjectSchema, TextureImportOptionsSchema, UserDefinedEnumEntrySchema, UserDefinedEnumMutationOperationSchema, UserDefinedStructFieldSchema, UserDefinedStructMutationOperationSchema, WidgetNodeSchema, WidgetSelectorFieldsSchema, WindowFontApplicationSchema, } from './schemas/tool-inputs.js';
5
5
  import { applyWindowUiChangesResultSchema, AutomationRunListSchema, automationRunSchema, CaptureResultSchema, CascadeResultSchema, CleanupCapturesResultSchema, CompareCaptureResultSchema, CompareMotionCaptureBundleResultSchema, CreateModifyWidgetAnimationResultSchema, ExtractWidgetAnimationResultSchema, ListCapturesResultSchema, motionCaptureModeSchema, MotionCaptureBundleResultSchema, widgetAnimationCheckpointSchema, } from './schemas/tool-results.js';
6
6
  import { registerAnimationAuthoringTools } from './tools/animation-authoring.js';
7
7
  import { registerAnalysisTools } from './tools/analysis-tools.js';
@@ -47,7 +47,6 @@ export function registerServerTools({ server, client, projectController, automat
47
47
  server,
48
48
  callSubsystemJson,
49
49
  widgetNodeSchema: WidgetNodeSchema,
50
- widgetBlueprintMutationOperationSchema: WidgetBlueprintMutationOperationSchema,
51
50
  });
52
51
  registerWidgetExtractionTools({
53
52
  server,