blueprint-extractor-mcp 8.2.3 → 8.2.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
@@ -19,6 +19,8 @@
19
19
 
20
20
  Blueprint Extractor MCP is a [Model Context Protocol](https://modelcontextprotocol.io) server that bridges AI coding assistants (Claude Code, Codex, OpenCode, etc.) to a running Unreal Editor instance via the Remote Control HTTP API.
21
21
 
22
+ For compatible tools, the server can also execute through a commandlet lane when no reachable editor session is available. When an editor is already running, editor execution stays preferred for editor-only flows and for save paths that need to avoid package-lock contention.
23
+
22
24
  ```
23
25
  AI Assistant stdio MCP Server HTTP :30010 Unreal Editor
24
26
  ───────────── ◄────────────► ───────────────── ◄──────────────────► ─────────────────
@@ -116,11 +118,11 @@ npm install --prefix ~/.config/opencode --save-exact blueprint-extractor-mcp@lat
116
118
 
117
119
  ## Tool Surface
118
120
 
119
- 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`.
121
+ Use `activate_tool_profile` to switch between the compact `default` surface and the full `expert` surface. The default profile keeps the context window lean with a retrieval-first core and loads specialized families on demand via `activate_workflow_scope`.
120
122
 
121
123
  | Scope | What It Unlocks |
122
124
  |:------|:----------------|
123
- | **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` |
125
+ | **Core** *(always on in `default` profile)* | Retrieval-first discovery and persistence: `search_assets`, `find_and_extract`, `extract_blueprint`, `extract_asset`, `check_asset_exists`, `save_assets`, `get_tool_help`, `activate_tool_profile`, and `activate_workflow_scope` |
124
126
  | `widget_authoring` | Parent scope that loads `widget_authoring_structure`, `widget_authoring_visual`, and `widget_verification` together |
125
127
  | `widget_authoring_structure` | Recipe-first widget authoring, tree replacement, unified-diff patching, and focused structure edits without the deprecated widget aliases |
126
128
  | `widget_authoring_visual` | Widget compile flows, CommonUI styles, widget animations, and widget preview capture |
@@ -146,6 +148,7 @@ The tool contract is optimized for model reliability:
146
148
  - **`structuredContent`** carries the canonical success and error payload for MCP clients that consume structured results directly
147
149
  - **Structured error envelopes** with diagnostic codes and recovery hints
148
150
  - **Explicit-save semantics** — nothing persists until `save_assets` is called
151
+ - **Dual execution lanes** — compatible tools can fall back to commandlet execution when no editor is reachable, while `save_assets` prefers a running editor and can reroute there on file-lock contention
149
152
  - **Next-step hints** guiding the assistant toward the logical follow-up action
150
153
 
151
154
  See [../docs/CURRENT_STATUS.md](../docs/CURRENT_STATUS.md) for the current validation snapshot, normative docs, and the one-shot stabilization ledger.
@@ -80,7 +80,7 @@ export class ActiveEditorSession {
80
80
  return this.getClient(rebound).checkConnection();
81
81
  }
82
82
  async editorModeAvailable() {
83
- return true;
83
+ return this.checkConnection();
84
84
  }
85
85
  async listRunningEditors() {
86
86
  const result = await listRegisteredEditors();
@@ -11,6 +11,17 @@ const EDITOR_FALLBACK_ERROR_FRAGMENTS = [
11
11
  'previously selected active editor',
12
12
  'The selected active editor is currently unavailable on its registered Remote Control endpoint.',
13
13
  ];
14
+ const COMMANDLET_LOCK_ERROR_FRAGMENTS = [
15
+ 'locked by another process',
16
+ 'locked file',
17
+ 'cannot access the file',
18
+ 'asset lock conflict',
19
+ 'save_failed',
20
+ 'Failed to save one or more packages',
21
+ ];
22
+ const EDITOR_PREFERRED_WHILE_RUNNING_TOOLS = new Set([
23
+ 'save_assets',
24
+ ]);
14
25
  export class AdaptiveExecutor {
15
26
  editorAdapter;
16
27
  commandletAdapter;
@@ -56,7 +67,16 @@ export class AdaptiveExecutor {
56
67
  async executeRouted(editorFallback, method, params, options) {
57
68
  const toolName = this._activeToolName;
58
69
  const detection = await this.detector.detect();
59
- const tryCommandletFallback = async (error, requiredCapability) => {
70
+ if (!toolName) {
71
+ return editorFallback(method, params, options);
72
+ }
73
+ const toolMode = this.getToolMode(toolName);
74
+ const requiredCapability = toolMode === 'read_only'
75
+ ? 'read'
76
+ : toolMode === 'both'
77
+ ? 'write_simple'
78
+ : 'write_complex';
79
+ const tryCommandletFallback = async (error) => {
60
80
  if (!toolName || !this.commandletAdapter || !shouldFallbackToCommandlet(error)) {
61
81
  throw error;
62
82
  }
@@ -71,18 +91,9 @@ export class AdaptiveExecutor {
71
91
  this.detector.invalidateCache();
72
92
  return this.commandletAdapter.execute('BlueprintExtractor', method, params);
73
93
  };
74
- // If no active tool context or editor mode, use the original path
75
- // (preserves callSubsystemJson error-checking layer)
76
- if (!toolName || detection.mode === 'editor') {
77
- if (!toolName) {
78
- return editorFallback(method, params, options);
79
- }
80
- const toolMode = this.getToolMode(toolName);
81
- const requiredCapability = toolMode === 'read_only'
82
- ? 'read'
83
- : toolMode === 'both'
84
- ? 'write_simple'
85
- : 'write_complex';
94
+ // Editor mode keeps the existing direct path, with commandlet fallback for
95
+ // compatible tools when the editor call fails.
96
+ if (detection.mode === 'editor') {
86
97
  try {
87
98
  return await editorFallback(method, params, options);
88
99
  }
@@ -90,15 +101,9 @@ export class AdaptiveExecutor {
90
101
  if (toolMode === 'editor_only') {
91
102
  throw error;
92
103
  }
93
- return tryCommandletFallback(error, requiredCapability);
104
+ return tryCommandletFallback(error);
94
105
  }
95
106
  }
96
- const toolMode = this.getToolMode(toolName);
97
- const requiredCapability = toolMode === 'read_only'
98
- ? 'read'
99
- : toolMode === 'both'
100
- ? 'write_simple'
101
- : 'write_complex';
102
107
  // Try commandlet for compatible tools
103
108
  if (detection.mode === 'commandlet' && this.commandletAdapter) {
104
109
  if (toolMode === 'editor_only') {
@@ -108,7 +113,36 @@ export class AdaptiveExecutor {
108
113
  if (!capabilities.has(requiredCapability) && requiredCapability !== 'write_simple') {
109
114
  throw new ExecutorError('CAPABILITY_MISMATCH', `Tool '${toolName}' requires '${requiredCapability}' capability which commandlet mode does not support.`, toolName, detection.mode, requiredCapability);
110
115
  }
111
- return this.commandletAdapter.execute('BlueprintExtractor', method, params);
116
+ if (shouldPreferEditorWhileRunning(toolName)) {
117
+ try {
118
+ const editorAvailable = await this.editorAdapter.isAvailable();
119
+ if (editorAvailable) {
120
+ this.detector.invalidateCache();
121
+ return await editorFallback(method, params, options);
122
+ }
123
+ }
124
+ catch {
125
+ // Fall back to the commandlet path below.
126
+ }
127
+ }
128
+ try {
129
+ return await this.commandletAdapter.execute('BlueprintExtractor', method, params);
130
+ }
131
+ catch (error) {
132
+ if (toolMode !== 'read_only' && shouldFallbackToEditorOnLock(error)) {
133
+ try {
134
+ const editorAvailable = await this.editorAdapter.isAvailable();
135
+ if (editorAvailable) {
136
+ this.detector.invalidateCache();
137
+ return await editorFallback(method, params, options);
138
+ }
139
+ }
140
+ catch {
141
+ // If the editor is not actually reachable, preserve the commandlet error.
142
+ }
143
+ }
144
+ throw error;
145
+ }
112
146
  }
113
147
  // No adapter available
114
148
  throw new ExecutorError('MODE_UNAVAILABLE', `No execution mode available for tool '${toolName}'. ${detection.reason}`, toolName, detection.mode, requiredCapability);
@@ -122,7 +156,6 @@ export class AdaptiveExecutor {
122
156
  : toolMode === 'both'
123
157
  ? 'write_simple'
124
158
  : 'write_complex';
125
- // Try editor first
126
159
  if (detection.mode === 'editor') {
127
160
  return this.editorAdapter.execute(subsystem, method, params);
128
161
  }
@@ -135,7 +168,36 @@ export class AdaptiveExecutor {
135
168
  if (!capabilities.has(requiredCapability) && requiredCapability !== 'write_simple') {
136
169
  throw new ExecutorError('CAPABILITY_MISMATCH', `Tool '${toolName}' requires '${requiredCapability}' capability which commandlet mode does not support.`, toolName, detection.mode, requiredCapability);
137
170
  }
138
- return this.commandletAdapter.execute(subsystem, method, params);
171
+ if (shouldPreferEditorWhileRunning(toolName)) {
172
+ try {
173
+ const editorAvailable = await this.editorAdapter.isAvailable();
174
+ if (editorAvailable) {
175
+ this.detector.invalidateCache();
176
+ return await this.editorAdapter.execute(subsystem, method, params);
177
+ }
178
+ }
179
+ catch {
180
+ // Keep the commandlet path below.
181
+ }
182
+ }
183
+ try {
184
+ return await this.commandletAdapter.execute(subsystem, method, params);
185
+ }
186
+ catch (error) {
187
+ if (toolMode !== 'read_only' && shouldFallbackToEditorOnLock(error)) {
188
+ try {
189
+ const editorAvailable = await this.editorAdapter.isAvailable();
190
+ if (editorAvailable) {
191
+ this.detector.invalidateCache();
192
+ return await this.editorAdapter.execute(subsystem, method, params);
193
+ }
194
+ }
195
+ catch {
196
+ // Preserve the original commandlet failure below.
197
+ }
198
+ }
199
+ throw error;
200
+ }
139
201
  }
140
202
  // No adapter available
141
203
  throw new ExecutorError('MODE_UNAVAILABLE', `No execution mode available for tool '${toolName}'. ${detection.reason}`, toolName, detection.mode, requiredCapability);
@@ -148,6 +210,13 @@ function shouldFallbackToCommandlet(error) {
148
210
  }
149
211
  return EDITOR_FALLBACK_ERROR_FRAGMENTS.some((fragment) => message.includes(fragment));
150
212
  }
213
+ function shouldFallbackToEditorOnLock(error) {
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ return COMMANDLET_LOCK_ERROR_FRAGMENTS.some((fragment) => message.includes(fragment));
216
+ }
217
+ function shouldPreferEditorWhileRunning(toolName) {
218
+ return EDITOR_PREFERRED_WHILE_RUNNING_TOOLS.has(toolName);
219
+ }
151
220
  export class ExecutorError extends Error {
152
221
  code;
153
222
  toolName;
@@ -1,7 +1,7 @@
1
1
  import { access } from 'node:fs/promises';
2
2
  import { constants as fsConstants } from 'node:fs';
3
3
  import path, { posix as posixPath, win32 as win32Path } from 'node:path';
4
- import { buildEngineAssociationCandidates, isWindowsStylePath, isWslMountedWindowsPath, readProjectEngineAssociation, toHostFilesystemPath, toWindowsStylePath, } from './workspace-project.js';
4
+ import { buildEngineAssociationCandidates, filesystemPathsEqual, isWindowsStylePath, isWslMountedWindowsPath, readProjectEngineAssociation, toHostFilesystemPath, toWindowsStylePath, } from './workspace-project.js';
5
5
  export function rememberExternalBuild(result) {
6
6
  return {
7
7
  success: result.success === true,
@@ -114,6 +114,32 @@ async function probePreferredEngineRoot(candidates, hostPlatform) {
114
114
  }
115
115
  return undefined;
116
116
  }
117
+ function hasConcreteAssociationCandidate(candidates) {
118
+ return candidates.some((candidate) => (isWindowsStylePath(candidate.path)
119
+ || isWslMountedWindowsPath(candidate.path)
120
+ || candidate.path.startsWith('/')));
121
+ }
122
+ function matchesAssociationCandidate(engineRoot, candidates) {
123
+ if (!engineRoot) {
124
+ return false;
125
+ }
126
+ return candidates.some((candidate) => filesystemPathsEqual(engineRoot, candidate.path));
127
+ }
128
+ function buildEngineRootConflict(engineAssociation, candidates, implicitRoots) {
129
+ if (!engineAssociation || implicitRoots.length === 0) {
130
+ return undefined;
131
+ }
132
+ const roots = implicitRoots.map(({ source, path }) => `${source}:${path}`).join(', ');
133
+ const concreteCandidates = candidates
134
+ .map((candidate) => candidate.path)
135
+ .filter((candidate, index, all) => all.indexOf(candidate) === index)
136
+ .filter((candidate) => (isWindowsStylePath(candidate)
137
+ || isWslMountedWindowsPath(candidate)
138
+ || candidate.startsWith('/')));
139
+ return concreteCandidates.length > 0
140
+ ? `project EngineAssociation "${engineAssociation}" conflicts with implicit engine roots (${roots}); expected one of ${concreteCandidates.join(' | ')}`
141
+ : `project EngineAssociation "${engineAssociation}" conflicts with implicit engine roots (${roots})`;
142
+ }
117
143
  export async function resolveProjectInputs(request, deps) {
118
144
  const { getProjectAutomationContext, firstDefinedString, env = process.env, workspaceProjectPath, platform = process.platform, } = deps;
119
145
  let context = null;
@@ -149,11 +175,45 @@ export async function resolveProjectInputs(request, deps) {
149
175
  path: candidate,
150
176
  platform: candidatePlatform,
151
177
  }))));
178
+ const associationIsConcrete = hasConcreteAssociationCandidate(associationCandidates);
179
+ const matchingContextEngineRoot = associationIsConcrete && matchesAssociationCandidate(engineRootFromContext, associationCandidates)
180
+ ? engineRootFromContext
181
+ : undefined;
182
+ const matchingEnvEngineRoot = associationIsConcrete && matchesAssociationCandidate(engineRootFromEnv, associationCandidates)
183
+ ? engineRootFromEnv
184
+ : undefined;
185
+ const conflictingImplicitRoots = associationIsConcrete
186
+ ? [
187
+ ...(engineRootFromContext && !matchingContextEngineRoot ? [{ source: 'editor_context', path: engineRootFromContext }] : []),
188
+ ...(engineRootFromEnv && !matchingEnvEngineRoot ? [{ source: 'environment', path: engineRootFromEnv }] : []),
189
+ ]
190
+ : [];
152
191
  let engineRoot = firstDefinedString(request.engine_root, engineRootFromContext, engineRootFromEnv);
153
192
  let engineRootSource;
193
+ let engineRootConflict;
154
194
  if (request.engine_root) {
155
195
  engineRootSource = 'explicit';
156
196
  }
197
+ else if (associationIsConcrete) {
198
+ const preferredCandidate = await probePreferredEngineRoot(associationCandidates, platform);
199
+ if (matchingContextEngineRoot) {
200
+ engineRoot = matchingContextEngineRoot;
201
+ engineRootSource = 'editor_context';
202
+ }
203
+ else if (matchingEnvEngineRoot) {
204
+ engineRoot = matchingEnvEngineRoot;
205
+ engineRootSource = 'environment';
206
+ }
207
+ else if (preferredCandidate) {
208
+ engineRoot = preferredCandidate;
209
+ engineRootSource = 'project_association';
210
+ }
211
+ else {
212
+ engineRoot = undefined;
213
+ engineRootSource = 'missing';
214
+ engineRootConflict = buildEngineRootConflict(engineAssociation, associationCandidates, conflictingImplicitRoots);
215
+ }
216
+ }
157
217
  else if (engineRootFromContext) {
158
218
  engineRootSource = 'editor_context';
159
219
  }
@@ -161,8 +221,7 @@ export async function resolveProjectInputs(request, deps) {
161
221
  engineRootSource = 'environment';
162
222
  }
163
223
  else {
164
- const preferredCandidate = await probePreferredEngineRoot(associationCandidates, platform);
165
- let heuristicRoot = preferredCandidate;
224
+ let heuristicRoot;
166
225
  if (!heuristicRoot) {
167
226
  for (const candidatePlatform of heuristicPlatforms) {
168
227
  heuristicRoot = await probeEngineRootHeuristic(candidatePlatform, platform);
@@ -185,6 +244,8 @@ export async function resolveProjectInputs(request, deps) {
185
244
  target,
186
245
  context,
187
246
  contextError,
247
+ projectEngineAssociation: engineAssociation,
248
+ engineRootConflict,
188
249
  sources: {
189
250
  engineRoot: engineRootSource,
190
251
  projectPath: request.project_path ? 'explicit' : projectPathFromContext ? 'editor_context' : projectPathFromWorkspace ? 'workspace' : projectPathFromEnv ? 'environment' : 'missing',
@@ -10,11 +10,17 @@ export function buildProjectResolutionDiagnostics(resolved) {
10
10
  `project_path=${resolved.sources.projectPath}`,
11
11
  `target=${resolved.sources.target}`,
12
12
  ];
13
+ if (resolved.projectEngineAssociation) {
14
+ diagnostics.push(`project_engine_association=${resolved.projectEngineAssociation}`);
15
+ }
16
+ if (resolved.engineRootConflict) {
17
+ diagnostics.push(`engine_root_conflict=${resolved.engineRootConflict}`);
18
+ }
13
19
  if (resolved.contextError) {
14
20
  diagnostics.push(`editor_context_error=${resolved.contextError}`);
15
21
  }
16
22
  return diagnostics;
17
23
  }
18
24
  export function explainProjectResolutionFailure(prefix, resolved) {
19
- return new Error(`${prefix}; attempted explicit args -> editor context -> environment (${buildProjectResolutionDiagnostics(resolved).join(', ')})`);
25
+ return new Error(`${prefix}; attempted explicit args -> project association -> editor context -> environment (${buildProjectResolutionDiagnostics(resolved).join(', ')})`);
20
26
  }
@@ -225,7 +225,7 @@ export function registerStaticDocResources(server) {
225
225
  '- list_message_log_listings probes known built-in and caller-supplied Message Log listing names and reports which ones are currently registered.',
226
226
  '- read_message_log reads one registered Message Log listing with severity, token, text, and paging filters.',
227
227
  '- compile_project_code runs an external UBT build from the MCP host.',
228
- '- compile_project_code and sync_project_code resolve engine_root, project_path, and target in this order: explicit args -> editor context -> environment.',
228
+ '- compile_project_code and sync_project_code resolve project inputs by preferring explicit args, then honoring a concrete .uproject EngineAssociation before falling back to editor context and environment defaults.',
229
229
  '- 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.',
230
230
  '- 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.',
231
231
  '- 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.',
@@ -5,8 +5,9 @@ export const serverInstructions = [
5
5
  'Blueprint Extractor MCP uses a v2 public contract with tool profiles, workflow-scoped tool surfaces, snake_case arguments, prompt workflows, and structured JSON results.',
6
6
  // Tool discovery
7
7
  'Use activate_tool_profile with profile: "default" for the compact scoped surface or profile: "expert" for the full flat tool list. Clients with tool-list change support start in default; fallback clients start in expert.',
8
- 'A compact core tool set is visible in the default profile. 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, project_control, automation_testing, verification, analysis, and project_intelligence.',
9
- 'Use find_and_extract for search+extract in one call from the default core surface. Use search_assets when you only need to locate assets.',
8
+ 'A compact retrieval-first core tool set is visible in the default profile: search_assets, find_and_extract, extract_blueprint, extract_asset, check_asset_exists, save_assets, get_tool_help, and the profile/scope switches.',
9
+ '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, project_control, automation_testing, verification, analysis, and project_intelligence.',
10
+ 'Prefer find_and_extract for search plus routed extraction from the default core surface. Use search_assets when you only need to locate assets.',
10
11
  'Call get_tool_help before the first use of a complex or polymorphic tool when you need operation-specific payload guidance. Use activate_workflow_scope explicitly when the tool family is not in the current profile.',
11
12
  // Deferred tool directory (tools available via activate_workflow_scope)
12
13
  'Deferred tool families — widget_authoring_structure: recipe-first widget authoring, tree replacement, diff/patch DSLs, and focused structural edits. widget_authoring_visual: CommonUI styles, widget animations, compile_widget, extraction, and preview capture. widget_verification: captures and comparisons. widget_authoring: activates all three widget sub-scopes. material_authoring: create_material_setup, modify_material DSL/batch authoring, material_graph_operation, and material instances. blueprint_authoring: scaffold_blueprint, modify_blueprint_graphs DSL authoring, member edits, and Live Coding trigger. 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. project_control: editor-session binding, launch/wait, project automation context, Output Log and Message Log inspection, PIE lifecycle control, host build/restart/sync flows, and apply_window_ui_changes. automation_testing: run/get/list automation tests. verification: widget captures, editor screenshots, runtime screenshots, and comparisons. analysis: deterministic Blueprint review and asset audits. project_intelligence: editor context, project indexing, and metadata-first context search.',
@@ -47,13 +47,15 @@ export type EditorContextSnapshot = {
47
47
  partial?: boolean;
48
48
  unsupportedSections?: string[];
49
49
  };
50
- export type ProjectInputSource = 'explicit' | 'editor_context' | 'workspace' | 'environment' | 'filesystem_heuristic' | 'missing';
50
+ export type ProjectInputSource = 'explicit' | 'editor_context' | 'workspace' | 'environment' | 'project_association' | 'filesystem_heuristic' | 'missing';
51
51
  export type ResolvedProjectInputs = {
52
52
  engineRoot?: string;
53
53
  projectPath?: string;
54
54
  target?: string;
55
55
  context: ProjectAutomationContext | null;
56
56
  contextError?: string;
57
+ projectEngineAssociation?: string;
58
+ engineRootConflict?: string;
57
59
  sources: {
58
60
  engineRoot: ProjectInputSource;
59
61
  projectPath: ProjectInputSource;
@@ -8,6 +8,10 @@ export interface WorkflowScope {
8
8
  description: string;
9
9
  }
10
10
  export declare const WORKFLOW_SCOPE_IDS: readonly WorkflowScopeId[];
11
+ /**
12
+ * Keep the always-visible default surface small so weaker models can route
13
+ * through search, routed extraction, and explicit help before expanding.
14
+ */
11
15
  export declare const CORE_TOOLS: ReadonlySet<string>;
12
16
  export declare class ToolSurfaceManager {
13
17
  private registeredToolMap;
@@ -15,14 +15,15 @@ export const WORKFLOW_SCOPE_IDS = [
15
15
  'analysis',
16
16
  'project_intelligence',
17
17
  ];
18
+ /**
19
+ * Keep the always-visible default surface small so weaker models can route
20
+ * through search, routed extraction, and explicit help before expanding.
21
+ */
18
22
  export const CORE_TOOLS = new Set([
19
23
  'search_assets',
20
24
  'find_and_extract',
21
25
  'extract_blueprint',
22
26
  'extract_asset',
23
- 'extract_material',
24
- 'extract_cascade',
25
- 'list_assets',
26
27
  'check_asset_exists',
27
28
  'save_assets',
28
29
  'get_tool_help',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blueprint-extractor-mcp",
3
- "version": "8.2.3",
3
+ "version": "8.2.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",