blueprint-extractor-mcp 2.2.0 → 2.3.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.
@@ -1,3 +1,4 @@
1
+ import { spawn } from 'node:child_process';
1
2
  export type BuildPlatform = 'Win64' | 'Mac' | 'Linux';
2
3
  export type BuildConfiguration = 'Debug' | 'DebugGame' | 'Development' | 'Shipping' | 'Test';
3
4
  export type SyncStrategy = 'live_coding' | 'build_and_restart';
@@ -11,6 +12,11 @@ export interface CompileProjectCodeRequest {
11
12
  includeOutput?: boolean;
12
13
  clearUhtCache?: boolean;
13
14
  }
15
+ export interface LaunchEditorRequest {
16
+ engineRoot?: string;
17
+ projectPath?: string;
18
+ additionalArgs?: string[];
19
+ }
14
20
  export interface SyncProjectCodeRequest extends CompileProjectCodeRequest {
15
21
  changedPaths: string[];
16
22
  forceRebuild?: boolean;
@@ -58,6 +64,19 @@ export interface RestartReconnectResult {
58
64
  reconnectTimeoutMs: number;
59
65
  diagnostics: string[];
60
66
  }
67
+ export interface LaunchEditorResult {
68
+ success: boolean;
69
+ operation: 'launch_editor';
70
+ engineRoot: string;
71
+ projectPath: string;
72
+ projectDir: string;
73
+ command: {
74
+ executable: string;
75
+ args: string[];
76
+ };
77
+ detached: boolean;
78
+ diagnostics: string[];
79
+ }
61
80
  export type ProbeConnection = (() => Promise<boolean>) | null;
62
81
  export interface CommandInvocation {
63
82
  executable: string;
@@ -78,18 +97,23 @@ export interface ProjectControllerOptions {
78
97
  env?: NodeJS.ProcessEnv;
79
98
  platform?: NodeJS.Platform;
80
99
  runCommand?: CommandRunner;
100
+ spawnProcess?: typeof spawn;
81
101
  sleep?: (ms: number) => Promise<void>;
82
102
  }
83
103
  export interface ProjectControllerLike {
84
104
  readonly liveCodingSupported: boolean;
85
105
  classifyChangedPaths(changedPaths: string[], forceRebuild?: boolean): SyncStrategyPlan;
86
106
  compileProjectCode(request: CompileProjectCodeRequest): Promise<CompileProjectCodeResult>;
107
+ launchEditor(request: LaunchEditorRequest): Promise<LaunchEditorResult>;
87
108
  waitForEditorRestart(probeConnection: ProbeConnection, options?: {
88
109
  disconnectTimeoutMs?: number;
89
110
  reconnectTimeoutMs?: number;
111
+ waitForDisconnect?: boolean;
112
+ waitForReconnect?: boolean;
90
113
  }): Promise<RestartReconnectResult>;
91
114
  }
92
115
  export declare function resolveCommandInvocation(executable: string, args: string[], platform: NodeJS.Platform, env?: NodeJS.ProcessEnv): CommandInvocation;
116
+ export declare function resolveEditorExecutable(engineRoot: string, platform: NodeJS.Platform, mode?: 'editor' | 'commandlet'): Promise<string>;
93
117
  export declare function classifyChangedPaths(changedPaths: string[], forceRebuild?: boolean): SyncStrategyPlan;
94
118
  /** Classify UBT build output into an error category. */
95
119
  export declare function classifyBuildError(stdout: string, stderr: string, exitCode: number): {
@@ -101,13 +125,17 @@ export declare class ProjectController implements ProjectControllerLike {
101
125
  private readonly env;
102
126
  private readonly platform;
103
127
  private readonly runCommand;
128
+ private readonly spawnProcess;
104
129
  private readonly sleep;
105
130
  constructor(options?: ProjectControllerOptions);
106
131
  get liveCodingSupported(): boolean;
107
132
  classifyChangedPaths(changedPaths: string[], forceRebuild?: boolean): SyncStrategyPlan;
108
133
  compileProjectCode(request: CompileProjectCodeRequest): Promise<CompileProjectCodeResult>;
134
+ launchEditor(request: LaunchEditorRequest): Promise<LaunchEditorResult>;
109
135
  waitForEditorRestart(probeConnection: ProbeConnection, options?: {
110
136
  disconnectTimeoutMs?: number;
111
137
  reconnectTimeoutMs?: number;
138
+ waitForDisconnect?: boolean;
139
+ waitForReconnect?: boolean;
112
140
  }): Promise<RestartReconnectResult>;
113
141
  }
@@ -38,6 +38,18 @@ export function resolveCommandInvocation(executable, args, platform, env = proce
38
38
  args: ['/d', '/s', '/c', commandLine],
39
39
  };
40
40
  }
41
+ export async function resolveEditorExecutable(engineRoot, platform, mode = 'editor') {
42
+ const executableName = mode === 'commandlet'
43
+ ? (platform === 'win32' ? 'UnrealEditor-Cmd.exe' : 'UnrealEditor-Cmd')
44
+ : (platform === 'win32' ? 'UnrealEditor.exe' : 'UnrealEditor');
45
+ const executable = platform === 'win32'
46
+ ? resolve(engineRoot, 'Engine', 'Binaries', 'Win64', executableName)
47
+ : platform === 'darwin'
48
+ ? resolve(engineRoot, 'Engine', 'Binaries', 'Mac', executableName)
49
+ : resolve(engineRoot, 'Engine', 'Binaries', 'Linux', executableName);
50
+ await access(executable, fsConstants.F_OK);
51
+ return executable;
52
+ }
41
53
  async function defaultRunCommand(executable, args, options) {
42
54
  return await new Promise((resolveRun, rejectRun) => {
43
55
  const invocation = resolveCommandInvocation(executable, args, process.platform, options.env ?? process.env);
@@ -276,11 +288,13 @@ export class ProjectController {
276
288
  env;
277
289
  platform;
278
290
  runCommand;
291
+ spawnProcess;
279
292
  sleep;
280
293
  constructor(options = {}) {
281
294
  this.env = options.env ?? process.env;
282
295
  this.platform = parsePlatform(options.platform, process.platform);
283
296
  this.runCommand = options.runCommand ?? defaultRunCommand;
297
+ this.spawnProcess = options.spawnProcess ?? spawn;
284
298
  this.sleep = options.sleep ?? defaultSleep;
285
299
  }
286
300
  get liveCodingSupported() {
@@ -369,12 +383,52 @@ export class ProjectController {
369
383
  }
370
384
  return result;
371
385
  }
386
+ async launchEditor(request) {
387
+ const engineRoot = request.engineRoot ?? this.env.UE_ENGINE_ROOT;
388
+ const projectPath = request.projectPath ?? this.env.UE_PROJECT_PATH;
389
+ const diagnostics = [];
390
+ if (!engineRoot) {
391
+ throw new Error('launch_editor requires engine_root or UE_ENGINE_ROOT');
392
+ }
393
+ if (!projectPath) {
394
+ throw new Error('launch_editor requires project_path or UE_PROJECT_PATH');
395
+ }
396
+ const executable = await resolveEditorExecutable(engineRoot, this.platform, 'editor');
397
+ const args = [projectPath, ...(request.additionalArgs ?? [])];
398
+ const invocation = resolveCommandInvocation(executable, args, this.platform, this.env);
399
+ const child = this.spawnProcess(invocation.executable, invocation.args, {
400
+ cwd: dirname(projectPath),
401
+ env: this.env,
402
+ shell: false,
403
+ windowsVerbatimArguments: isWindowsBatchScript(executable, this.platform),
404
+ detached: true,
405
+ stdio: 'ignore',
406
+ });
407
+ child.unref();
408
+ return {
409
+ success: true,
410
+ operation: 'launch_editor',
411
+ engineRoot,
412
+ projectPath,
413
+ projectDir: dirname(projectPath),
414
+ command: {
415
+ executable: invocation.executable,
416
+ args: invocation.args,
417
+ },
418
+ detached: true,
419
+ diagnostics,
420
+ };
421
+ }
372
422
  async waitForEditorRestart(probeConnection, options = {}) {
373
423
  const disconnectTimeoutMs = options.disconnectTimeoutMs ?? DEFAULT_DISCONNECT_TIMEOUT_MS;
374
424
  const reconnectTimeoutMs = options.reconnectTimeoutMs ?? DEFAULT_RECONNECT_TIMEOUT_MS;
425
+ const waitForDisconnect = options.waitForDisconnect ?? true;
426
+ const waitForReconnect = options.waitForReconnect ?? true;
375
427
  const diagnostics = [];
376
- const disconnected = await waitForState(probeConnection, false, disconnectTimeoutMs, this.sleep);
377
- if (!disconnected) {
428
+ const disconnected = waitForDisconnect
429
+ ? await waitForState(probeConnection, false, disconnectTimeoutMs, this.sleep)
430
+ : false;
431
+ if (waitForDisconnect && !disconnected) {
378
432
  diagnostics.push('Editor never disconnected after restart request');
379
433
  return {
380
434
  success: false,
@@ -386,14 +440,16 @@ export class ProjectController {
386
440
  diagnostics,
387
441
  };
388
442
  }
389
- const reconnected = await waitForState(probeConnection, true, reconnectTimeoutMs, this.sleep);
390
- if (!reconnected) {
443
+ const reconnected = waitForReconnect
444
+ ? await waitForState(probeConnection, true, reconnectTimeoutMs, this.sleep)
445
+ : false;
446
+ if (waitForReconnect && !reconnected) {
391
447
  diagnostics.push('Editor did not reconnect before the timeout elapsed');
392
448
  }
393
449
  return {
394
- success: reconnected,
450
+ success: (waitForDisconnect ? disconnected : true) && (waitForReconnect ? reconnected : true),
395
451
  operation: 'restart_editor',
396
- disconnected: true,
452
+ disconnected,
397
453
  reconnected,
398
454
  disconnectTimeoutMs,
399
455
  reconnectTimeoutMs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blueprint-extractor-mcp",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "MCP server for the Unreal Engine BlueprintExtractor plugin over Remote Control",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",