blueprint-extractor-mcp 8.1.0 → 8.2.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.
Files changed (172) hide show
  1. package/README.md +0 -0
  2. package/dist/active-editor-session.d.ts +0 -0
  3. package/dist/active-editor-session.js +0 -0
  4. package/dist/automation-controller.d.ts +0 -0
  5. package/dist/automation-controller.js +0 -0
  6. package/dist/catalogs/example-catalog.d.ts +0 -0
  7. package/dist/catalogs/example-catalog.js +0 -0
  8. package/dist/compactor.d.ts +0 -0
  9. package/dist/compactor.js +0 -0
  10. package/dist/editor-instance-registry.d.ts +0 -0
  11. package/dist/editor-instance-registry.js +67 -28
  12. package/dist/editor-instance-types.d.ts +0 -0
  13. package/dist/editor-instance-types.js +0 -0
  14. package/dist/execution/adapters/commandlet-adapter.d.ts +0 -0
  15. package/dist/execution/adapters/commandlet-adapter.js +0 -0
  16. package/dist/execution/adapters/editor-adapter.d.ts +0 -0
  17. package/dist/execution/adapters/editor-adapter.js +0 -0
  18. package/dist/execution/adapters/lazy-commandlet-adapter.d.ts +0 -0
  19. package/dist/execution/adapters/lazy-commandlet-adapter.js +0 -0
  20. package/dist/execution/adaptive-executor.d.ts +0 -0
  21. package/dist/execution/adaptive-executor.js +0 -0
  22. package/dist/execution/execution-adapter.d.ts +0 -0
  23. package/dist/execution/execution-adapter.js +0 -0
  24. package/dist/execution/execution-mode-detector.d.ts +0 -0
  25. package/dist/execution/execution-mode-detector.js +0 -0
  26. package/dist/execution/index.d.ts +0 -0
  27. package/dist/execution/index.js +0 -0
  28. package/dist/helpers/active-editor-utils.d.ts +0 -0
  29. package/dist/helpers/active-editor-utils.js +0 -0
  30. package/dist/helpers/alias-registration.d.ts +0 -0
  31. package/dist/helpers/alias-registration.js +0 -0
  32. package/dist/helpers/blueprint-dsl-parser.d.ts +0 -0
  33. package/dist/helpers/blueprint-dsl-parser.js +0 -0
  34. package/dist/helpers/blueprint-validation.d.ts +0 -0
  35. package/dist/helpers/blueprint-validation.js +0 -0
  36. package/dist/helpers/capture.d.ts +0 -0
  37. package/dist/helpers/capture.js +0 -0
  38. package/dist/helpers/commonui-button-style.d.ts +0 -0
  39. package/dist/helpers/commonui-button-style.js +0 -0
  40. package/dist/helpers/composite-patterns.d.ts +0 -0
  41. package/dist/helpers/composite-patterns.js +0 -0
  42. package/dist/helpers/formatting.d.ts +0 -0
  43. package/dist/helpers/formatting.js +1 -1
  44. package/dist/helpers/live-coding.d.ts +0 -0
  45. package/dist/helpers/live-coding.js +0 -0
  46. package/dist/helpers/material-dsl-parser.d.ts +0 -0
  47. package/dist/helpers/material-dsl-parser.js +0 -0
  48. package/dist/helpers/mutation-filter.d.ts +0 -0
  49. package/dist/helpers/mutation-filter.js +0 -0
  50. package/dist/helpers/next-step-hints.d.ts +0 -0
  51. package/dist/helpers/next-step-hints.js +0 -0
  52. package/dist/helpers/operation-deny-list.d.ts +0 -0
  53. package/dist/helpers/operation-deny-list.js +0 -0
  54. package/dist/helpers/package-metadata.d.ts +0 -0
  55. package/dist/helpers/package-metadata.js +0 -0
  56. package/dist/helpers/phantom-filter.d.ts +0 -0
  57. package/dist/helpers/phantom-filter.js +0 -0
  58. package/dist/helpers/project-resolution.d.ts +0 -0
  59. package/dist/helpers/project-resolution.js +0 -0
  60. package/dist/helpers/project-utils.d.ts +0 -0
  61. package/dist/helpers/project-utils.js +0 -0
  62. package/dist/helpers/property-path-parser.d.ts +0 -0
  63. package/dist/helpers/property-path-parser.js +0 -0
  64. package/dist/helpers/property-shorthand.d.ts +0 -0
  65. package/dist/helpers/property-shorthand.js +0 -0
  66. package/dist/helpers/response-summarizer.d.ts +0 -0
  67. package/dist/helpers/response-summarizer.js +0 -0
  68. package/dist/helpers/slot-presets.d.ts +0 -0
  69. package/dist/helpers/slot-presets.js +0 -0
  70. package/dist/helpers/subsystem.d.ts +0 -0
  71. package/dist/helpers/subsystem.js +0 -0
  72. package/dist/helpers/token-budget.d.ts +0 -0
  73. package/dist/helpers/token-budget.js +0 -0
  74. package/dist/helpers/tool-help.d.ts +0 -0
  75. package/dist/helpers/tool-help.js +0 -0
  76. package/dist/helpers/tool-registration.d.ts +0 -0
  77. package/dist/helpers/tool-registration.js +0 -0
  78. package/dist/helpers/tool-results.d.ts +0 -0
  79. package/dist/helpers/tool-results.js +16 -1
  80. package/dist/helpers/verification.d.ts +0 -0
  81. package/dist/helpers/verification.js +0 -0
  82. package/dist/helpers/widget-class-aliases.d.ts +0 -0
  83. package/dist/helpers/widget-class-aliases.js +0 -0
  84. package/dist/helpers/widget-diff-parser.d.ts +0 -0
  85. package/dist/helpers/widget-diff-parser.js +0 -0
  86. package/dist/helpers/widget-dsl-parser.d.ts +0 -0
  87. package/dist/helpers/widget-dsl-parser.js +0 -0
  88. package/dist/helpers/widget-recipe-formatter.d.ts +0 -0
  89. package/dist/helpers/widget-recipe-formatter.js +0 -0
  90. package/dist/helpers/widget-recipe-parser.d.ts +0 -0
  91. package/dist/helpers/widget-recipe-parser.js +0 -0
  92. package/dist/helpers/widget-utils.d.ts +0 -0
  93. package/dist/helpers/widget-utils.js +0 -0
  94. package/dist/helpers/workspace-project.d.ts +0 -0
  95. package/dist/helpers/workspace-project.js +5 -0
  96. package/dist/index.d.ts +0 -0
  97. package/dist/index.js +0 -0
  98. package/dist/project-controller.d.ts +0 -0
  99. package/dist/project-controller.js +13 -2
  100. package/dist/prompts/prompt-catalog.d.ts +0 -0
  101. package/dist/prompts/prompt-catalog.js +0 -0
  102. package/dist/register-server-resources.d.ts +0 -0
  103. package/dist/register-server-resources.js +0 -0
  104. package/dist/register-server-tools.d.ts +0 -0
  105. package/dist/register-server-tools.js +0 -0
  106. package/dist/resources/example-and-capture-resources.d.ts +0 -0
  107. package/dist/resources/example-and-capture-resources.js +0 -0
  108. package/dist/resources/static-doc-resources.d.ts +0 -0
  109. package/dist/resources/static-doc-resources.js +0 -0
  110. package/dist/schemas/tool-inputs.d.ts +0 -0
  111. package/dist/schemas/tool-inputs.js +0 -0
  112. package/dist/schemas/tool-results.d.ts +0 -0
  113. package/dist/schemas/tool-results.js +0 -0
  114. package/dist/server-config.d.ts +0 -0
  115. package/dist/server-config.js +0 -0
  116. package/dist/server-factory.d.ts +0 -0
  117. package/dist/server-factory.js +0 -0
  118. package/dist/tool-context.d.ts +0 -0
  119. package/dist/tool-context.js +0 -0
  120. package/dist/tool-surface-manager.d.ts +0 -0
  121. package/dist/tool-surface-manager.js +0 -0
  122. package/dist/tools/analysis-tools.d.ts +0 -0
  123. package/dist/tools/analysis-tools.js +0 -0
  124. package/dist/tools/animation-authoring.d.ts +0 -0
  125. package/dist/tools/animation-authoring.js +0 -0
  126. package/dist/tools/automation-runs.d.ts +0 -0
  127. package/dist/tools/automation-runs.js +0 -0
  128. package/dist/tools/blueprint-authoring.d.ts +0 -0
  129. package/dist/tools/blueprint-authoring.js +0 -0
  130. package/dist/tools/commonui-button-style.d.ts +0 -0
  131. package/dist/tools/commonui-button-style.js +0 -0
  132. package/dist/tools/composite-tools.d.ts +0 -0
  133. package/dist/tools/composite-tools.js +0 -0
  134. package/dist/tools/composite-workflows.d.ts +0 -0
  135. package/dist/tools/composite-workflows.js +0 -0
  136. package/dist/tools/data-and-input.d.ts +0 -0
  137. package/dist/tools/data-and-input.js +0 -0
  138. package/dist/tools/extraction.d.ts +0 -0
  139. package/dist/tools/extraction.js +0 -0
  140. package/dist/tools/import-jobs.d.ts +0 -0
  141. package/dist/tools/import-jobs.js +0 -0
  142. package/dist/tools/material-authoring.d.ts +0 -0
  143. package/dist/tools/material-authoring.js +0 -0
  144. package/dist/tools/material-instance.d.ts +0 -0
  145. package/dist/tools/material-instance.js +0 -0
  146. package/dist/tools/project-control.d.ts +0 -0
  147. package/dist/tools/project-control.js +124 -5
  148. package/dist/tools/project-intelligence.d.ts +0 -0
  149. package/dist/tools/project-intelligence.js +0 -0
  150. package/dist/tools/recipe-tools.d.ts +0 -0
  151. package/dist/tools/recipe-tools.js +0 -0
  152. package/dist/tools/schema-and-ai-authoring.d.ts +0 -0
  153. package/dist/tools/schema-and-ai-authoring.js +0 -0
  154. package/dist/tools/tables-and-curves.d.ts +0 -0
  155. package/dist/tools/tables-and-curves.js +0 -0
  156. package/dist/tools/utility-tools.d.ts +0 -0
  157. package/dist/tools/utility-tools.js +0 -0
  158. package/dist/tools/widget-animation-authoring.d.ts +0 -0
  159. package/dist/tools/widget-animation-authoring.js +0 -0
  160. package/dist/tools/widget-extraction.d.ts +0 -0
  161. package/dist/tools/widget-extraction.js +0 -0
  162. package/dist/tools/widget-structure.d.ts +0 -0
  163. package/dist/tools/widget-structure.js +0 -0
  164. package/dist/tools/widget-verification.d.ts +0 -0
  165. package/dist/tools/widget-verification.js +0 -0
  166. package/dist/tools/window-ui.d.ts +0 -0
  167. package/dist/tools/window-ui.js +0 -0
  168. package/dist/types.d.ts +0 -0
  169. package/dist/types.js +0 -0
  170. package/dist/ue-client.d.ts +0 -0
  171. package/dist/ue-client.js +0 -0
  172. package/package.json +1 -1
package/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/compactor.js CHANGED
File without changes
File without changes
@@ -4,6 +4,8 @@ import path from 'node:path';
4
4
  import { z } from 'zod';
5
5
  const REGISTRY_ENV = 'BLUEPRINT_EXTRACTOR_EDITOR_REGISTRY_DIR';
6
6
  const DEFAULT_STALE_TTL_MS = 15_000;
7
+ const WSL_WINDOWS_USERS_ROOT = '/mnt/c/Users';
8
+ const WSL_WINDOWS_REGISTRY_SUFFIX = ['AppData', 'Local', 'Temp', 'BlueprintExtractor', 'EditorRegistry'];
7
9
  const editorInstanceSchema = z.object({
8
10
  instanceId: z.string(),
9
11
  projectName: z.string().optional(),
@@ -29,45 +31,82 @@ export function getEditorRegistryDir() {
29
31
  ? path.resolve(process.env[REGISTRY_ENV])
30
32
  : path.join(tmpdir(), 'BlueprintExtractor', 'EditorRegistry');
31
33
  }
32
- export async function listRegisteredEditors(staleTtlMs = DEFAULT_STALE_TTL_MS) {
33
- const registryDir = getEditorRegistryDir();
34
- let fileNames = [];
34
+ async function getEditorRegistryDirs() {
35
+ const overrideDir = process.env[REGISTRY_ENV];
36
+ if (overrideDir) {
37
+ return [path.resolve(overrideDir)];
38
+ }
39
+ const dirs = new Set([getEditorRegistryDir()]);
40
+ if (process.platform !== 'linux') {
41
+ return Array.from(dirs);
42
+ }
35
43
  try {
36
- fileNames = await readdir(registryDir);
44
+ const userEntries = await readdir(WSL_WINDOWS_USERS_ROOT, { withFileTypes: true });
45
+ for (const entry of userEntries) {
46
+ if (!entry.isDirectory()) {
47
+ continue;
48
+ }
49
+ dirs.add(path.join(WSL_WINDOWS_USERS_ROOT, entry.name, ...WSL_WINDOWS_REGISTRY_SUFFIX));
50
+ }
37
51
  }
38
52
  catch {
39
- return {
40
- editors: [],
41
- registryDir,
42
- staleEntryCount: 0,
43
- };
53
+ // Ignore missing /mnt/c/Users on non-WSL or restricted hosts.
44
54
  }
45
- const editors = [];
55
+ return Array.from(dirs);
56
+ }
57
+ export async function listRegisteredEditors(staleTtlMs = DEFAULT_STALE_TTL_MS) {
58
+ const registryDirs = await getEditorRegistryDirs();
59
+ const editorsByInstanceId = new Map();
46
60
  let staleEntryCount = 0;
47
- for (const fileName of fileNames) {
48
- if (!fileName.toLowerCase().endsWith('.json')) {
61
+ for (const registryDir of registryDirs) {
62
+ let fileNames = [];
63
+ try {
64
+ fileNames = await readdir(registryDir);
65
+ }
66
+ catch {
49
67
  continue;
50
68
  }
51
- const fullPath = path.join(registryDir, fileName);
52
- try {
53
- const [raw, fileStat] = await Promise.all([
54
- readFile(fullPath, 'utf8'),
55
- stat(fullPath),
56
- ]);
57
- const parsed = editorInstanceSchema.parse(JSON.parse(raw));
58
- const lastSeenAt = parseLastSeenAt(parsed.lastSeenAt) ?? fileStat.mtimeMs;
59
- if ((Date.now() - lastSeenAt) > staleTtlMs) {
69
+ for (const fileName of fileNames) {
70
+ if (!fileName.toLowerCase().endsWith('.json')) {
71
+ continue;
72
+ }
73
+ const fullPath = path.join(registryDir, fileName);
74
+ try {
75
+ const [raw, fileStat] = await Promise.all([
76
+ readFile(fullPath, 'utf8'),
77
+ stat(fullPath),
78
+ ]);
79
+ const parsed = editorInstanceSchema.parse(JSON.parse(raw));
80
+ const lastSeenAt = parseLastSeenAt(parsed.lastSeenAt) ?? fileStat.mtimeMs;
81
+ if ((Date.now() - lastSeenAt) > staleTtlMs) {
82
+ staleEntryCount += 1;
83
+ await rm(fullPath, { force: true }).catch(() => undefined);
84
+ continue;
85
+ }
86
+ // Verify the process is actually alive — registry files can outlive the editor
87
+ // process (e.g. after a crash, force-kill, or external build restart).
88
+ if (typeof parsed.processId === 'number') {
89
+ try {
90
+ process.kill(parsed.processId, 0);
91
+ }
92
+ catch {
93
+ staleEntryCount += 1;
94
+ await rm(fullPath, { force: true }).catch(() => undefined);
95
+ continue;
96
+ }
97
+ }
98
+ const existing = editorsByInstanceId.get(parsed.instanceId);
99
+ if (!existing || lastSeenAt >= existing.lastSeenAt) {
100
+ editorsByInstanceId.set(parsed.instanceId, { snapshot: parsed, lastSeenAt });
101
+ }
102
+ }
103
+ catch {
60
104
  staleEntryCount += 1;
61
105
  await rm(fullPath, { force: true }).catch(() => undefined);
62
- continue;
63
106
  }
64
- editors.push(parsed);
65
- }
66
- catch {
67
- staleEntryCount += 1;
68
- await rm(fullPath, { force: true }).catch(() => undefined);
69
107
  }
70
108
  }
109
+ const editors = Array.from(editorsByInstanceId.values(), (entry) => entry.snapshot);
71
110
  editors.sort((left, right) => {
72
111
  const projectCompare = String(left.projectName ?? left.projectFilePath)
73
112
  .localeCompare(String(right.projectName ?? right.projectFilePath));
@@ -78,7 +117,7 @@ export async function listRegisteredEditors(staleTtlMs = DEFAULT_STALE_TTL_MS) {
78
117
  });
79
118
  return {
80
119
  editors,
81
- registryDir,
120
+ registryDir: registryDirs[0] ?? getEditorRegistryDir(),
82
121
  staleEntryCount,
83
122
  };
84
123
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -104,7 +104,7 @@ export function extractExtraContent(result) {
104
104
  if (!isPlainObject(result) || !Array.isArray(result.content)) {
105
105
  return [];
106
106
  }
107
- return result.content.filter((candidate) => (isContentBlock(candidate) && candidate.type !== 'text'));
107
+ return result.content.filter((candidate) => (isContentBlock(candidate)));
108
108
  }
109
109
  export function maybeBoolean(...values) {
110
110
  for (const value of values) {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,5 +1,17 @@
1
1
  import { isPlainObject } from './formatting.js';
2
2
  export function createToolResultNormalizers({ taskAwareTools, classifyRecoverableToolFailure, }) {
3
+ function serializeSuccessEnvelope(envelope) {
4
+ try {
5
+ return JSON.stringify(envelope);
6
+ }
7
+ catch {
8
+ return JSON.stringify({
9
+ success: true,
10
+ operation: typeof envelope.operation === 'string' ? envelope.operation : 'unknown_operation',
11
+ message: 'Unable to serialize tool result payload.',
12
+ });
13
+ }
14
+ }
3
15
  function extractNonTextContent(existingResult) {
4
16
  if (!existingResult || !Array.isArray(existingResult.content)) {
5
17
  return [];
@@ -132,7 +144,10 @@ export function createToolResultNormalizers({ taskAwareTools, classifyRecoverabl
132
144
  ...(taskAwareTools.has(toolName) ? { execution: inferExecutionMetadata(toolName, basePayload) } : {}),
133
145
  };
134
146
  return {
135
- content: extraContent,
147
+ content: [
148
+ { type: 'text', text: serializeSuccessEnvelope(envelope) },
149
+ ...extraContent,
150
+ ],
136
151
  structuredContent: envelope,
137
152
  };
138
153
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -48,6 +48,11 @@ export function normalizeFilesystemPath(input) {
48
48
  if (isWindowsStylePath(trimmed)) {
49
49
  return path.win32.normalize(trimmed).replaceAll('\\', '/').toLowerCase();
50
50
  }
51
+ // Canonicalize WSL-mounted Windows paths to the same normalized form as native Windows paths
52
+ // so registry entries like D:/Project/MyGame.uproject match /mnt/d/Project/MyGame.uproject.
53
+ if (isWslMountedWindowsPath(trimmed)) {
54
+ return toWindowsStylePath(trimmed).replaceAll('\\', '/').toLowerCase();
55
+ }
51
56
  return path.posix.normalize(trimmed);
52
57
  }
53
58
  export function filesystemPathsEqual(left, right) {
package/dist/index.d.ts CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
File without changes
@@ -7,7 +7,14 @@ const DEFAULT_BUILD_TIMEOUT_MS = 30 * 60 * 1000;
7
7
  const DEFAULT_DISCONNECT_TIMEOUT_MS = 60 * 1000;
8
8
  const DEFAULT_RECONNECT_TIMEOUT_MS = 3 * 60 * 1000;
9
9
  const DEFAULT_POLL_INTERVAL_MS = 1_000;
10
- const DEFAULT_EDITOR_LAUNCH_ARGS = ['-RCWebControlEnable', '-RCWebInterfaceEnable'];
10
+ const DEFAULT_EDITOR_LAUNCH_ARGS = ['-RCWebControlEnable', '-RCWebInterfaceEnable', '-WebControl.EnableServerOnStartup=1'];
11
+ function hasCommandLineArg(args, arg) {
12
+ const normalizedTarget = arg.toLowerCase();
13
+ return args.some((candidate) => {
14
+ const normalizedCandidate = candidate.toLowerCase();
15
+ return normalizedCandidate === normalizedTarget || normalizedCandidate.startsWith(`${normalizedTarget}=`);
16
+ });
17
+ }
11
18
  function defaultSleep(ms) {
12
19
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
13
20
  }
@@ -504,9 +511,13 @@ export class ProjectController {
504
511
  const hostExecutable = toHostFilesystemPath(executable, executionPlatform, this.platform);
505
512
  const requestedArgs = request.additionalArgs ?? [];
506
513
  const commandProjectPath = normalizeFilesystemPathForCommand(projectPath, executionPlatform);
514
+ const defaultArgs = DEFAULT_EDITOR_LAUNCH_ARGS.filter((arg) => {
515
+ const argName = arg.includes('=') ? arg.slice(0, arg.indexOf('=')) : arg;
516
+ return !hasCommandLineArg(requestedArgs, argName);
517
+ });
507
518
  const args = [
508
519
  commandProjectPath,
509
- ...DEFAULT_EDITOR_LAUNCH_ARGS.filter((arg) => !requestedArgs.includes(arg)),
520
+ ...defaultArgs,
510
521
  ...requestedArgs,
511
522
  ];
512
523
  const invocation = resolveCommandInvocation(hostExecutable, args, executionPlatform, this.env);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -47,6 +47,9 @@ export function registerProjectControlTools({ server, client, projectController,
47
47
  engineRoot: previousEditor?.engineRoot ?? fallback.engineRoot,
48
48
  target: previousEditor?.editorTarget ?? fallback.target,
49
49
  });
50
+ const matchesResolvedEditor = (entry, resolved) => (filesystemPathsEqual(entry.projectFilePath, resolved.projectPath)
51
+ && (!resolved.engineRoot || !entry.engineRoot || filesystemPathsEqual(entry.engineRoot, resolved.engineRoot))
52
+ && (!resolved.target || !entry.editorTarget || entry.editorTarget === resolved.target));
50
53
  const recoverEditorViaHostRelaunch = async (request) => {
51
54
  const recovery = {
52
55
  strategy: 'host_relaunch_after_failed_graceful_restart',
@@ -452,18 +455,51 @@ export function registerProjectControlTools({ server, client, projectController,
452
455
  if (!resolved.engineRoot || !resolved.projectPath) {
453
456
  throw explainProjectResolutionFailure('launch_editor requires engine_root and project_path', resolved);
454
457
  }
458
+ const resolvedProjectPath = resolved.projectPath;
459
+ const resolvedEngineRoot = resolved.engineRoot;
460
+ const resolvedTarget = resolved.target;
461
+ if (activeEditorSession) {
462
+ const matchingEditors = (await activeEditorSession.listRunningEditors()).filter((entry) => matchesResolvedEditor(entry, {
463
+ projectPath: resolvedProjectPath,
464
+ engineRoot: resolvedEngineRoot,
465
+ target: resolvedTarget,
466
+ }));
467
+ if (matchingEditors.length > 1) {
468
+ throw new Error(`Multiple running editors already match "${resolvedProjectPath}". `
469
+ + 'Call list_running_editors and select_editor instead of launch_editor.');
470
+ }
471
+ if (matchingEditors.length === 1) {
472
+ try {
473
+ const bound = await activeEditorSession.selectEditor({ instanceId: matchingEditors[0].instanceId });
474
+ clearProjectAutomationContext();
475
+ const activeEditor = toLabeledActiveEditor(bound);
476
+ return jsonToolSuccess({
477
+ success: true,
478
+ operation: 'launch_editor',
479
+ launched: false,
480
+ reusedExistingEditor: true,
481
+ message: 'Bound the existing matching editor instead of launching a new process.',
482
+ inputResolution: buildInputResolution(resolved),
483
+ activeEditor,
484
+ });
485
+ }
486
+ catch {
487
+ // Fall through to a real launch when the registry entry is stale or not yet responsive.
488
+ }
489
+ }
490
+ }
455
491
  const launched = await projectController.launchEditor({
456
- engineRoot: resolved.engineRoot,
457
- projectPath: resolved.projectPath,
492
+ engineRoot: resolvedEngineRoot,
493
+ projectPath: resolvedProjectPath,
458
494
  });
459
495
  clearProjectAutomationContext();
460
496
  let activeEditor;
461
497
  if (activeEditorSession) {
462
498
  const bound = await activeEditorSession.bindLaunchedEditor({
463
499
  processId: launched.processId,
464
- projectPath: resolved.projectPath,
465
- engineRoot: resolved.engineRoot,
466
- target: resolved.target,
500
+ projectPath: resolvedProjectPath,
501
+ engineRoot: resolvedEngineRoot,
502
+ target: resolvedTarget,
467
503
  timeoutMs: reconnect_timeout_seconds * 1000,
468
504
  });
469
505
  activeEditor = toLabeledActiveEditor(bound);
@@ -1288,4 +1324,87 @@ export function registerProjectControlTools({ server, client, projectController,
1288
1324
  return jsonToolSuccess(failurePayload);
1289
1325
  }
1290
1326
  });
1327
+ // ============================================================
1328
+ // StateTree Debugger
1329
+ // ============================================================
1330
+ server.registerTool('start_statetree_debugger', {
1331
+ title: 'Start StateTree Debugger',
1332
+ description: 'Start recording StateTree debug traces. Requires PIE to be running. Optionally filter to a specific StateTree asset.',
1333
+ inputSchema: {
1334
+ asset_path: z.string().default('').describe('UE content path of the StateTree asset to filter on (e.g. /Game/AI/ST_Character). Empty = record all.'),
1335
+ },
1336
+ annotations: {
1337
+ title: 'Start StateTree Debugger',
1338
+ readOnlyHint: false,
1339
+ destructiveHint: false,
1340
+ idempotentHint: false,
1341
+ openWorldHint: false,
1342
+ },
1343
+ }, async ({ asset_path }) => {
1344
+ try {
1345
+ const parsed = await callSubsystemJson('StartStateTreeDebugger', {
1346
+ AssetPath: asset_path,
1347
+ });
1348
+ return jsonToolSuccess(parsed);
1349
+ }
1350
+ catch (error) {
1351
+ return jsonToolError(error);
1352
+ }
1353
+ });
1354
+ server.registerTool('stop_statetree_debugger', {
1355
+ title: 'Stop StateTree Debugger',
1356
+ description: 'Stop the active StateTree debugger session and discard trace data.',
1357
+ inputSchema: {},
1358
+ annotations: {
1359
+ title: 'Stop StateTree Debugger',
1360
+ readOnlyHint: false,
1361
+ destructiveHint: false,
1362
+ idempotentHint: false,
1363
+ openWorldHint: false,
1364
+ },
1365
+ }, async () => {
1366
+ try {
1367
+ const parsed = await callSubsystemJson('StopStateTreeDebugger', {});
1368
+ return jsonToolSuccess(parsed);
1369
+ }
1370
+ catch (error) {
1371
+ return jsonToolError(error);
1372
+ }
1373
+ });
1374
+ server.registerTool('read_statetree_debugger', {
1375
+ title: 'Read StateTree Debugger',
1376
+ description: 'Read current StateTree debugger data: instances, events, active states, transitions, conditions.',
1377
+ inputSchema: {
1378
+ instance_id: z.string().default('').describe('Filter to a specific instance ID (from a previous read). Empty = list all instances without events.'),
1379
+ max_events: z.number().int().positive().default(500).describe('Maximum events to return per instance.'),
1380
+ scrub_time: z.number().default(-1).describe('Set the scrub time before reading. -1 = use current position.'),
1381
+ },
1382
+ annotations: {
1383
+ title: 'Read StateTree Debugger',
1384
+ readOnlyHint: true,
1385
+ destructiveHint: false,
1386
+ idempotentHint: true,
1387
+ openWorldHint: false,
1388
+ },
1389
+ }, async ({ instance_id, max_events, scrub_time }) => {
1390
+ try {
1391
+ const payload = {};
1392
+ if (instance_id) {
1393
+ payload.instanceId = instance_id;
1394
+ }
1395
+ if (max_events !== 500) {
1396
+ payload.maxEvents = max_events;
1397
+ }
1398
+ if (scrub_time >= 0) {
1399
+ payload.scrubTime = scrub_time;
1400
+ }
1401
+ const parsed = await callSubsystemJson('ReadStateTreeDebugger', {
1402
+ PayloadJson: JSON.stringify(payload),
1403
+ });
1404
+ return jsonToolSuccess(parsed);
1405
+ }
1406
+ catch (error) {
1407
+ return jsonToolError(error);
1408
+ }
1409
+ });
1291
1410
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/types.d.ts CHANGED
File without changes
package/dist/types.js CHANGED
File without changes
File without changes
package/dist/ue-client.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blueprint-extractor-mcp",
3
- "version": "8.1.0",
3
+ "version": "8.2.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",