blueprint-extractor-mcp 3.1.0 → 3.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.
- package/dist/helpers/subsystem.js +33 -3
- package/dist/helpers/tool-results.js +9 -0
- package/dist/register-server-tools.js +2 -1
- package/dist/resources/static-doc-resources.js +1 -1
- package/dist/schemas/tool-inputs.d.ts +17 -1
- package/dist/schemas/tool-inputs.js +7 -0
- package/dist/tools/project-control.js +36 -3
- package/dist/tools/schema-and-ai-authoring.d.ts +2 -1
- package/dist/tools/schema-and-ai-authoring.js +106 -5
- package/dist/ue-client.js +10 -1
- package/package.json +2 -2
|
@@ -6,7 +6,9 @@ export async function callSubsystemJson(client, method, params, options) {
|
|
|
6
6
|
}
|
|
7
7
|
const parsed = JSON.parse(result);
|
|
8
8
|
if (typeof parsed.error === 'string' && parsed.error.length > 0) {
|
|
9
|
-
|
|
9
|
+
const err = new Error(parsed.error);
|
|
10
|
+
err.ueResponse = parsed;
|
|
11
|
+
throw err;
|
|
10
12
|
}
|
|
11
13
|
// Check for error-only failure responses (success: false with an explicit error message
|
|
12
14
|
// but no business-level fields). Structured responses with success: false are passed
|
|
@@ -14,12 +16,31 @@ export async function callSubsystemJson(client, method, params, options) {
|
|
|
14
16
|
if (parsed.success === false) {
|
|
15
17
|
const explicitMessage = parsed.message ?? parsed.errorMessage;
|
|
16
18
|
if (typeof explicitMessage === 'string' && explicitMessage.length > 0) {
|
|
17
|
-
|
|
19
|
+
const err = new Error(explicitMessage);
|
|
20
|
+
err.ueResponse = parsed;
|
|
21
|
+
throw err;
|
|
18
22
|
}
|
|
19
23
|
}
|
|
24
|
+
// Catch structured error responses with diagnostics but no explicit message.
|
|
25
|
+
// These come from FAssetMutationContext.BuildResult(false) in the C++ plugin.
|
|
26
|
+
if (parsed.success === false
|
|
27
|
+
&& Array.isArray(parsed.diagnostics)
|
|
28
|
+
&& parsed.diagnostics.length > 0) {
|
|
29
|
+
const messages = parsed.diagnostics
|
|
30
|
+
.filter((d) => typeof d === 'object' && d !== null && typeof d.message === 'string')
|
|
31
|
+
.map((d) => d.message);
|
|
32
|
+
const synthesized = messages.length > 0
|
|
33
|
+
? messages.join('; ')
|
|
34
|
+
: `Operation failed with ${parsed.diagnostics.length} diagnostic(s)`;
|
|
35
|
+
const err = new Error(synthesized);
|
|
36
|
+
err.ueResponse = parsed;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
20
39
|
if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
|
|
21
40
|
const firstError = parsed.errors[0];
|
|
22
|
-
|
|
41
|
+
const err = new Error(typeof firstError === 'string' ? firstError : JSON.stringify(firstError));
|
|
42
|
+
err.ueResponse = parsed;
|
|
43
|
+
throw err;
|
|
23
44
|
}
|
|
24
45
|
if (Object.keys(parsed).length === 0) {
|
|
25
46
|
throw new Error('Empty response from subsystem');
|
|
@@ -28,6 +49,15 @@ export async function callSubsystemJson(client, method, params, options) {
|
|
|
28
49
|
}
|
|
29
50
|
export function jsonToolSuccess(parsed, options = {}) {
|
|
30
51
|
const structuredContent = isRecord(parsed) ? parsed : { data: parsed };
|
|
52
|
+
// Guard: if the UE response indicates failure, route through error path.
|
|
53
|
+
// This prevents tool handlers from accidentally passing error payloads as successes.
|
|
54
|
+
if (isRecord(parsed) && parsed.success === false) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: 'text', text: `Error: ${typeof parsed.message === 'string' ? parsed.message : 'Operation failed'}` }],
|
|
57
|
+
structuredContent,
|
|
58
|
+
isError: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
31
61
|
return {
|
|
32
62
|
content: options.extraContent ? [...options.extraContent] : [],
|
|
33
63
|
structuredContent,
|
|
@@ -65,6 +65,15 @@ export function createToolResultNormalizers({ taskAwareTools, classifyRecoverabl
|
|
|
65
65
|
const diagnostics = Array.isArray(payload.diagnostics)
|
|
66
66
|
? payload.diagnostics
|
|
67
67
|
: [];
|
|
68
|
+
// Merge diagnostics from ueResponse if the error carries one (set by callSubsystemJson).
|
|
69
|
+
if (diagnostics.length === 0
|
|
70
|
+
&& payloadOrError instanceof Error
|
|
71
|
+
&& isRecord(payloadOrError.ueResponse)) {
|
|
72
|
+
const ueResp = payloadOrError.ueResponse;
|
|
73
|
+
if (Array.isArray(ueResp.diagnostics)) {
|
|
74
|
+
diagnostics.push(...ueResp.diagnostics);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
68
77
|
const firstDiagnostic = diagnostics.find((candidate) => (isRecord(candidate)
|
|
69
78
|
&& typeof candidate.message === 'string'
|
|
70
79
|
&& candidate.message.length > 0));
|
|
@@ -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, ImportPayloadSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialFunctionAssetKindSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialGraphPayloadSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportPayloadSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, TextureImportPayloadSchema, 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, ImportPayloadSchema, InputMappingSchema, JsonObjectSchema, MaterialConnectionSelectorFieldsSchema, MaterialFontParameterSchema, MaterialFunctionAssetKindSchema, MaterialGraphOperationKindSchema, MaterialGraphOperationSchema, MaterialGraphPayloadSchema, MaterialLayerStackSchema, MaterialNodePositionSchema, MaterialScalarParameterSchema, MaterialStaticSwitchParameterSchema, MaterialTextureParameterSchema, MaterialVectorParameterSchema, MeshImportPayloadSchema, StateTreeEditorNodeSelectorSchema, StateTreeMutationOperationSchema, StateTreeStateSelectorSchema, StateTreeTransitionSelectorSchema, StateTreeBindingSchema, TextureImportPayloadSchema, UserDefinedEnumEntrySchema, UserDefinedEnumMutationOperationSchema, UserDefinedStructFieldSchema, UserDefinedStructMutationOperationSchema, WidgetBlueprintMutationOperationSchema, 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 { registerAutomationRunTools } from './tools/automation-runs.js';
|
|
@@ -166,6 +166,7 @@ export function registerServerTools({ server, client, projectController, automat
|
|
|
166
166
|
stateTreeStateSelectorSchema: StateTreeStateSelectorSchema,
|
|
167
167
|
stateTreeEditorNodeSelectorSchema: StateTreeEditorNodeSelectorSchema,
|
|
168
168
|
stateTreeTransitionSelectorSchema: StateTreeTransitionSelectorSchema,
|
|
169
|
+
stateTreeBindingSchema: StateTreeBindingSchema,
|
|
169
170
|
});
|
|
170
171
|
registerAnimationAuthoringTools({
|
|
171
172
|
server,
|
|
@@ -68,7 +68,7 @@ export function registerStaticDocResources(server) {
|
|
|
68
68
|
'- AnimSequence: notify selector by notifyId/notifyGuid with notifyIndex or track metadata as fallback; operations replace_notifies, patch_notify, replace_sync_markers, replace_curve_metadata.',
|
|
69
69
|
'- AnimMontage: notify selector by notifyId/notifyGuid with notifyIndex or track metadata as fallback; operations replace_notifies, patch_notify, replace_sections, replace_slots.',
|
|
70
70
|
'- BlendSpace: sample selector by sampleIndex; operations replace_samples, patch_sample, set_axes.',
|
|
71
|
-
'- Blueprint members: selectors by variableName, componentName, and functionName; operations replace_variables, patch_variable, replace_components, patch_component, replace_function_stubs, patch_class_defaults, compile.',
|
|
71
|
+
'- Blueprint members: selectors by variableName, componentName, and functionName; operations replace_variables, patch_variable, replace_components, patch_component, add_component, replace_function_stubs, patch_class_defaults, compile.',
|
|
72
72
|
'- Blueprint graphs: operation upsert_function_graphs preserves unrelated graphs; append_function_call_to_sequence patches an existing sequence-style initializer without replacing the whole graph.',
|
|
73
73
|
'',
|
|
74
74
|
'WidgetBlueprint guidance:',
|
|
@@ -3038,7 +3038,7 @@ export declare const StateTreeMutationOperationSchema: z.ZodEnum<["replace_tree"
|
|
|
3038
3038
|
export declare const AnimSequenceMutationOperationSchema: z.ZodEnum<["replace_notifies", "patch_notify", "replace_sync_markers", "replace_curve_metadata"]>;
|
|
3039
3039
|
export declare const AnimMontageMutationOperationSchema: z.ZodEnum<["replace_notifies", "patch_notify", "replace_sections", "replace_slots"]>;
|
|
3040
3040
|
export declare const BlendSpaceMutationOperationSchema: z.ZodEnum<["replace_samples", "patch_sample", "set_axes"]>;
|
|
3041
|
-
export declare const BlueprintMemberMutationOperationSchema: z.ZodEnum<["replace_variables", "patch_variable", "replace_components", "patch_component", "replace_function_stubs", "patch_class_defaults", "compile"]>;
|
|
3041
|
+
export declare const BlueprintMemberMutationOperationSchema: z.ZodEnum<["replace_variables", "patch_variable", "replace_components", "patch_component", "add_component", "replace_function_stubs", "patch_class_defaults", "compile"]>;
|
|
3042
3042
|
export declare const BlueprintGraphMutationOperationSchema: z.ZodEnum<["upsert_function_graphs", "append_function_call_to_sequence", "compile"]>;
|
|
3043
3043
|
export declare const UserDefinedStructFieldSchema: z.ZodObject<{
|
|
3044
3044
|
guid: z.ZodOptional<z.ZodString>;
|
|
@@ -3146,6 +3146,22 @@ export declare const StateTreeTransitionSelectorSchema: z.ZodObject<{
|
|
|
3146
3146
|
transitionId: z.ZodOptional<z.ZodString>;
|
|
3147
3147
|
id: z.ZodOptional<z.ZodString>;
|
|
3148
3148
|
}, z.ZodTypeAny, "passthrough">>;
|
|
3149
|
+
export declare const StateTreeBindingSchema: z.ZodObject<{
|
|
3150
|
+
sourceTask: z.ZodString;
|
|
3151
|
+
sourceProperty: z.ZodString;
|
|
3152
|
+
targetTask: z.ZodString;
|
|
3153
|
+
targetProperty: z.ZodString;
|
|
3154
|
+
}, "strip", z.ZodTypeAny, {
|
|
3155
|
+
sourceTask: string;
|
|
3156
|
+
sourceProperty: string;
|
|
3157
|
+
targetTask: string;
|
|
3158
|
+
targetProperty: string;
|
|
3159
|
+
}, {
|
|
3160
|
+
sourceTask: string;
|
|
3161
|
+
sourceProperty: string;
|
|
3162
|
+
targetTask: string;
|
|
3163
|
+
targetProperty: string;
|
|
3164
|
+
}>;
|
|
3149
3165
|
export declare const AnimationNotifySelectorSchema: z.ZodObject<{
|
|
3150
3166
|
notifyId: z.ZodOptional<z.ZodString>;
|
|
3151
3167
|
notifyGuid: z.ZodOptional<z.ZodString>;
|
|
@@ -359,6 +359,7 @@ export const BlueprintMemberMutationOperationSchema = z.enum([
|
|
|
359
359
|
'patch_variable',
|
|
360
360
|
'replace_components',
|
|
361
361
|
'patch_component',
|
|
362
|
+
'add_component',
|
|
362
363
|
'replace_function_stubs',
|
|
363
364
|
'patch_class_defaults',
|
|
364
365
|
'compile',
|
|
@@ -408,6 +409,12 @@ export const StateTreeTransitionSelectorSchema = z.object({
|
|
|
408
409
|
transitionId: z.string().optional(),
|
|
409
410
|
id: z.string().optional(),
|
|
410
411
|
}).passthrough();
|
|
412
|
+
export const StateTreeBindingSchema = z.object({
|
|
413
|
+
sourceTask: z.string().describe('Name of the source task whose output provides the value'),
|
|
414
|
+
sourceProperty: z.string().describe('Property name on the source task to read from'),
|
|
415
|
+
targetTask: z.string().describe('Name of the target task whose input receives the value'),
|
|
416
|
+
targetProperty: z.string().describe('Property name on the target task to write to'),
|
|
417
|
+
}).describe('Task output-to-input binding (requires C++ plugin support — not yet implemented)');
|
|
411
418
|
export const AnimationNotifySelectorSchema = z.object({
|
|
412
419
|
notifyId: z.string().optional(),
|
|
413
420
|
notifyGuid: z.string().optional(),
|
|
@@ -304,6 +304,8 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
304
304
|
openWorldHint: false,
|
|
305
305
|
},
|
|
306
306
|
}, async ({ changed_paths, force_rebuild, engine_root, project_path, target, platform, configuration, save_dirty_assets, save_asset_paths, build_timeout_seconds, disconnect_timeout_seconds, reconnect_timeout_seconds, include_output, clear_uht_cache, restart_first, }) => {
|
|
307
|
+
const stepErrors = {};
|
|
308
|
+
let currentStep = 'init';
|
|
307
309
|
try {
|
|
308
310
|
const resolvedProjectInputs = await resolveProjectInputs({ engine_root, project_path, target });
|
|
309
311
|
// Normalize changed_paths to absolute paths
|
|
@@ -325,7 +327,6 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
325
327
|
pathWarnings.push(`Normalized relative path: "${original}" → "${normalizedPaths[i]}"`);
|
|
326
328
|
}
|
|
327
329
|
});
|
|
328
|
-
const stepErrors = {};
|
|
329
330
|
const plan = projectController.classifyChangedPaths(normalizedPaths, force_rebuild);
|
|
330
331
|
const structuredResult = {
|
|
331
332
|
success: false,
|
|
@@ -337,6 +338,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
337
338
|
if (pathWarnings.length > 0) {
|
|
338
339
|
structuredResult.pathWarnings = pathWarnings;
|
|
339
340
|
}
|
|
341
|
+
currentStep = 'live_coding';
|
|
340
342
|
if (plan.strategy === 'live_coding') {
|
|
341
343
|
if (!projectController.liveCodingSupported) {
|
|
342
344
|
structuredResult.plan = {
|
|
@@ -365,6 +367,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
365
367
|
changedPaths: normalizedPaths,
|
|
366
368
|
plan,
|
|
367
369
|
liveCoding,
|
|
370
|
+
failedStep: liveCoding.success !== true ? currentStep : undefined,
|
|
368
371
|
};
|
|
369
372
|
if (pathWarnings.length > 0)
|
|
370
373
|
lcResult.pathWarnings = pathWarnings;
|
|
@@ -381,6 +384,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
386
|
if (restart_first) {
|
|
387
|
+
currentStep = 'pre_save';
|
|
384
388
|
if (Array.isArray(save_asset_paths) && save_asset_paths.length > 0) {
|
|
385
389
|
try {
|
|
386
390
|
const preSave = await callSubsystemJson('SaveAssets', {
|
|
@@ -393,6 +397,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
393
397
|
// Pre-save is non-critical — continue to restart
|
|
394
398
|
}
|
|
395
399
|
}
|
|
400
|
+
currentStep = 'pre_restart';
|
|
396
401
|
try {
|
|
397
402
|
const preRestart = await callSubsystemJson('RestartEditor', {
|
|
398
403
|
bWarn: false,
|
|
@@ -404,6 +409,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
404
409
|
if (preRestart.success === false) {
|
|
405
410
|
stepErrors.preRestart = 'RestartEditor returned success=false';
|
|
406
411
|
structuredResult.strategy = 'restart_first';
|
|
412
|
+
structuredResult.failedStep = currentStep;
|
|
407
413
|
if (Object.keys(stepErrors).length > 0)
|
|
408
414
|
structuredResult.stepErrors = stepErrors;
|
|
409
415
|
return jsonToolSuccess(structuredResult);
|
|
@@ -413,6 +419,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
413
419
|
stepErrors.preRestart = preRestartError instanceof Error ? preRestartError.message : String(preRestartError);
|
|
414
420
|
structuredResult.strategy = 'restart_first';
|
|
415
421
|
structuredResult.success = false;
|
|
422
|
+
structuredResult.failedStep = currentStep;
|
|
416
423
|
if (Object.keys(stepErrors).length > 0)
|
|
417
424
|
structuredResult.stepErrors = stepErrors;
|
|
418
425
|
return jsonToolSuccess(structuredResult);
|
|
@@ -427,6 +434,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
427
434
|
if (!preDisconnect.success) {
|
|
428
435
|
stepErrors.preDisconnect = 'Editor did not disconnect within timeout';
|
|
429
436
|
structuredResult.strategy = 'restart_first';
|
|
437
|
+
structuredResult.failedStep = currentStep;
|
|
430
438
|
if (Object.keys(stepErrors).length > 0)
|
|
431
439
|
structuredResult.stepErrors = stepErrors;
|
|
432
440
|
return jsonToolSuccess(structuredResult);
|
|
@@ -436,11 +444,13 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
436
444
|
stepErrors.preDisconnect = preDisconnectError instanceof Error ? preDisconnectError.message : String(preDisconnectError);
|
|
437
445
|
structuredResult.strategy = 'restart_first';
|
|
438
446
|
structuredResult.success = false;
|
|
447
|
+
structuredResult.failedStep = currentStep;
|
|
439
448
|
if (Object.keys(stepErrors).length > 0)
|
|
440
449
|
structuredResult.stepErrors = stepErrors;
|
|
441
450
|
return jsonToolSuccess(structuredResult);
|
|
442
451
|
}
|
|
443
452
|
}
|
|
453
|
+
currentStep = 'build';
|
|
444
454
|
let build;
|
|
445
455
|
try {
|
|
446
456
|
build = await projectController.compileProjectCode({
|
|
@@ -459,6 +469,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
459
469
|
stepErrors.build = buildError instanceof Error ? buildError.message : String(buildError);
|
|
460
470
|
structuredResult.strategy = restart_first ? 'restart_first' : 'build_and_restart';
|
|
461
471
|
structuredResult.success = false;
|
|
472
|
+
structuredResult.failedStep = currentStep;
|
|
462
473
|
if (Object.keys(stepErrors).length > 0)
|
|
463
474
|
structuredResult.stepErrors = stepErrors;
|
|
464
475
|
return jsonToolSuccess(structuredResult);
|
|
@@ -466,10 +477,12 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
466
477
|
structuredResult.strategy = restart_first ? 'restart_first' : 'build_and_restart';
|
|
467
478
|
structuredResult.build = build;
|
|
468
479
|
if (!build.success) {
|
|
480
|
+
structuredResult.failedStep = currentStep;
|
|
469
481
|
if (Object.keys(stepErrors).length > 0)
|
|
470
482
|
structuredResult.stepErrors = stepErrors;
|
|
471
483
|
return jsonToolSuccess(structuredResult);
|
|
472
484
|
}
|
|
485
|
+
currentStep = 'save';
|
|
473
486
|
if (!restart_first && Array.isArray(save_asset_paths) && save_asset_paths.length > 0) {
|
|
474
487
|
try {
|
|
475
488
|
const saveResult = await callSubsystemJson('SaveAssets', {
|
|
@@ -478,6 +491,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
478
491
|
structuredResult.save = saveResult;
|
|
479
492
|
if (saveResult.success === false) {
|
|
480
493
|
stepErrors.save = 'SaveAssets returned success=false';
|
|
494
|
+
structuredResult.failedStep = currentStep;
|
|
481
495
|
if (Object.keys(stepErrors).length > 0)
|
|
482
496
|
structuredResult.stepErrors = stepErrors;
|
|
483
497
|
return jsonToolSuccess(structuredResult);
|
|
@@ -488,6 +502,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
488
502
|
// Save is non-critical before restart — continue
|
|
489
503
|
}
|
|
490
504
|
}
|
|
505
|
+
currentStep = 'restart';
|
|
491
506
|
let reconnect;
|
|
492
507
|
if (restart_first) {
|
|
493
508
|
try {
|
|
@@ -499,6 +514,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
499
514
|
structuredResult.editorLaunch = launch;
|
|
500
515
|
if (!launch.success) {
|
|
501
516
|
stepErrors.editorLaunch = 'launchEditor returned success=false';
|
|
517
|
+
structuredResult.failedStep = currentStep;
|
|
502
518
|
if (Object.keys(stepErrors).length > 0)
|
|
503
519
|
structuredResult.stepErrors = stepErrors;
|
|
504
520
|
return jsonToolSuccess(structuredResult);
|
|
@@ -507,10 +523,12 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
507
523
|
catch (launchError) {
|
|
508
524
|
stepErrors.editorLaunch = launchError instanceof Error ? launchError.message : String(launchError);
|
|
509
525
|
structuredResult.success = false;
|
|
526
|
+
structuredResult.failedStep = currentStep;
|
|
510
527
|
if (Object.keys(stepErrors).length > 0)
|
|
511
528
|
structuredResult.stepErrors = stepErrors;
|
|
512
529
|
return jsonToolSuccess(structuredResult);
|
|
513
530
|
}
|
|
531
|
+
currentStep = 'reconnect';
|
|
514
532
|
try {
|
|
515
533
|
reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
516
534
|
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
@@ -534,6 +552,7 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
534
552
|
structuredResult.restartRequestSaveDirtyAssetsAccepted = save_dirty_assets;
|
|
535
553
|
if (restartRequest.success === false) {
|
|
536
554
|
stepErrors.restart = 'RestartEditor returned success=false';
|
|
555
|
+
structuredResult.failedStep = currentStep;
|
|
537
556
|
if (Object.keys(stepErrors).length > 0)
|
|
538
557
|
structuredResult.stepErrors = stepErrors;
|
|
539
558
|
return jsonToolSuccess(structuredResult);
|
|
@@ -542,10 +561,12 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
542
561
|
catch (restartError) {
|
|
543
562
|
stepErrors.restart = restartError instanceof Error ? restartError.message : String(restartError);
|
|
544
563
|
structuredResult.success = false;
|
|
564
|
+
structuredResult.failedStep = currentStep;
|
|
545
565
|
if (Object.keys(stepErrors).length > 0)
|
|
546
566
|
structuredResult.stepErrors = stepErrors;
|
|
547
567
|
return jsonToolSuccess(structuredResult);
|
|
548
568
|
}
|
|
569
|
+
currentStep = 'reconnect';
|
|
549
570
|
try {
|
|
550
571
|
reconnect = await projectController.waitForEditorRestart(supportsConnectionProbe(client), {
|
|
551
572
|
disconnectTimeoutMs: disconnect_timeout_seconds * 1000,
|
|
@@ -572,9 +593,21 @@ export function registerProjectControlTools({ server, client, projectController,
|
|
|
572
593
|
return jsonToolSuccess(structuredResult);
|
|
573
594
|
}
|
|
574
595
|
catch (error) {
|
|
575
|
-
|
|
596
|
+
// Preserve accumulated step context even in unhandled exception path.
|
|
597
|
+
const resolved = await resolveProjectInputs({ engine_root, project_path, target }).catch(() => ({}));
|
|
576
598
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
577
|
-
|
|
599
|
+
const failurePayload = {
|
|
600
|
+
success: false,
|
|
601
|
+
operation: 'sync_project_code',
|
|
602
|
+
message: String(explainProjectResolutionFailure(errorMessage, resolved)),
|
|
603
|
+
failedStep: currentStep,
|
|
604
|
+
stepErrors,
|
|
605
|
+
changedPaths: changed_paths,
|
|
606
|
+
};
|
|
607
|
+
if (error instanceof Error && error.stack) {
|
|
608
|
+
failurePayload.stack = error.stack;
|
|
609
|
+
}
|
|
610
|
+
return jsonToolSuccess(failurePayload);
|
|
578
611
|
}
|
|
579
612
|
});
|
|
580
613
|
}
|
|
@@ -19,6 +19,7 @@ type RegisterSchemaAndAiAuthoringToolsOptions = {
|
|
|
19
19
|
stateTreeStateSelectorSchema: z.ZodTypeAny;
|
|
20
20
|
stateTreeEditorNodeSelectorSchema: z.ZodTypeAny;
|
|
21
21
|
stateTreeTransitionSelectorSchema: z.ZodTypeAny;
|
|
22
|
+
stateTreeBindingSchema: z.ZodTypeAny;
|
|
22
23
|
};
|
|
23
|
-
export declare function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, jsonObjectSchema, userDefinedStructMutationOperationSchema, userDefinedStructFieldSchema, userDefinedEnumMutationOperationSchema, userDefinedEnumEntrySchema, blackboardMutationOperationSchema, blackboardKeySchema, behaviorTreeMutationOperationSchema, behaviorTreeNodeSelectorSchema, stateTreeMutationOperationSchema, stateTreeStateSelectorSchema, stateTreeEditorNodeSelectorSchema, stateTreeTransitionSelectorSchema, }: RegisterSchemaAndAiAuthoringToolsOptions): void;
|
|
24
|
+
export declare function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, jsonObjectSchema, userDefinedStructMutationOperationSchema, userDefinedStructFieldSchema, userDefinedEnumMutationOperationSchema, userDefinedEnumEntrySchema, blackboardMutationOperationSchema, blackboardKeySchema, behaviorTreeMutationOperationSchema, behaviorTreeNodeSelectorSchema, stateTreeMutationOperationSchema, stateTreeStateSelectorSchema, stateTreeEditorNodeSelectorSchema, stateTreeTransitionSelectorSchema, stateTreeBindingSchema, }: RegisterSchemaAndAiAuthoringToolsOptions): void;
|
|
24
25
|
export {};
|
|
@@ -3,6 +3,7 @@ import { jsonToolError, jsonToolSuccess, normalizeUStructPath } from '../helpers
|
|
|
3
3
|
/**
|
|
4
4
|
* Recursively walks a payload object and normalizes all `nodeStructType` values
|
|
5
5
|
* by stripping the C++ F-prefix from USTRUCT script paths.
|
|
6
|
+
* Also validates that nodeStructType paths match the expected `/Script/...` pattern.
|
|
6
7
|
*/
|
|
7
8
|
function normalizePayloadPaths(obj, warnings) {
|
|
8
9
|
if (Array.isArray(obj))
|
|
@@ -26,6 +27,47 @@ function normalizePayloadPaths(obj, warnings) {
|
|
|
26
27
|
}
|
|
27
28
|
return obj;
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Validates that all `nodeStructType` values in the payload use the correct
|
|
32
|
+
* `/Script/Module.ClassName` path format. Collects warnings for paths that
|
|
33
|
+
* look like Blueprint asset paths, raw class names, or other incorrect formats.
|
|
34
|
+
*
|
|
35
|
+
* This runs AFTER normalizePayloadPaths so F-prefix issues are already resolved.
|
|
36
|
+
*/
|
|
37
|
+
function validateNodeStructTypes(obj, warnings) {
|
|
38
|
+
if (Array.isArray(obj)) {
|
|
39
|
+
for (const item of obj)
|
|
40
|
+
validateNodeStructTypes(item, warnings);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (obj && typeof obj === 'object') {
|
|
44
|
+
const record = obj;
|
|
45
|
+
for (const [key, value] of Object.entries(record)) {
|
|
46
|
+
if (key === 'nodeStructType' && typeof value === 'string') {
|
|
47
|
+
if (!value.startsWith('/Script/')) {
|
|
48
|
+
if (value.startsWith('/Game/') || value.startsWith('/Content/')) {
|
|
49
|
+
warnings.push(`Warning: nodeStructType "${value}" looks like a Blueprint asset path. `
|
|
50
|
+
+ 'nodeStructType should be a C++ USTRUCT script path (e.g., "/Script/Module.ClassName")');
|
|
51
|
+
}
|
|
52
|
+
else if (!value.includes('/')) {
|
|
53
|
+
warnings.push(`Warning: nodeStructType "${value}" is a bare class name. `
|
|
54
|
+
+ 'Use the full script path format: "/Script/ModuleName.ClassName"');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
warnings.push(`Warning: nodeStructType "${value}" does not match the expected /Script/Module.ClassName pattern`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (!value.includes('.')) {
|
|
61
|
+
warnings.push(`Warning: nodeStructType "${value}" is missing the class name after the module path. `
|
|
62
|
+
+ 'Expected format: "/Script/Module.ClassName"');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
validateNodeStructTypes(value, warnings);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
29
71
|
/**
|
|
30
72
|
* Checks transition targetState.stateName references against the flat list of
|
|
31
73
|
* state names in the payload. Collects warnings for unresolvable targets.
|
|
@@ -60,7 +102,56 @@ function validateTransitionTargets(payload, warnings) {
|
|
|
60
102
|
}
|
|
61
103
|
}
|
|
62
104
|
}
|
|
63
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Collects all task names from a states array, recursively walking children.
|
|
107
|
+
*/
|
|
108
|
+
function collectTaskNames(states) {
|
|
109
|
+
const taskNames = new Set();
|
|
110
|
+
function walkStates(stateList) {
|
|
111
|
+
for (const state of stateList) {
|
|
112
|
+
const tasks = state.tasks;
|
|
113
|
+
if (Array.isArray(tasks)) {
|
|
114
|
+
for (const task of tasks) {
|
|
115
|
+
if (typeof task.name === 'string')
|
|
116
|
+
taskNames.add(task.name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const children = state.children;
|
|
120
|
+
if (Array.isArray(children))
|
|
121
|
+
walkStates(children);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
walkStates(states);
|
|
125
|
+
return taskNames;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validates that binding sourceTask and targetTask references point to task
|
|
129
|
+
* names that exist in the payload's states. Collects warnings for unresolvable references.
|
|
130
|
+
*/
|
|
131
|
+
function validateBindingTaskReferences(payload, warnings) {
|
|
132
|
+
if (!payload)
|
|
133
|
+
return;
|
|
134
|
+
const bindings = (payload.bindings ?? payload.stateTree?.bindings);
|
|
135
|
+
if (!Array.isArray(bindings) || bindings.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
const states = (payload.states ?? payload.stateTree?.states);
|
|
138
|
+
if (!Array.isArray(states) || states.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
const taskNames = collectTaskNames(states);
|
|
141
|
+
if (taskNames.size === 0)
|
|
142
|
+
return;
|
|
143
|
+
for (const binding of bindings) {
|
|
144
|
+
const source = binding.sourceTask;
|
|
145
|
+
const target = binding.targetTask;
|
|
146
|
+
if (typeof source === 'string' && source.length > 0 && !taskNames.has(source)) {
|
|
147
|
+
warnings.push(`Warning: binding references sourceTask "${source}" which is not found in the states' tasks`);
|
|
148
|
+
}
|
|
149
|
+
if (typeof target === 'string' && target.length > 0 && !taskNames.has(target)) {
|
|
150
|
+
warnings.push(`Warning: binding references targetTask "${target}" which is not found in the states' tasks`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, jsonObjectSchema, userDefinedStructMutationOperationSchema, userDefinedStructFieldSchema, userDefinedEnumMutationOperationSchema, userDefinedEnumEntrySchema, blackboardMutationOperationSchema, blackboardKeySchema, behaviorTreeMutationOperationSchema, behaviorTreeNodeSelectorSchema, stateTreeMutationOperationSchema, stateTreeStateSelectorSchema, stateTreeEditorNodeSelectorSchema, stateTreeTransitionSelectorSchema, stateTreeBindingSchema, }) {
|
|
64
155
|
server.registerTool('create_user_defined_struct', {
|
|
65
156
|
title: 'Create UserDefinedStruct',
|
|
66
157
|
description: 'Create a UE5 UserDefinedStruct asset from extractor-shaped field definitions.',
|
|
@@ -367,6 +458,7 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
367
458
|
states: z.array(jsonObjectSchema).optional(),
|
|
368
459
|
evaluators: z.array(jsonObjectSchema).optional(),
|
|
369
460
|
globalTasks: z.array(jsonObjectSchema).optional(),
|
|
461
|
+
bindings: z.array(stateTreeBindingSchema).optional().describe('Task output-to-input bindings. Declares data flow between tasks (requires C++ plugin support — not yet implemented).'),
|
|
370
462
|
}).passthrough().default({}).describe('Extractor-shaped StateTree payload.'),
|
|
371
463
|
validate_only: z.boolean().default(false).describe('Validate and compile without creating the asset.'),
|
|
372
464
|
timeout_seconds: z.number().positive().optional().describe('Timeout in seconds for the subsystem call. Default 120. Complex payloads may need more time.'),
|
|
@@ -393,9 +485,12 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
393
485
|
warnings.push(`Warning: schema path "${schema}" does not match expected /Script/... pattern`);
|
|
394
486
|
}
|
|
395
487
|
validateTransitionTargets(payload, warnings);
|
|
488
|
+
validateBindingTaskReferences(payload, warnings);
|
|
396
489
|
const normWarnings = [];
|
|
397
490
|
const normalizedPayload = normalizePayloadPaths(payload ?? {}, normWarnings);
|
|
398
491
|
warnings.push(...normWarnings);
|
|
492
|
+
// Validate nodeStructType path formats after normalization
|
|
493
|
+
validateNodeStructTypes(normalizedPayload, warnings);
|
|
399
494
|
const timeoutMs = (timeout_seconds ?? 120) * 1000;
|
|
400
495
|
const parsed = await callSubsystemJson('CreateStateTree', {
|
|
401
496
|
AssetPath: asset_path,
|
|
@@ -405,8 +500,9 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
405
500
|
const result = warnings.length > 0
|
|
406
501
|
? { ...parsed, warnings }
|
|
407
502
|
: parsed;
|
|
408
|
-
const
|
|
409
|
-
|
|
503
|
+
const allWarnings = [...normWarnings, ...warnings.filter(w => !normWarnings.includes(w))];
|
|
504
|
+
const extraContent = allWarnings.length > 0
|
|
505
|
+
? [{ type: 'text', text: allWarnings.join('\n') }]
|
|
410
506
|
: undefined;
|
|
411
507
|
return jsonToolSuccess(result, { extraContent });
|
|
412
508
|
}
|
|
@@ -435,6 +531,7 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
435
531
|
statePath: z.string().optional(),
|
|
436
532
|
editorNodeId: z.string().optional(),
|
|
437
533
|
transitionId: z.string().optional(),
|
|
534
|
+
bindings: z.array(stateTreeBindingSchema).optional().describe('Task output-to-input bindings. Declares data flow between tasks (requires C++ plugin support — not yet implemented).'),
|
|
438
535
|
}).passthrough().default({}).describe('Operation payload. Selectors support stateId/statePath, editorNodeId, and transitionId.'),
|
|
439
536
|
validate_only: z.boolean().default(false).describe('Validate and compile without changing the asset.'),
|
|
440
537
|
timeout_seconds: z.number().positive().optional().describe('Timeout in seconds for the subsystem call. Default 90. Complex payloads may need more time.'),
|
|
@@ -451,9 +548,12 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
451
548
|
const { asset_path, operation, payload, validate_only, timeout_seconds } = args;
|
|
452
549
|
const warnings = [];
|
|
453
550
|
validateTransitionTargets(payload, warnings);
|
|
551
|
+
validateBindingTaskReferences(payload, warnings);
|
|
454
552
|
const normWarnings = [];
|
|
455
553
|
const normalizedPayload = normalizePayloadPaths(payload ?? {}, normWarnings);
|
|
456
554
|
warnings.push(...normWarnings);
|
|
555
|
+
// Validate nodeStructType path formats after normalization
|
|
556
|
+
validateNodeStructTypes(normalizedPayload, warnings);
|
|
457
557
|
const timeoutMs = (timeout_seconds ?? 90) * 1000;
|
|
458
558
|
const parsed = await callSubsystemJson('ModifyStateTree', {
|
|
459
559
|
AssetPath: asset_path,
|
|
@@ -464,8 +564,9 @@ export function registerSchemaAndAiAuthoringTools({ server, callSubsystemJson, j
|
|
|
464
564
|
const result = warnings.length > 0
|
|
465
565
|
? { ...parsed, warnings }
|
|
466
566
|
: parsed;
|
|
467
|
-
const
|
|
468
|
-
|
|
567
|
+
const allWarnings = [...normWarnings, ...warnings.filter(w => !normWarnings.includes(w))];
|
|
568
|
+
const extraContent = allWarnings.length > 0
|
|
569
|
+
? [{ type: 'text', text: allWarnings.join('\n') }]
|
|
469
570
|
: undefined;
|
|
470
571
|
return jsonToolSuccess(result, { extraContent });
|
|
471
572
|
}
|
package/dist/ue-client.js
CHANGED
|
@@ -133,6 +133,12 @@ export class UEClient {
|
|
|
133
133
|
if (result.timedOut) {
|
|
134
134
|
details.push(`timeoutMs=${this.timeoutMs}`);
|
|
135
135
|
}
|
|
136
|
+
if (typeof result.responseBody === 'string' && result.responseBody.length > 0) {
|
|
137
|
+
const truncated = result.responseBody.length > 500
|
|
138
|
+
? result.responseBody.slice(0, 500) + '…'
|
|
139
|
+
: result.responseBody;
|
|
140
|
+
details.push(`body=${truncated}`);
|
|
141
|
+
}
|
|
136
142
|
return `Failed to call ${method} on BlueprintExtractorSubsystem (${details.join(', ')})`;
|
|
137
143
|
}
|
|
138
144
|
async rawCall(objectPath, functionName, parameters, timeoutMs) {
|
|
@@ -169,10 +175,13 @@ export class UEClient {
|
|
|
169
175
|
response: null,
|
|
170
176
|
status: res.status,
|
|
171
177
|
error: errorDetail,
|
|
178
|
+
responseBody,
|
|
172
179
|
};
|
|
173
180
|
}
|
|
181
|
+
const text = await res.text();
|
|
174
182
|
return {
|
|
175
|
-
response:
|
|
183
|
+
response: JSON.parse(text),
|
|
184
|
+
responseBody: text,
|
|
176
185
|
};
|
|
177
186
|
}
|
|
178
187
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blueprint-extractor-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"build": "tsc",
|
|
40
40
|
"start": "node dist/index.js",
|
|
41
41
|
"test": "npm run test:unit && npm run test:stdio",
|
|
42
|
-
"test:unit": "vitest run tests/ue-client.test.ts tests/project-controller.test.ts tests/animation-authoring.test.ts tests/automation-controller.test.ts tests/automation-runs.test.ts tests/blueprint-authoring.test.ts tests/capture.test.ts tests/commonui-button-style.test.ts tests/compactor.test.ts tests/data-and-input.test.ts tests/extraction-tools.test.ts tests/formatting.test.ts tests/helper-utils.test.ts tests/import-jobs.test.ts tests/material-authoring.test.ts tests/material-instance.test.ts tests/project-control.test.ts tests/schema-and-ai-authoring.test.ts tests/tables-and-curves.test.ts tests/tool-results.test.ts tests/utility-tools.test.ts tests/verification.test.ts tests/widget-animation-authoring.test.ts tests/widget-extraction.test.ts tests/widget-structure.test.ts tests/widget-verification.test.ts tests/window-ui.test.ts tests/server-bootstrap.test.ts tests/server-contract.test.ts tests/schema-validation.test.ts tests/server-config.test.ts tests/subsystem.test.ts",
|
|
42
|
+
"test:unit": "vitest run tests/ue-client.test.ts tests/project-controller.test.ts tests/animation-authoring.test.ts tests/automation-controller.test.ts tests/automation-runs.test.ts tests/blueprint-authoring.test.ts tests/capture.test.ts tests/commonui-button-style.test.ts tests/compactor.test.ts tests/data-and-input.test.ts tests/extraction-tools.test.ts tests/formatting.test.ts tests/helper-utils.test.ts tests/import-jobs.test.ts tests/live-coding.test.ts tests/material-authoring.test.ts tests/material-instance.test.ts tests/project-control.test.ts tests/project-resolution.test.ts tests/schema-and-ai-authoring.test.ts tests/tables-and-curves.test.ts tests/tool-help.test.ts tests/tool-results.test.ts tests/utility-tools.test.ts tests/verification.test.ts tests/widget-animation-authoring.test.ts tests/widget-extraction.test.ts tests/widget-structure.test.ts tests/widget-verification.test.ts tests/window-ui.test.ts tests/server-bootstrap.test.ts tests/server-contract.test.ts tests/schema-validation.test.ts tests/server-config.test.ts tests/subsystem.test.ts",
|
|
43
43
|
"test:stdio": "npm run build && vitest run tests/stdio.integration.test.ts",
|
|
44
44
|
"test:live": "npm run build && vitest run tests/live.e2e.test.ts",
|
|
45
45
|
"test:pack-smoke": "node tests/pack-smoke.mjs",
|