ue-mcp 1.3.1-beta.1 → 1.3.1-beta.2

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/action-class.js +46 -2
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/tool-counts.json +2 -2
  5. package/package.json +1 -1
  6. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +41 -0
  7. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +45 -0
  8. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +28 -0
  9. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +64 -0
  10. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +11 -0
  11. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +55 -0
  12. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +12 -0
  13. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_UV.cpp +31 -0
  14. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_Depth.cpp +17 -4
  15. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_MetaSound.cpp +5 -1
  16. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_MetaSoundRead.cpp +6 -1
  17. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +32 -1
  18. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -6
  19. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +14 -0
  20. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +87 -5
  21. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +9 -0
  22. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +41 -2
  23. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EpicHandlers.cpp +22 -0
  24. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +75 -7
  25. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +12 -0
  26. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_Input.cpp +19 -4
  27. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_Perception.cpp +17 -0
  28. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +13 -0
  29. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +5 -0
  30. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1 -1
  31. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +6 -0
  32. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +37 -0
  33. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +64 -1
  34. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -0
  35. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1 -1
  36. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +3 -3
  37. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +9 -0
  38. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +54 -0
  39. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +30 -0
  40. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers_Compile.cpp +17 -2
  41. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +65 -3
  42. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +46 -0
  43. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +55 -0
  44. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +1 -1
package/README.md CHANGED
@@ -177,7 +177,7 @@ Details: [Guards](https://ue-mcp.com/docs/plugins-guards/) and [Handler Conventi
177
177
 
178
178
  | | |
179
179
  |---|---|
180
- | **Unreal Engine** | 5.4-5.8 (Windows), 5.6+ (Linux and macOS). `epic` category needs 5.8+. |
180
+ | **Unreal Engine** | 5.4-5.8 (Windows), 5.6+ (Linux and macOS). Compiled and verified on 5.7 and 5.8; 5.4 to 5.6 are version-gated in source but not built here. `epic` category needs 5.8+. |
181
181
  | **Node.js** | 18 or newer |
182
182
  | **UE plugins** | `PythonScriptPlugin` (ships with the engine); `init` enables the rest for you |
183
183
  | **C++ toolchain** | Required - the bridge is a C++ plugin and compiles on first editor launch. On Windows that means Visual Studio with the C++ workload; on macOS, Xcode command line tools. |
@@ -267,7 +267,13 @@ const OVERRIDES = {
267
267
  // terrain, but a file appearing on disk is an external side effect and the
268
268
  // taxonomy above counts that as a mutation.
269
269
  "landscape.export_heightmap": "mutate",
270
- "audio.extract_pcm": "mutate",
270
+ // Not one of the exports above, despite sitting next to them here since it
271
+ // was written. It decodes a USoundWave's imported audio into memory and
272
+ // returns the samples base64-encoded in the response: no intermediate file,
273
+ // no path parameter to write one to, nothing dirtied and nothing saved. It
274
+ // was classified `mutate` by association with the file-writing exports, and
275
+ // the handler body settles it the other way.
276
+ "audio.extract_pcm": "read",
271
277
  // Rebuilds the full-text index in the editor's own database.
272
278
  "asset.reindex_fts": "mutate",
273
279
  // Loads, rewrites and saves the packages that reference a set of
@@ -288,6 +294,31 @@ const OVERRIDES = {
288
294
  // The verb says read; what it can do says mutate, and the gate follows what
289
295
  // it can do.
290
296
  "gas.get_live_attribute_value": "mutate",
297
+ // Runs an EQS query through FEnvQueryManager::RunInstantQuery against an
298
+ // already-spawned querier and serialises the scored items. `run` is a mutate
299
+ // verb and this one does not: no spawn, no Modify, no save, nothing written
300
+ // back to the query asset or the world. Landing it in the wrong editor
301
+ // returns the wrong scores and changes nothing.
302
+ "gameplay.run_eqs_query": "read",
303
+ // ── Reads whose name leads with a subsystem, not a verb ─────────────
304
+ // The lexicon only trusts a read verb in the leading segment, so these say
305
+ // out loud what they read. Each one was confirmed against its handler: they
306
+ // open an asset, walk a graph or a document, and write nothing back.
307
+ "audio.metasound_get_graph": "read",
308
+ "audio.metasound_read_document": "read",
309
+ "audio.metasound_list_node_classes": "read",
310
+ "audio.metasound_list_node_pins": "read",
311
+ "audio.metasound_list_connections": "read",
312
+ "audio.metasound_list_variables": "read",
313
+ "audio.metasound_search_nodes": "read",
314
+ "audio.metasound_inspect_node": "read",
315
+ // Runs the MetaSound builder's own validation over a document and reports
316
+ // the errors. It builds nothing and saves nothing.
317
+ "audio.metasound_validate": "read",
318
+ "audio.cue_get_graph": "read",
319
+ // Answers whether a named weightmap layer is present on a landscape. The
320
+ // `exists` question never creates the layer it asks about.
321
+ "landscape.layer_exists": "read",
291
322
  // ── The workflow journal and the skill packs ────────────────────────
292
323
  // Neither reaches a bridge, so nothing here can land in the wrong EDITOR.
293
324
  // Both are still per project: the journal file is keyed by absolute project
@@ -333,7 +364,20 @@ export function classifyActionClass(tool, action) {
333
364
  const segments = action.toLowerCase().split(/[._]/).filter(Boolean);
334
365
  if (segments.some((s) => MUTATE_VERBS.has(s)))
335
366
  return { class: "mutate", source: "lexicon" };
336
- if (segments.some((s) => READ_VERBS.has(s)))
367
+ // A read verb only settles the question from the FRONT of the name. Anywhere
368
+ // else it is a noun as often as a verb, and the direction of that mistake is
369
+ // the dangerous one: `wire_rvt_sample` was a mutation that classified as a
370
+ // read on the strength of `sample`, and untargeted dispatch would have let it
371
+ // edit whichever editor happened to be active. It reached a human review
372
+ // rather than a release, and was renamed to `add_rvt_sampler`, which is a fix
373
+ // to one name and not to the rule that produced it.
374
+ //
375
+ // So a trailing read verb now decides nothing and falls through to
376
+ // `unresolved`, which is gated exactly like a mutation and which the drift
377
+ // guard fails on. The cost is an override line for every genuine read whose
378
+ // name leads with a subsystem instead of a verb; those are below, each saying
379
+ // what it reads. The alternative costs a silent wrong-editor write.
380
+ if (READ_VERBS.has(segments[0] ?? ""))
337
381
  return { class: "read", source: "lexicon" };
338
382
  // Epic's wrapped engine tools are injected at runtime from a live catalog, so
339
383
  // no build-time guard can enumerate them: enrichment can invent whole
@@ -1 +1 @@
1
- {"version":3,"file":"action-class.js","sourceRoot":"","sources":["../src/action-class.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action-class.js","sourceRoot":"","sources":["../src/action-class.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,6 @@
31
31
  },
32
32
  "nativeToolsets": 52,
33
33
  "nativeToolActions": 830,
34
- "generatedAt": "2026-08-30T02:34:19.266Z",
35
- "version": "1.3.1-beta.1"
34
+ "generatedAt": "2026-08-30T11:29:36.512Z",
35
+ "version": "1.3.1-beta.2"
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ue-mcp",
3
- "version": "1.3.1-beta.1",
3
+ "version": "1.3.1-beta.2",
4
4
  "description": "Unreal Engine MCP server - 24 tools, 1090+ actions for AI-driven editor control",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -655,6 +655,13 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRetargeter(const TSharedPt
655
655
  PreparedPose = MoveTemp(Pose);
656
656
  }
657
657
 
658
+ // Read before the transaction reassigns them. A rollback payload built after
659
+ // the write would carry the values this call just installed.
660
+ const UIKRigDefinition* PreviousSourceRig = Controller->GetIKRig(ERetargetSourceOrTarget::Source);
661
+ const UIKRigDefinition* PreviousTargetRig = Controller->GetIKRig(ERetargetSourceOrTarget::Target);
662
+ USkeletalMesh* PreviousSourcePreview = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Source);
663
+ USkeletalMesh* PreviousTargetPreview = Controller->GetPreviewMesh(ERetargetSourceOrTarget::Target);
664
+
658
665
  bool bMutationFailed = false;
659
666
  FString MutationError;
660
667
  bool bAddedDefaultOps = false;
@@ -948,6 +955,40 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRetargeter(const TSharedPt
948
955
  Result->SetObjectField(TEXT("pose"), BuildPoseJson(Controller, Pose.Side, Pose.Name, Pose.bAutoAlignAll));
949
956
  }
950
957
 
958
+ // Only the assignment half of this action is reversible: the rigs and preview
959
+ // meshes each had a previous asset that a replay of this same action can
960
+ // point back at. ensureDefaultOps is forced off in that replay so a pure
961
+ // assignment restore cannot install an op stack of its own.
962
+ const bool bAssignmentsOnly = !bAddedDefaultOps && !bHasAutoMap
963
+ && PreparedMappings.IsEmpty() && !PreparedPose.IsSet();
964
+ const bool bAssignedAnything = bHasSourceRig || bHasTargetRig || bHasSourcePreview || bHasTargetPreview;
965
+ const bool bAssignmentsRestorable =
966
+ (!bHasSourceRig || PreviousSourceRig != nullptr)
967
+ && (!bHasTargetRig || PreviousTargetRig != nullptr)
968
+ && (!bHasSourcePreview || PreviousSourcePreview != nullptr)
969
+ && (!bHasTargetPreview || PreviousTargetPreview != nullptr);
970
+ if (bAssignmentsOnly && bAssignedAnything && bAssignmentsRestorable)
971
+ {
972
+ auto Payload = MakeShared<FJsonObject>();
973
+ Payload->SetStringField(TEXT("retargeterPath"), Retargeter->GetPathName());
974
+ Payload->SetBoolField(TEXT("ensureDefaultOps"), false);
975
+ if (bHasSourceRig) Payload->SetStringField(TEXT("sourceRig"), PreviousSourceRig->GetPathName());
976
+ if (bHasTargetRig) Payload->SetStringField(TEXT("targetRig"), PreviousTargetRig->GetPathName());
977
+ if (bHasSourcePreview) Payload->SetStringField(TEXT("sourcePreviewMesh"), PreviousSourcePreview->GetPathName());
978
+ if (bHasTargetPreview) Payload->SetStringField(TEXT("targetPreviewMesh"), PreviousTargetPreview->GetPathName());
979
+ MCPSetRollback(Result, TEXT("configure_ik_retargeter"), Payload);
980
+ }
981
+ else
982
+ {
983
+ TArray<FString> Causes;
984
+ if (bAddedDefaultOps) Causes.Add(TEXT("a default retarget op stack was installed and no action removes a retarget op"));
985
+ if (bHasAutoMap || !PreparedMappings.IsEmpty()) Causes.Add(TEXT("chain mappings were rewritten over mappings this call did not capture"));
986
+ if (PreparedPose.IsSet()) Causes.Add(TEXT("a retarget pose was created, reset or realigned and no action deletes a pose"));
987
+ if (!bAssignmentsRestorable) Causes.Add(TEXT("a rig or preview mesh this call assigned had none before, and configure_ik_retargeter takes asset paths so it cannot clear one back to unset"));
988
+ if (Causes.IsEmpty()) Causes.Add(TEXT("this call assigned no rig or preview mesh, so there is no previous asset for a replay to point back at"));
989
+ MCPSetNoRollback(Result, FString::Join(Causes, TEXT("; ")) + TEXT("."));
990
+ }
991
+
951
992
  auto Validation = MakeShared<FJsonObject>();
952
993
  Validation->SetBoolField(TEXT("ran"), bValidationRan);
953
994
  Validation->SetBoolField(TEXT("initialized"), bValidationInitialized);
@@ -620,6 +620,11 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRig(const TSharedPtr<FJson
620
620
  const int32 BeforeGoalCount = Controller->GetAllGoals().Num();
621
621
  const int32 BeforeSolverCount = Controller->GetNumSolvers();
622
622
  const TSet<FName> BeforeExcluded(Skeleton.ExcludedBones);
623
+ // Read before the transaction writes over them, because the rollback payload
624
+ // below has to carry the bones this rig used to name, not the ones it names
625
+ // after this call succeeds.
626
+ const FName BeforeRetargetRoot = Controller->GetRetargetRoot();
627
+ const FName BeforeRootMotionBone = Controller->GetRootMotionBone();
623
628
  UPackage* Package = Rig->GetOutermost();
624
629
  const bool bWasDirty = Package && Package->IsDirty();
625
630
  bool bFailed = false;
@@ -892,6 +897,46 @@ TSharedPtr<FJsonValue> FAnimationHandlers::ConfigureIKRig(const TSharedPtr<FJson
892
897
  Result->SetNumberField(TEXT("fullBodyIKSolverIndex"), SolverIndex);
893
898
  Result->SetBoolField(TEXT("fullBodyIKSolverCreated"), Controller->GetNumSolvers() > BeforeSolverCount);
894
899
  }
900
+ // Only the settings half of this action is reversible. retargetRoot,
901
+ // rootMotionBone and bone exclusions each had a previous value that a replay
902
+ // of this same action can write back. The rest of what it does is creation,
903
+ // and creation is what no registered action can take back.
904
+ const bool bSettingsOnly = !bAutoRetarget && Chains.IsEmpty() && !FullBody.bPresent
905
+ && (RetargetRoot.IsSet() || RootMotionBone.IsSet() || !Exclusions.IsEmpty());
906
+ const bool bBonesRestorable =
907
+ (!RetargetRoot.IsSet() || !BeforeRetargetRoot.IsNone())
908
+ && (!RootMotionBone.IsSet() || !BeforeRootMotionBone.IsNone());
909
+ if (bSettingsOnly && bBonesRestorable)
910
+ {
911
+ TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
912
+ Payload->SetStringField(TEXT("rigPath"), Rig->GetPathName());
913
+ if (RetargetRoot.IsSet()) Payload->SetStringField(TEXT("retargetRoot"), BeforeRetargetRoot.ToString());
914
+ if (RootMotionBone.IsSet()) Payload->SetStringField(TEXT("rootMotionBone"), BeforeRootMotionBone.ToString());
915
+ if (!Exclusions.IsEmpty())
916
+ {
917
+ TArray<TSharedPtr<FJsonValue>> PreviousExclusions;
918
+ for (const FExclusionRequest& Request : Exclusions)
919
+ {
920
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
921
+ Entry->SetStringField(TEXT("bone"), Request.Bone.ToString());
922
+ Entry->SetBoolField(TEXT("excluded"), BeforeExcluded.Contains(Request.Bone));
923
+ PreviousExclusions.Add(MakeShared<FJsonValueObject>(Entry));
924
+ }
925
+ Payload->SetArrayField(TEXT("exclusions"), PreviousExclusions);
926
+ }
927
+ MCPSetRollback(Result, TEXT("configure_ik_rig"), Payload);
928
+ }
929
+ else
930
+ {
931
+ TArray<FString> Causes;
932
+ if (bAutoRetarget) Causes.Add(TEXT("autoSetup replaced the rig's retarget definition and no action restores the previous one"));
933
+ if (!Chains.IsEmpty()) Causes.Add(TEXT("retarget chains were upserted and no action removes a chain"));
934
+ if (FullBody.bPresent) Causes.Add(TEXT("IK goals and a Full Body IK solver were upserted and no action removes a goal or a solver"));
935
+ if (!bBonesRestorable) Causes.Add(TEXT("retargetRoot or rootMotionBone had no previous bone, and configure_ik_rig takes a bone name so it cannot clear one back to none"));
936
+ if (Causes.IsEmpty()) Causes.Add(TEXT("this call changed nothing whose previous value a replay could write back"));
937
+ MCPSetNoRollback(Result, FString::Join(Causes, TEXT("; ")) + TEXT("."));
938
+ }
939
+
895
940
  TArray<TSharedPtr<FJsonValue>> WarningValues;
896
941
  for (const FString& Warning : Warnings) WarningValues.Add(MakeShared<FJsonValueString>(Warning));
897
942
  Result->SetArrayField(TEXT("warnings"), WarningValues);
@@ -730,6 +730,34 @@ TSharedPtr<FJsonValue> FAnimationHandlers::AnalyzeAnimation(const TSharedPtr<FJs
730
730
  Result->SetStringField(TEXT("outputDirectory"), OutputDirectory);
731
731
  Result->SetStringField(TEXT("manifestPath"), ManifestPath);
732
732
  Result->SetStringField(TEXT("samplesPath"), SamplesPath);
733
+
734
+ // The only effect this call has outside the response, and it happens
735
+ // only when the caller named a directory. The sequence, its skeleton
736
+ // and the preview mesh are read and never written: the pose evaluation
737
+ // runs against a stack-local FCompactPose.
738
+ //
739
+ // No inverse is named because deleting a report this same call
740
+ // regenerates on demand is not a meaningful undo, and the bridge has no
741
+ // delete-file action to name anyway. The files are safe to leave: the
742
+ // handler refuses to run when either already exists, so a replay cannot
743
+ // overwrite a report somebody is reading.
744
+ MCPSetNoRollback(Result, FString::Printf(TEXT(
745
+ "Wrote the analysis report '%s' and its samples file '%s', and changed no animation, skeleton or "
746
+ "asset state. An analysis report is an output artifact rather than project state, and this call "
747
+ "regenerates it on demand, so no inverse is named. Nothing in the bridge deletes a file outside "
748
+ "the content browser."), *ManifestPath, *SamplesPath));
749
+ MCPSetCreated(Result);
750
+ }
751
+ else
752
+ {
753
+ // The whole call is a read on this path: it opens the sequence, samples
754
+ // poses into a local buffer and reports. Said out loud because a caller
755
+ // reading a body with neither a rollback nor a note cannot tell that
756
+ // from a handler that forgot one.
757
+ MCPSetNoRollback(Result, TEXT(
758
+ "Read the sequence and reported on it. No file was written and no asset, skeleton or world state "
759
+ "changed, so there is nothing to undo."));
760
+ Result->SetBoolField(TEXT("unchanged"), true);
733
761
  }
734
762
  return MCPResult(Result);
735
763
  }
@@ -2566,6 +2566,15 @@ TSharedPtr<FJsonValue> FAssetHandlers::SaveAsset(const TSharedPtr<FJsonObject>&
2566
2566
  Result->SetStringField(TEXT("path"), AssetPath);
2567
2567
  Result->SetBoolField(TEXT("force"), bForce);
2568
2568
 
2569
+ // The dirty flag is the only honest answer to "did this call change
2570
+ // anything", and it has to be read before the save, because afterwards
2571
+ // every package is clean and a save that wrote nothing is
2572
+ // indistinguishable from one that flushed an edit.
2573
+ UObject* PreSaveAsset = MCPLoadAssetObject(AssetPath);
2574
+ UPackage* PreSavePackage = PreSaveAsset ? PreSaveAsset->GetOutermost() : nullptr;
2575
+ const bool bWasDirty = PreSavePackage && PreSavePackage->IsDirty();
2576
+ Result->SetBoolField(TEXT("wasDirty"), bWasDirty);
2577
+
2569
2578
  bool bSuccess = false;
2570
2579
  if (bForce)
2571
2580
  {
@@ -2598,6 +2607,35 @@ TSharedPtr<FJsonValue> FAssetHandlers::SaveAsset(const TSharedPtr<FJsonObject>&
2598
2607
  }
2599
2608
  }
2600
2609
  Result->SetBoolField(TEXT("success"), bSuccess);
2610
+
2611
+ // A clean package had nothing to flush. Without force the save was a
2612
+ // no-op and says so; with force the file was rewritten anyway, which is
2613
+ // a write to disk even though no edit was pending, so it does not claim
2614
+ // to have changed nothing.
2615
+ if (bWasDirty)
2616
+ {
2617
+ MCPSetUpdated(Result);
2618
+ }
2619
+ else if (bForce && bSuccess)
2620
+ {
2621
+ MCPSetUpdated(Result);
2622
+ Result->SetStringField(TEXT("note"),
2623
+ TEXT("The package was not dirty. force=true rewrote the file on disk anyway; without it this call would have written nothing."));
2624
+ }
2625
+ else
2626
+ {
2627
+ Result->SetBoolField(TEXT("updated"), false);
2628
+ Result->SetBoolField(TEXT("unchanged"), true);
2629
+ if (bSuccess)
2630
+ {
2631
+ // Only when the save itself reported success. A failed save
2632
+ // wrote nothing either, and telling that caller the package was
2633
+ // simply clean would hide the failure behind a no-op.
2634
+ Result->SetStringField(TEXT("note"),
2635
+ TEXT("The package was not dirty, so nothing was written. Pass force=true to write it regardless."));
2636
+ }
2637
+ }
2638
+
2601
2639
  Result->SetBoolField(TEXT("rollbackPossible"), false);
2602
2640
  Result->SetStringField(TEXT("rollbackNote"),
2603
2641
  TEXT("A save overwrites the file on disk with what is in memory. The previous file contents are gone and the ")
@@ -2613,10 +2651,36 @@ TSharedPtr<FJsonValue> FAssetHandlers::SaveAsset(const TSharedPtr<FJsonObject>&
2613
2651
  {
2614
2652
  return MCPError(TEXT("'force' requires an assetPath - it forces one package to disk. To flush everything, use asset(save_all_dirty), which reports exactly which packages were written."));
2615
2653
  }
2654
+ // Counted before the sweep for the same reason as the single-asset
2655
+ // branch: SaveDirectory is dirty-only, so with nothing dirty it writes
2656
+ // nothing, and afterwards there is no way to tell that from a sweep
2657
+ // that flushed a hundred packages.
2658
+ TArray<UPackage*> DirtyBefore;
2659
+ FEditorFileUtils::GetDirtyContentPackages(DirtyBefore);
2660
+ FEditorFileUtils::GetDirtyWorldPackages(DirtyBefore);
2661
+ int32 DirtyUnderGame = 0;
2662
+ for (UPackage* Package : DirtyBefore)
2663
+ {
2664
+ if (Package && Package->IsDirty() && Package->GetName().StartsWith(TEXT("/Game/")))
2665
+ {
2666
+ ++DirtyUnderGame;
2667
+ }
2668
+ }
2669
+
2616
2670
  UEditorAssetLibrary::SaveDirectory(TEXT("/Game"));
2617
2671
  auto Result = MCPSuccess();
2618
2672
  Result->SetBoolField(TEXT("success"), true);
2619
2673
  Result->SetStringField(TEXT("message"), TEXT("All modified assets under /Game saved (dirty only). Use save_all_dirty for a per-package report."));
2674
+ Result->SetNumberField(TEXT("dirtyPackagesBefore"), DirtyUnderGame);
2675
+ if (DirtyUnderGame > 0)
2676
+ {
2677
+ MCPSetUpdated(Result);
2678
+ }
2679
+ else
2680
+ {
2681
+ Result->SetBoolField(TEXT("updated"), false);
2682
+ Result->SetBoolField(TEXT("unchanged"), true);
2683
+ }
2620
2684
  Result->SetBoolField(TEXT("rollbackPossible"), false);
2621
2685
  Result->SetStringField(TEXT("rollbackNote"),
2622
2686
  TEXT("A save overwrites the files on disk with what is in memory. The previous file contents are gone and the ")
@@ -474,5 +474,16 @@ TSharedPtr<FJsonValue> FAssetHandlers::FixupRedirectors(const TSharedPtr<FJsonOb
474
474
  Result->SetNumberField(TEXT("deletedRedirectorCount"), ConfirmedDeleted);
475
475
  Result->SetArrayField(TEXT("redirectors"), RedirectorStatuses);
476
476
  MCPSetUpdated(Result);
477
+ // Two irreversible writes, neither of which the bridge can undo. The
478
+ // referencer packages were saved over their previous bytes, and no copy of
479
+ // those was kept; the redirector packages were deleted from disk once
480
+ // nothing pointed at them. Restoring the old state needs a call that
481
+ // recreates an ObjectRedirector at a vacated path and a call that rewinds a
482
+ // package to its pre-save contents, and neither exists.
483
+ MCPSetNoRollback(Result, FString::Printf(
484
+ TEXT("Rewrote and saved %d referencer package(s) over their previous contents and deleted %d redirector ")
485
+ TEXT("package(s). Undoing this needs an action that recreates an ObjectRedirector at an old path and one that ")
486
+ TEXT("restores a package's pre-save bytes; neither exists, and no copy of the overwritten packages was kept."),
487
+ SavedCount, ConfirmedDeleted));
477
488
  return MCPResult(Result);
478
489
  }
@@ -318,6 +318,13 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
318
318
  {
319
319
  if (SM->Sockets[i] && SM->Sockets[i]->SocketName == FName(*SocketName))
320
320
  {
321
+ // Read before the removal. After RemoveAt the array no longer
322
+ // holds the socket, and a transform captured then would put
323
+ // back a socket at the origin rather than where this one was.
324
+ const FVector PrevLoc = SM->Sockets[i]->RelativeLocation;
325
+ const FRotator PrevRot = SM->Sockets[i]->RelativeRotation;
326
+ const FVector PrevScale = SM->Sockets[i]->RelativeScale;
327
+
321
328
  SM->Modify();
322
329
  SM->Sockets.RemoveAt(i);
323
330
  SM->MarkPackageDirty();
@@ -327,6 +334,18 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
327
334
  Result->SetStringField(TEXT("meshType"), TEXT("StaticMesh"));
328
335
  Result->SetStringField(TEXT("source"), TEXT("mesh"));
329
336
  Result->SetBoolField(TEXT("deleted"), true);
337
+
338
+ // add_socket recreates it with the transform it had.
339
+ // onConflict=update so the restore lands whether or not a
340
+ // socket of that name exists again by the time it runs.
341
+ TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
342
+ Payload->SetStringField(TEXT("assetPath"), AssetPath);
343
+ Payload->SetStringField(TEXT("socketName"), SocketName);
344
+ Payload->SetObjectField(TEXT("relativeLocation"), MCPVec3ToJsonObject(PrevLoc));
345
+ Payload->SetObjectField(TEXT("relativeRotation"), MCPRotatorToJsonObject(PrevRot));
346
+ Payload->SetObjectField(TEXT("relativeScale"), MCPVec3ToJsonObject(PrevScale));
347
+ Payload->SetStringField(TEXT("onConflict"), TEXT("update"));
348
+ MCPSetRollback(Result, TEXT("add_socket"), Payload);
330
349
  return MCPResult(Result);
331
350
  }
332
351
  }
@@ -346,6 +365,14 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
346
365
  {
347
366
  if (Sockets[i] && Sockets[i]->SocketName == FName(*SocketName))
348
367
  {
368
+ // Captured before the removal, and the bone with it: a socket
369
+ // restored onto the wrong bone is attached to the wrong part of
370
+ // the skeleton however right its transform is.
371
+ const FVector PrevLoc = Sockets[i]->RelativeLocation;
372
+ const FRotator PrevRot = Sockets[i]->RelativeRotation;
373
+ const FVector PrevScale = Sockets[i]->RelativeScale;
374
+ const FString PrevBone = Sockets[i]->BoneName.ToString();
375
+
349
376
  SKM->Modify();
350
377
  Sockets[i]->Modify();
351
378
  Sockets.RemoveAt(i);
@@ -357,6 +384,16 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
357
384
  Result->SetStringField(TEXT("meshType"), TEXT("SkeletalMesh"));
358
385
  Result->SetStringField(TEXT("source"), TEXT("mesh"));
359
386
  Result->SetBoolField(TEXT("deleted"), true);
387
+
388
+ TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
389
+ Payload->SetStringField(TEXT("assetPath"), AssetPath);
390
+ Payload->SetStringField(TEXT("socketName"), SocketName);
391
+ Payload->SetStringField(TEXT("boneName"), PrevBone);
392
+ Payload->SetObjectField(TEXT("relativeLocation"), MCPVec3ToJsonObject(PrevLoc));
393
+ Payload->SetObjectField(TEXT("relativeRotation"), MCPRotatorToJsonObject(PrevRot));
394
+ Payload->SetObjectField(TEXT("relativeScale"), MCPVec3ToJsonObject(PrevScale));
395
+ Payload->SetStringField(TEXT("onConflict"), TEXT("update"));
396
+ MCPSetRollback(Result, TEXT("add_socket"), Payload);
360
397
  return MCPResult(Result);
361
398
  }
362
399
  }
@@ -375,6 +412,11 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
375
412
  {
376
413
  if (Skel->Sockets[i] && Skel->Sockets[i]->SocketName == FName(*SocketName))
377
414
  {
415
+ const FVector PrevLoc = Skel->Sockets[i]->RelativeLocation;
416
+ const FRotator PrevRot = Skel->Sockets[i]->RelativeRotation;
417
+ const FVector PrevScale = Skel->Sockets[i]->RelativeScale;
418
+ const FString PrevBone = Skel->Sockets[i]->BoneName.ToString();
419
+
378
420
  Skel->Modify();
379
421
  Skel->Sockets[i]->Modify();
380
422
  Skel->Sockets.RemoveAt(i);
@@ -385,6 +427,19 @@ TSharedPtr<FJsonValue> FAssetHandlers::RemoveSocket(const TSharedPtr<FJsonObject
385
427
  Result->SetStringField(TEXT("meshType"), TEXT("Skeleton"));
386
428
  Result->SetStringField(TEXT("source"), TEXT("skeleton"));
387
429
  Result->SetBoolField(TEXT("deleted"), true);
430
+
431
+ // The same assetPath routes add_socket back to the Skeleton's
432
+ // own socket array, so the rig-level socket is restored where it
433
+ // lived rather than on a mesh.
434
+ TSharedPtr<FJsonObject> Payload = MakeShared<FJsonObject>();
435
+ Payload->SetStringField(TEXT("assetPath"), AssetPath);
436
+ Payload->SetStringField(TEXT("socketName"), SocketName);
437
+ Payload->SetStringField(TEXT("boneName"), PrevBone);
438
+ Payload->SetObjectField(TEXT("relativeLocation"), MCPVec3ToJsonObject(PrevLoc));
439
+ Payload->SetObjectField(TEXT("relativeRotation"), MCPRotatorToJsonObject(PrevRot));
440
+ Payload->SetObjectField(TEXT("relativeScale"), MCPVec3ToJsonObject(PrevScale));
441
+ Payload->SetStringField(TEXT("onConflict"), TEXT("update"));
442
+ MCPSetRollback(Result, TEXT("add_socket"), Payload);
388
443
  return MCPResult(Result);
389
444
  }
390
445
  }
@@ -268,6 +268,18 @@ TSharedPtr<FJsonValue> FAssetHandlers::CreateSubobject(const TSharedPtr<FJsonObj
268
268
  OwnerAsset->PostEditChange();
269
269
  OwnerPackage->MarkPackageDirty();
270
270
 
271
+ // The new object lives inside a package that existed before this call, and
272
+ // nothing in this surface takes one back out: delete_asset would destroy
273
+ // the owning asset along with everything else in it, which is a larger loss
274
+ // than the thing being undone. Undoing this needs a remove_subobject that
275
+ // does not exist, so the property values overwritten on a reused subobject
276
+ // are not recoverable through the bridge either.
277
+ MCPSetNoRollback(Result, FString::Printf(
278
+ TEXT("Created or reconfigured the subobject '%s' inside the existing package '%s'. No action removes a named ")
279
+ TEXT("subobject from a package or restores its previous property values; delete_asset would destroy the whole ")
280
+ TEXT("owning asset rather than undo this."),
281
+ *Name, *OwnerPackage->GetName()));
282
+
271
283
  // Saving in the same call is what closes the garbage-collection window the
272
284
  // issue is about: after this the subobject is an export on disk, so a later
273
285
  // call resolves it by path even if the in-memory copy is collected.
@@ -2743,6 +2743,10 @@ TSharedPtr<FJsonValue> FAssetHandlers::ExportUvLayout(const TSharedPtr<FJsonObje
2743
2743
  TEXT("The %dx%d UV layout for '%s' could not be PNG-encoded."), Size, Size, *Target.AssetPath));
2744
2744
  }
2745
2745
  const TArray64<uint8> PngData = PngWrapper->GetCompressed(100);
2746
+ // Asked before the write, because afterwards every export looks like a
2747
+ // first one. A caller rerunning this over a directory it is collecting
2748
+ // pictures in wants to know which of them it just replaced.
2749
+ const bool bOverwroteExistingFile = FPaths::FileExists(OutputPath);
2746
2750
  if (!FFileHelper::SaveArrayToFile(PngData, *OutputPath))
2747
2751
  {
2748
2752
  return MCPError(FString::Printf(
@@ -2764,6 +2768,33 @@ TSharedPtr<FJsonValue> FAssetHandlers::ExportUvLayout(const TSharedPtr<FJsonObje
2764
2768
  TEXT("White wire = UV triangle edges. Yellow border = the unit square. Grey grid = quarters. Coloured fill ")
2765
2769
  TEXT("= one hue per UV island. Red fill = two or more triangles on the same texel, which is what breaks ")
2766
2770
  TEXT("a lightmap bake."));
2771
+
2772
+ // The render always runs and the file is always written, so the honest
2773
+ // answer to "did this change anything" is whether a picture was already
2774
+ // sitting at that path.
2775
+ if (bOverwroteExistingFile)
2776
+ {
2777
+ MCPSetExisted(Result);
2778
+ MCPSetUpdated(Result);
2779
+ }
2780
+ else
2781
+ {
2782
+ MCPSetCreated(Result);
2783
+ }
2784
+
2785
+ // The mesh is untouched: this writes a preview PNG under Saved/ and nothing
2786
+ // else. An export produces an output artifact rather than project state,
2787
+ // and deleting a picture that regenerates on demand from the same call is
2788
+ // not a meaningful undo, so no inverse is named. The six export_* actions
2789
+ // that predate this one emit no rollback for the same reason.
2790
+ const FString OverwriteNote = bOverwroteExistingFile
2791
+ ? FString(TEXT(" The picture that was already at that path is gone and no copy was kept."))
2792
+ : FString();
2793
+ MCPSetNoRollback(Result, FString::Printf(
2794
+ TEXT("Wrote the preview PNG '%s' and changed no project state. An export produces an output artifact, and ")
2795
+ TEXT("deleting a file that this same call regenerates on demand is not a meaningful undo, so no inverse is ")
2796
+ TEXT("named.%s"),
2797
+ *OutputPath, *OverwriteNote));
2767
2798
  return MCPResult(Result);
2768
2799
  }
2769
2800
 
@@ -65,10 +65,14 @@
65
65
  #include "EdGraph/EdGraphNode.h"
66
66
 
67
67
  #include "MetasoundBuilderBase.h"
68
+ // MetasoundDocumentBuilderRegistry.h, and never MetasoundFrontendDocumentBuilderRegistry.h:
69
+ // the frontend header ships on 5.8 only. This MetasoundEngine header reaches
70
+ // Metasound::Frontend::IDocumentBuilderRegistry on every supported engine, by
71
+ // including the frontend header on 5.8 and MetasoundDocumentInterface.h, where
72
+ // 5.7 and earlier declare the class, on all of them.
68
73
  #include "MetasoundDocumentBuilderRegistry.h"
69
74
  #include "MetasoundDocumentInterface.h"
70
75
  #include "MetasoundFrontendDocument.h"
71
- #include "MetasoundFrontendDocumentBuilderRegistry.h"
72
76
  #include "MetasoundFrontendLiteral.h"
73
77
  #include "MetasoundSource.h"
74
78
 
@@ -240,9 +244,18 @@ namespace
240
244
  const FMetasoundFrontendDocument* MSEditDocument(const FMSEditTarget& T)
241
245
  {
242
246
  if (!T.Builder) return nullptr;
243
- TScriptInterface<IMetaSoundDocumentInterface> Doc = T.Builder->GetMetaSound();
244
- const IMetaSoundDocumentInterface* Iface = Doc.GetInterface();
245
- return Iface ? &Iface->GetConstDocument() : nullptr;
247
+ // Through the builder's own document rather than through the MetaSound
248
+ // object it is editing. UMetaSoundBuilderBase::GetMetaSound arrived in
249
+ // 5.8, and the two accessors that would stand in for it on 5.7 are
250
+ // private in both engines, but GetConstDocumentChecked has been public
251
+ // on FMetaSoundFrontendDocumentBuilder throughout and is what the
252
+ // GetMetaSound round trip was reaching for anyway.
253
+ //
254
+ // IsValid first, because the Checked form asserts on a builder with no
255
+ // document while the round trip it replaces returned null for it.
256
+ const FMetaSoundFrontendDocumentBuilder& Builder = T.Builder->GetConstBuilder();
257
+ if (!Builder.IsValid()) return nullptr;
258
+ return &Builder.GetConstDocumentChecked();
246
259
  }
247
260
 
248
261
  /**
@@ -45,9 +45,13 @@
45
45
 
46
46
  #include "MetasoundBuilderSubsystem.h"
47
47
  #include "MetasoundBuilderBase.h"
48
+ // MetasoundDocumentBuilderRegistry.h, and never MetasoundFrontendDocumentBuilderRegistry.h:
49
+ // the frontend header ships on 5.8 only. This MetasoundEngine header reaches
50
+ // Metasound::Frontend::IDocumentBuilderRegistry on every supported engine, by
51
+ // including the frontend header on 5.8 and MetasoundDocumentInterface.h, where
52
+ // 5.7 and earlier declare the class, on all of them.
48
53
  #include "MetasoundDocumentBuilderRegistry.h"
49
54
  #include "MetasoundDocumentInterface.h"
50
- #include "MetasoundFrontendDocumentBuilderRegistry.h"
51
55
  #include "MetasoundSource.h"
52
56
  #include "MetasoundFrontendLiteral.h"
53
57
  #include "MetasoundFrontendDocument.h"
@@ -39,9 +39,14 @@
39
39
 
40
40
  #include "MetasoundBuilderSubsystem.h"
41
41
  #include "MetasoundBuilderBase.h"
42
+ // MetasoundDocumentBuilderRegistry.h, and never MetasoundFrontendDocumentBuilderRegistry.h:
43
+ // the frontend header ships on 5.8 only. This MetasoundEngine header reaches
44
+ // Metasound::Frontend::IDocumentBuilderRegistry on every supported engine, by
45
+ // including the frontend header on 5.8 and MetasoundDocumentInterface.h, where
46
+ // 5.7 and earlier declare the class, on all of them.
47
+ #include "MetasoundDocumentBuilderRegistry.h"
42
48
  #include "MetasoundDocumentInterface.h"
43
49
  #include "MetasoundFrontendDocument.h"
44
- #include "MetasoundFrontendDocumentBuilderRegistry.h"
45
50
  #include "MetasoundFrontendLiteral.h"
46
51
  #include "MetasoundSource.h"
47
52