From 0fe0cfa1c26fb82d3a051cbf5c0dc858f1a8325e Mon Sep 17 00:00:00 2001 From: jyelon Date: Sun, 8 Mar 2026 01:47:15 -0500 Subject: [PATCH] More MCP refactors --- .../BlueprintMCP/Private/MCPAssetFinder.cpp | 173 ++++++++++++++++++ .../Private/MCPHandlers_AnimMutation.h | 59 +++--- .../Private/MCPHandlers_AssetMutation.h | 20 +- .../Private/MCPHandlers_Components.h | 33 +--- .../Private/MCPHandlers_DiffBlueprints.h | 14 +- .../Private/MCPHandlers_Discovery.h | 67 ++++--- .../Private/MCPHandlers_Dispatchers.h | 22 +-- .../BlueprintMCP/Private/MCPHandlers_Graphs.h | 78 +++----- .../Private/MCPHandlers_Interfaces.h | 43 ++--- .../Private/MCPHandlers_MaterialInstance.h | 57 +++--- .../Private/MCPHandlers_MaterialMutation.h | 125 +++++-------- .../Private/MCPHandlers_MaterialRead.h | 156 +++++----------- .../Private/MCPHandlers_Mutation.h | 156 +++++----------- .../BlueprintMCP/Private/MCPHandlers_Params.h | 48 +---- .../Private/MCPHandlers_PinMutation.h | 50 +---- .../BlueprintMCP/Private/MCPHandlers_Read.h | 61 +++--- .../Private/MCPHandlers_StateMachine.h | 87 ++++----- .../Private/MCPHandlers_UserTypes.h | 21 +-- .../Private/MCPHandlers_Validation.h | 23 +-- .../Private/MCPHandlers_Variables.h | 62 ++----- .../BlueprintMCP/Public/MCPAssetFinder.h | 82 ++++++++- .../Source/BlueprintMCP/Public/MCPUtils.h | 1 + 22 files changed, 661 insertions(+), 777 deletions(-) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPAssetFinder.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPAssetFinder.cpp index d4b4453f..fdc11a21 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPAssetFinder.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPAssetFinder.cpp @@ -243,6 +243,179 @@ UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOr return LoadBlueprintOrLevelBlueprint(*Asset, Error); } +// ============================================================ +// MCPAssetsBase +// ============================================================ + +MCPAssetsBase::MCPAssetsBase(UClass* InTargetClass) + : TargetClass(InTargetClass) +{ +} + +MCPAssetsBase& MCPAssetsBase::Exact(const FString& InName) +{ + MatchName = InName; + bExactMatch = true; + bPatternHasSlash = MatchName.Contains(TEXT("/")); + return *this; +} + +MCPAssetsBase& MCPAssetsBase::Substring(const FString& InFilter) +{ + MatchName = InFilter; + bExactMatch = false; + bPatternHasSlash = MatchName.Contains(TEXT("/")); + return *this; +} + +MCPAssetsBase& MCPAssetsBase::NoDerived() +{ + bNoDerived = true; + return *this; +} + +MCPAssetsBase& MCPAssetsBase::AllContent() +{ + bAllContent = true; + return *this; +} + +MCPAssetsBase& MCPAssetsBase::Errors(MCPErrorCallback InCB) +{ + ErrorCB = InCB; + return *this; +} + + +bool MCPAssetsBase::Info() +{ + // In theory, there's no reason a person couldn't load/info + // more than once, to obtain updates. Might as well allow it. + AssetResults.Empty(); + UObjectResults.Empty(); + + // Query the asset registry + IAssetRegistry& AR = FModuleManager::LoadModuleChecked("AssetRegistry").Get(); + while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.1f); + + FARFilter Filter; + TArray Candidates; + ConfigureFilterClassPaths(Filter); + AR.GetAssets(Filter, Candidates); + for (const FAssetData &Data : Candidates) + { + if (AssetMatches(Data)) AssetResults.Add(Data); + if (bErrorIfAny && (AssetResults.Num() > 0)) + { + SetError(FString::Printf(TEXT("%s '%s' already exists."), *TargetClass->GetName(), *AssetResults[0].PackageName.ToString())); + return false; + } + if (bErrorIfTwo && (AssetResults.Num() > 1)) + { + SetError(FString::Printf( + TEXT("Ambiguous %s name '%s' — matches '%s' and '%s'. Use the full package path to disambiguate."), + *TargetClass->GetName(), *MatchName, *AssetResults[0].PackageName.ToString(), *AssetResults[1].PackageName.ToString())); + return false; + } + if (AssetResults.Num() >= MaxResults) break; + } + + // Check error conditions on the result count + if (bErrorIfNone && AssetResults.IsEmpty()) + { + SetError(FString::Printf(TEXT("%s '%s' not found."), *TargetClass->GetName(), *MatchName)); + return false; + } + return true; +} + +bool MCPAssetsBase::Load() +{ + if (!Info()) return false; + + TArray AssetsToLoad; + Swap(AssetsToLoad, AssetResults); + for (const FAssetData &Asset : AssetsToLoad) + { + UObject *Obj = TryLoadAsset(Asset); + if (Obj != nullptr) + { + AssetResults.Add(Asset); + UObjectResults.Add(Obj); + } + } + if (bErrorIfNone && AssetResults.IsEmpty()) + { + SetError(FString::Printf(TEXT("%s '%s' exists but cannot be loaded."), *TargetClass->GetName(), + *AssetsToLoad[0].PackageName.ToString())); + return false; + } + return true; +} + +void MCPAssetsBase::ConfigureFilterClassPaths(FARFilter &Filter) +{ + if (Classes.IsEmpty()) + { + Filter.ClassPaths.Add(TargetClass->GetClassPathName()); + } + else + { + for (UClass* C : Classes) Filter.ClassPaths.Add(C->GetClassPathName()); + } + Filter.bRecursiveClasses = !bNoDerived; + if (!bAllContent) + { + Filter.PackagePaths.Add(FName(TEXT("/Game"))); + Filter.bRecursivePaths = true; + } +} + +bool MCPAssetsBase::AssetMatches(const FAssetData &Asset) +{ + if (bExactMatch) + { + FString Name = bPatternHasSlash ? Asset.PackageName.ToString() : Asset.AssetName.ToString(); + return Name.Equals(MatchName, ESearchCase::IgnoreCase); + } + else + { + return Asset.AssetName.ToString().Contains(MatchName, ESearchCase::IgnoreCase) || + Asset.PackageName.ToString().Contains(MatchName, ESearchCase::IgnoreCase); + } +} + +UObject *MCPAssetsBase::TryLoadAsset(const FAssetData &Asset) +{ + UObject* Obj = Asset.GetAsset(); + if (Obj == nullptr) return nullptr; + if (Obj->IsA(TargetClass)) return Obj; + + if (TargetClass->IsChildOf(UBlueprint::StaticClass()) && + ULevelScriptBlueprint::StaticClass()->IsChildOf(TargetClass)) + { + UWorld* World = Cast(Obj); + if (World && World->PersistentLevel) + { + ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true); + if (LevelBP) return LevelBP; + } + } + + return nullptr; +} + +void MCPAssetsBase::SetError(const FString &Msg) +{ + AssetResults.Empty(); + UObjectResults.Empty(); + ErrorCB.SetError(Msg); +} + +// ============================================================ +// Load helpers +// ============================================================ + UAnimationStateMachineGraph* UMCPAssetFinder::LoadAnimStateMachineGraph( const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error) { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h index 9225b694..7233198b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h @@ -59,16 +59,13 @@ public: // Check if asset already exists FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; // Resolve skeleton - USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset(Skeleton, Result); - if (!SkeletonObj) return; + MCPAssets SkeletonAssets; + if (!SkeletonAssets.Exact(Skeleton).Errors(Result).ENone().ETwo().Load()) return; + USkeleton* SkeletonObj = SkeletonAssets.Object(); // Resolve parent class (default: UAnimInstance) UClass* ParentClassObj = UAnimInstance::StaticClass(); @@ -136,13 +133,9 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"), *Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprintName"), Name); - Result->SetStringField(TEXT("packagePath"), PackagePath); Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName()); Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); - Result->SetBoolField(TEXT("isAnimBlueprint"), true); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetArrayField(TEXT("graphs"), GraphNames); } @@ -173,8 +166,9 @@ public: return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } - UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(Blueprint, Result); - if (!AnimBP) return; + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimBlueprint* AnimBP = Assets.Object(); // Walk all anim nodes to collect slot names TSet SlotNames; @@ -208,8 +202,6 @@ public: SlotsArr.Add(MakeShared(Slot)); } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetArrayField(TEXT("slots"), SlotsArr); Result->SetNumberField(TEXT("count"), SlotsArr.Num()); } @@ -240,8 +232,9 @@ public: return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } - UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(Blueprint, Result); - if (!AnimBP) return; + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimBlueprint* AnimBP = Assets.Object(); // Walk all anim nodes to collect sync group names TSet SyncGroupNames; @@ -275,8 +268,6 @@ public: GroupsArr.Add(MakeShared(Group)); } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetArrayField(TEXT("syncGroups"), GroupsArr); Result->SetNumberField(TEXT("count"), GroupsArr.Num()); } @@ -321,16 +312,13 @@ public: // Check if asset already exists FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), Name)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; // Resolve skeleton - USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset(Skeleton, Result); - if (!SkeletonObj) return; + MCPAssets SkeletonAssets; + if (!SkeletonAssets.Exact(Skeleton).Errors(Result).ENone().ETwo().Load()) return; + USkeleton* SkeletonObj = SkeletonAssets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blend Space '%s' in '%s' with skeleton '%s'"), *Name, *PackagePath, *SkeletonObj->GetName()); @@ -360,7 +348,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"), *Name, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("skeleton"), SkeletonObj->GetName()); Result->SetBoolField(TEXT("saved"), bSaved); @@ -415,8 +402,9 @@ public: } // Load the blend space - UBlendSpace* BS = UMCPAssetFinder::LoadAsset(BlendSpace, Result); - if (!BS) return; + MCPAssets Assets; + if (!Assets.Exact(BlendSpace).Errors(Result).ENone().ETwo().Load()) return; + UBlendSpace* BS = Assets.Object(); // Set axis parameters BS->PreEditChange(nullptr); @@ -458,11 +446,9 @@ public: UAnimSequence* AnimSeq = nullptr; if (!AnimAssetName.IsEmpty()) { - FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName); - if (FoundAnimAsset) - { - AnimSeq = Cast(FoundAnimAsset->GetAsset()); - } + MCPAssets AnimAssets; + if (AnimAssets.Exact(AnimAssetName).Load()) + AnimSeq = AnimAssets.Object(); } FVector SampleValue(X, Y, 0.0f); @@ -487,7 +473,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set %d samples on Blend Space '%s' (saved: %s)"), SamplesSet, *BS->GetName(), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blendSpace"), BS->GetPathName()); Result->SetNumberField(TEXT("samplesSet"), SamplesSet); Result->SetBoolField(TEXT("saved"), bSaved); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h index 4f6bb1bf..ea2b2682 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h @@ -152,10 +152,7 @@ public: } } - Result->SetBoolField(TEXT("success"), bDeleted); - Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("filename"), PackageFilename); - Result->SetBoolField(TEXT("forced"), Force); if (!bDeleted) { MCPUtils::MakeErrorJson(Result, TEXT("Failed to delete file from disk")); @@ -201,17 +198,9 @@ public: TArray RenameData; // We need to load the asset to get the object - FAssetData* FoundAsset = UMCPAssetFinder::FindAnyAsset(AssetPath); - if (!FoundAsset) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Asset '%s' not found. Checked Blueprints, Materials, Material Instances, and Material Functions."), *AssetPath)); - } - - UObject* AssetObj = FoundAsset->GetAsset(); - if (!AssetObj) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to load asset '%s'"), *AssetPath)); - } + MCPAssets Assets; + if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return; + UObject* AssetObj = Assets.Object(); // Parse new path into package path and asset name FString NewPackagePath, NewAssetName; @@ -244,9 +233,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Rename %s"), bSuccess ? TEXT("succeeded") : TEXT("failed")); - Result->SetBoolField(TEXT("success"), bSuccess); - Result->SetStringField(TEXT("oldPath"), AssetPath); - Result->SetStringField(TEXT("newPath"), NewPath); Result->SetStringField(TEXT("newPackagePath"), NewPackagePath); Result->SetStringField(TEXT("newAssetName"), NewAssetName); if (!bSuccess) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Components.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Components.h index 64b599fb..85f5e4f1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Components.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Components.h @@ -33,12 +33,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) @@ -101,7 +98,6 @@ public: ComponentsArr.Add(MakeShared(CompObj)); } - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("count"), ComponentsArr.Num()); Result->SetArrayField(TEXT("components"), ComponentsArr); } @@ -137,12 +133,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) @@ -265,8 +258,6 @@ public: ParentSCSNode ? *ParentComponent : TEXT("(root)"), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("component"), NewNode->GetVariableName().ToString()); Result->SetStringField(TEXT("componentClass"), ComponentClassObj->GetName()); if (ParentSCSNode) @@ -300,12 +291,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) @@ -368,9 +356,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed component '%s' from '%s' (saved: %s)"), *Component, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("component"), Component); Result->SetBoolField(TEXT("saved"), bSaved); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_DiffBlueprints.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_DiffBlueprints.h index d50edadf..58e9ae37 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_DiffBlueprints.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_DiffBlueprints.h @@ -41,12 +41,13 @@ public: { // Load both blueprints - FString LoadErrorA, LoadErrorB; - UBlueprint* BPA = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintA, LoadErrorA); - if (!BPA) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; } + MCPAssets AssetsA; + if (!AssetsA.Exact(BlueprintA).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BPA = AssetsA.Object(); - UBlueprint* BPB = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintB, LoadErrorB); - if (!BPB) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; } + MCPAssets AssetsB; + if (!AssetsB.Exact(BlueprintB).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BPB = AssetsB.Object(); // Helper to gather graphs from a Blueprint auto GatherGraphs = [this](UBlueprint* BP) -> TArray @@ -251,9 +252,6 @@ public: } } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprintA"), BlueprintA); - Result->SetStringField(TEXT("blueprintB"), BlueprintB); Result->SetArrayField(TEXT("graphs"), GraphDiffs); if (VarsOnlyInA.Num() > 0) Result->SetArrayField(TEXT("variablesOnlyInA"), VarsOnlyInA); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Discovery.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Discovery.h index 461cc661..bb576c84 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Discovery.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Discovery.h @@ -43,12 +43,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UEdGraph* Graph = nullptr; UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); @@ -78,9 +75,6 @@ public: return; } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("pinName"), Pin->PinName.ToString()); Result->SetStringField(TEXT("direction"), Pin->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")); Result->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString()); @@ -168,12 +162,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UEdGraph* SourceGraph = nullptr; UEdGraphNode* FoundSourceNode = MCPUtils::FindNodeByGuid(BP, SourceNode, &SourceGraph); @@ -209,9 +200,6 @@ public: // Check compatibility using the schema const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - bool bCompatible = (Response.Response != ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW); Result->SetBoolField(TEXT("compatible"), bCompatible); @@ -371,7 +359,6 @@ public: ClassList.Add(MakeShared(ClassObj)); } - Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("count"), ClassList.Num()); Result->SetNumberField(TEXT("totalMatched"), TotalMatched); if (TotalMatched > Limit) @@ -494,7 +481,6 @@ public: FuncList.Add(MakeShared(FuncObj)); } - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetNumberField(TEXT("count"), FuncList.Num()); Result->SetArrayField(TEXT("functions"), FuncList); @@ -587,7 +573,6 @@ public: PropList.Add(MakeShared(PropObj)); } - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetNumberField(TEXT("count"), PropList.Num()); Result->SetArrayField(TEXT("properties"), PropList); @@ -604,6 +589,9 @@ class UMCPHandler_ShowCommands : public UObject, public IMCPHandler GENERATED_BODY() public: + UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions")) + bool Verbose = false; + virtual FString GetDescription() const override { return TEXT("List all available commands with their descriptions."); @@ -611,7 +599,8 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - TArray> CommandsArray; + // Collect all handler classes sorted by tool name + TArray> Handlers; for (TObjectIterator It; It; ++It) { UClass* Class = *It; @@ -620,9 +609,41 @@ public: if (!Handler) continue; const FString& ToolName = Class->GetMetaData(TEXT("ToolName")); if (ToolName.IsEmpty()) continue; + Handlers.Add({ToolName, Class}); + } + Handlers.Sort(); + + if (!Verbose) + { + // Compact format: "command_name(param1,param2,?optional)" + TArray> Lines; + for (const auto& Pair : Handlers) + { + FString Line = Pair.Key + TEXT("("); + bool bFirst = true; + for (TFieldIterator PropIt(Pair.Value, EFieldIterationFlags::None); PropIt; ++PropIt) + { + if (!bFirst) Line += TEXT(","); + bFirst = false; + if (PropIt->HasMetaData(TEXT("Optional"))) Line += TEXT("?"); + Line += MCPUtils::PropertyNameToJsonKey(PropIt->GetName()); + } + Line += TEXT(")"); + Lines.Add(MakeShared(Line)); + } + Result->SetNumberField(TEXT("count"), Lines.Num()); + Result->SetArrayField(TEXT("commands"), Lines); + return; + } + + TArray> CommandsArray; + for (const auto& Pair : Handlers) + { + UClass* Class = Pair.Value; + const IMCPHandler* Handler = Cast(Class->GetDefaultObject()); TSharedRef Entry = MakeShared(); - Entry->SetStringField(TEXT("command"), ToolName); + Entry->SetStringField(TEXT("command"), Pair.Key); Entry->SetStringField(TEXT("description"), Handler->GetDescription()); // Document parameters from UPROPERTY fields diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Dispatchers.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Dispatchers.h index 92c1d98b..e6e2da2d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Dispatchers.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Dispatchers.h @@ -39,12 +39,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); FName DispatcherFName(*DispatcherName); @@ -155,9 +152,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added event dispatcher '%s' to '%s' with %d params (saved: %s)"), *DispatcherName, *Blueprint, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("dispatcherName"), DispatcherName); Result->SetArrayField(TEXT("parameters"), AddedParamsJson); Result->SetBoolField(TEXT("saved"), bSaved); } @@ -183,12 +177,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TSet DelegateNameSet; FBlueprintEditorUtils::GetDelegateNameList(BP, DelegateNameSet); @@ -236,7 +227,6 @@ public: DispatchersArr.Add(MakeShared(DispObj)); } - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("count"), DispatchersArr.Num()); Result->SetArrayField(TEXT("dispatchers"), DispatchersArr); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Graphs.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Graphs.h index 02d23a25..b42f9794 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Graphs.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Graphs.h @@ -38,12 +38,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None"); @@ -64,11 +61,12 @@ public: // If not found as C++ class, try loading as a Blueprint asset if (!NewParentClassObj) { - FString ParentLoadError; - UBlueprint* ParentBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(NewParentClass, ParentLoadError); - if (ParentBP && ParentBP->GeneratedClass) + MCPAssets ParentAssets; + if (!ParentAssets.Exact(NewParentClass).AllContent().Errors(Result).ETwo().Load()) return; + if (!ParentAssets.Objects().IsEmpty()) { - NewParentClassObj = ParentBP->GeneratedClass; + if (ParentAssets.Object()->GeneratedClass) + NewParentClassObj = ParentAssets.Object()->GeneratedClass; } } @@ -109,8 +107,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("oldParentClass"), OldParentName); Result->SetStringField(TEXT("newParentClass"), NewParentActualName); Result->SetBoolField(TEXT("saved"), bSaved); @@ -154,12 +150,8 @@ public: // Check if asset already exists FString FullAssetPath = PackagePath / Blueprint; - if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Blueprint) || UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), FullAssetPath)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."), - *Blueprint)); - } + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Blueprint).Errors(Result).EAny().Info()) return; // Resolve parent class — try C++ class first, then Blueprint UClass* ParentClassObj = nullptr; @@ -175,11 +167,12 @@ public: if (!ParentClassObj) { - FString ParentLoadError; - UBlueprint* ParentBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(ParentClass, ParentLoadError); - if (ParentBP && ParentBP->GeneratedClass) + MCPAssets ParentAssets; + if (!ParentAssets.Exact(ParentClass).AllContent().Errors(Result).ETwo().Load()) return; + if (!ParentAssets.Objects().IsEmpty()) { - ParentClassObj = ParentBP->GeneratedClass; + if (ParentAssets.Object()->GeneratedClass) + ParentClassObj = ParentAssets.Object()->GeneratedClass; } } @@ -272,12 +265,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"), *Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprintName"), Blueprint); - Result->SetStringField(TEXT("packagePath"), PackagePath); Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); - Result->SetStringField(TEXT("blueprintType"), BlueprintType.IsEmpty() ? TEXT("Normal") : *BlueprintType); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetArrayField(TEXT("graphs"), GraphNames); } @@ -316,12 +305,9 @@ public: } // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Check graph name uniqueness TArray AllGraphs; @@ -410,10 +396,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"), *GraphType, *Graph, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("graphName"), Graph); - Result->SetStringField(TEXT("graphType"), GraphType); Result->SetBoolField(TEXT("saved"), bSaved); if (!CreatedNodeId.IsEmpty()) { @@ -445,12 +427,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the graph UEdGraph* TargetGraph = nullptr; @@ -508,9 +487,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"), *Graph, NodeCount, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("graphName"), Graph); Result->SetStringField(TEXT("graphType"), GraphType); Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetBoolField(TEXT("saved"), bSaved); @@ -543,12 +519,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Check if it's an UbergraphPage — disallow rename for (UEdGraph* CandidateGraph : BP->UbergraphPages) @@ -615,9 +588,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"), *Graph, *NewName, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("oldName"), Graph); Result->SetStringField(TEXT("newName"), TargetGraph->GetName()); Result->SetStringField(TEXT("graphType"), GraphType); Result->SetBoolField(TEXT("saved"), bSaved); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Interfaces.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Interfaces.h index 9897d3a7..6777853a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Interfaces.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Interfaces.h @@ -33,12 +33,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TArray> InterfacesArr; for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) @@ -65,7 +62,6 @@ public: InterfacesArr.Add(MakeShared(IfaceObj)); } - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("count"), InterfacesArr.Num()); Result->SetArrayField(TEXT("interfaces"), InterfacesArr); } @@ -96,12 +92,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Resolve the interface class UClass* InterfaceClass = nullptr; @@ -137,11 +130,13 @@ public: // Strategy 2: Try loading as a Blueprint Interface asset if (!InterfaceClass) { - FString IfaceLoadError; - UBlueprint* IfaceBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(InterfaceName, IfaceLoadError); - if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass())) + MCPAssets IfaceAssets; + if (!IfaceAssets.Exact(InterfaceName).AllContent().Errors(Result).ETwo().Load()) return; + if (!IfaceAssets.Objects().IsEmpty()) { - InterfaceClass = IfaceBP->GeneratedClass; + UClass* GenClass = IfaceAssets.Object()->GeneratedClass; + if (GenClass && GenClass->IsChildOf(UInterface::StaticClass())) + InterfaceClass = GenClass; } } @@ -199,8 +194,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"), *InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num()); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName()); @@ -241,12 +234,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the interface in ImplementedInterfaces by name (case-insensitive) UClass* FoundInterface = nullptr; @@ -308,9 +298,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"), *FoundInterface->GetName(), *Blueprint); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); - Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialInstance.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialInstance.h index ef026955..f141f00d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialInstance.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialInstance.h @@ -50,30 +50,25 @@ public: } // Check if asset already exists - FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UMaterialInstanceConstant::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UMaterialInstanceConstant::StaticClass(), FullAssetPath)) { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Material Instance '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; } // Load parent material — try as Material first, then as Material Instance UMaterialInterface* ParentMaterialObj = nullptr; { - FString LoadError; - UMaterial* ParentMat = UMCPAssetFinder::LoadAsset(ParentMaterial, LoadError); - if (ParentMat) + MCPAssets MatAssets; + if (MatAssets.Exact(ParentMaterial).ETwo().Load() && !MatAssets.Objects().IsEmpty()) { - ParentMaterialObj = ParentMat; + ParentMaterialObj = MatAssets.Object(); } else { - FString MILoadError; - UMaterialInstanceConstant* ParentMI = UMCPAssetFinder::LoadAsset(ParentMaterial, MILoadError); - if (ParentMI) + MCPAssets MIAssets; + if (MIAssets.Exact(ParentMaterial).ETwo().Load() && !MIAssets.Objects().IsEmpty()) { - ParentMaterialObj = ParentMI; + ParentMaterialObj = MIAssets.Object(); } } } @@ -122,8 +117,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Instance '%s' with parent '%s' (saved: %s)"), *Name, *ParentMaterialObj->GetName(), bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), MI->GetPathName()); Result->SetStringField(TEXT("parent"), ParentMaterialObj->GetPathName()); Result->SetBoolField(TEXT("saved"), bSaved); @@ -169,8 +162,9 @@ public: } // Load the Material Instance - UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset(MaterialInstance, Result); - if (!MI) return; + MCPAssets Assets; + if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return; + UMaterialInstanceConstant* MI = Assets.Object(); // Determine the parameter type — explicit or auto-detect from parent FString TypeStr = Type; @@ -359,9 +353,6 @@ public: DryRun ? TEXT("[DRY RUN] Would set") : TEXT("Set"), *ParameterName, *NewValueDescription, *MaterialInstance); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("materialInstance"), MaterialInstance); - Result->SetStringField(TEXT("parameterName"), ParameterName); Result->SetStringField(TEXT("type"), TypeStr); Result->SetStringField(TEXT("newValue"), NewValueDescription); if (DryRun) @@ -391,8 +382,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset(MaterialInstance, Result); - if (!MI) return; + MCPAssets Assets; + if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return; + UMaterialInstanceConstant* MI = Assets.Object(); Result->SetStringField(TEXT("name"), MI->GetName()); Result->SetStringField(TEXT("path"), MI->GetPathName()); @@ -643,8 +635,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Load the Material Instance - UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset(MaterialInstance, Result); - if (!MI) return; + MCPAssets Assets; + if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return; + UMaterialInstanceConstant* MI = Assets.Object(); // Capture old parent FString OldParentPath = MI->Parent ? MI->Parent->GetPathName() : TEXT("None"); @@ -652,19 +645,17 @@ public: // Load new parent — try as Material first, then as Material Instance UMaterialInterface* NewParentObj = nullptr; { - FString MatLoadError; - UMaterial* NewParentMat = UMCPAssetFinder::LoadAsset(NewParent, MatLoadError); - if (NewParentMat) + MCPAssets MatAssets; + if (MatAssets.Exact(NewParent).ETwo().Load() && !MatAssets.Objects().IsEmpty()) { - NewParentObj = NewParentMat; + NewParentObj = MatAssets.Object(); } else { - FString MILoadError; - UMaterialInstanceConstant* NewParentMI = UMCPAssetFinder::LoadAsset(NewParent, MILoadError); - if (NewParentMI) + MCPAssets MIAssets; + if (MIAssets.Exact(NewParent).ETwo().Load() && !MIAssets.Objects().IsEmpty()) { - NewParentObj = NewParentMI; + NewParentObj = MIAssets.Object(); } } } @@ -721,8 +712,6 @@ public: *MaterialInstance, bSaved ? TEXT("true") : TEXT("false")); } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("materialInstance"), MaterialInstance); Result->SetStringField(TEXT("oldParent"), OldParentPath); Result->SetStringField(TEXT("newParent"), NewParentObj->GetPathName()); if (DryRun) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialMutation.h index b39c235b..0236b896 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialMutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialMutation.h @@ -92,13 +92,8 @@ public: } // Check if asset already exists - FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), FullAssetPath)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Material '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Material '%s' in '%s'"), *Name, *PackagePath); @@ -197,8 +192,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material '%s' (saved: %s)"), *Name, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), MaterialObj->GetPathName()); Result->SetStringField(TEXT("domain"), DomainToString(MaterialObj->MaterialDomain)); Result->SetStringField(TEXT("blendMode"), BlendModeToString(MaterialObj->BlendMode)); @@ -240,8 +233,9 @@ public: } // Load material - UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets Assets; + if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + UMaterial* MaterialObj = Assets.Object(); FString OldValue; FString NewValue; @@ -490,9 +484,7 @@ public: DryRun ? TEXT("[DRY RUN] ") : TEXT(""), *Property, *Material, *OldValue, *NewValue); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), MaterialObj->GetName()); - Result->SetStringField(TEXT("property"), Property); Result->SetStringField(TEXT("oldValue"), OldValue); Result->SetStringField(TEXT("newValue"), NewValue); Result->SetBoolField(TEXT("dryRun"), DryRun); @@ -593,15 +585,17 @@ public: { return MCPUtils::MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both")); } - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); Owner = MatFunc; AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); Owner = MaterialObj; AssetDisplayName = MaterialObj->GetName(); } @@ -611,12 +605,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would add expression '%s' to '%s' at (%d, %d)"), *ExpressionClass, *AssetDisplayName, PosX, PosY); - Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("expressionClass"), ExpressionClass); - Result->SetNumberField(TEXT("posX"), PosX); - Result->SetNumberField(TEXT("posY"), PosY); return; } @@ -687,12 +677,8 @@ public: // Serialize the expression details TSharedPtr ExprDetails = MCPUtils::SerializeMaterialExpression(NewExpr); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("expressionClass"), ExpressionClass); Result->SetStringField(TEXT("nodeId"), NodeGuid); - Result->SetNumberField(TEXT("posX"), PosX); - Result->SetNumberField(TEXT("posY"), PosY); if (ExprDetails.IsValid()) { Result->SetObjectField(TEXT("expression"), ExprDetails); @@ -742,14 +728,16 @@ public: if (!MaterialFunction.IsEmpty()) { - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); AssetDisplayName = MaterialObj->GetName(); } @@ -792,10 +780,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would delete expression '%s' (nodeId: %s) from '%s'"), *DeletedExprClass, *Node, *AssetDisplayName); - Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("deletedNode"), Node); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); return; @@ -827,9 +813,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted expression '%s' (nodeId: %s) from '%s' (saved: %s)"), *DeletedExprClass, *Node, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("deletedNode"), Node); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); Result->SetBoolField(TEXT("saved"), bSaved); @@ -886,14 +870,16 @@ public: if (!MaterialFunction.IsEmpty()) { - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); AssetDisplayName = MaterialObj->GetName(); } @@ -967,9 +953,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would connect %s.%s -> %s.%s in '%s'"), *SourceNode, *SourcePinName, *TargetNode, *TargetPinName, *AssetDisplayName); - Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); - Result->SetBoolField(TEXT("connected"), false); Result->SetStringField(TEXT("material"), AssetDisplayName); return; } @@ -986,8 +970,6 @@ public: bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin); - Result->SetBoolField(TEXT("success"), bConnected); - Result->SetBoolField(TEXT("connected"), bConnected); Result->SetStringField(TEXT("material"), AssetDisplayName); if (!bConnected) @@ -1050,14 +1032,16 @@ public: if (!MaterialFunction.IsEmpty()) { - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); AssetDisplayName = MaterialObj->GetName(); } @@ -1109,11 +1093,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would disconnect pin '%s' on node '%s' in '%s' (%d links)"), *PinName, *Node, *AssetDisplayName, BrokenCount); - Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("nodeId"), Node); - Result->SetStringField(TEXT("pinName"), PinName); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); return; } @@ -1131,10 +1112,7 @@ public: // Save bool bSaved = MaterialObj ? MCPUtils::SaveMaterialPackage(MaterialObj) : MCPUtils::SaveGenericPackage(MatFunc); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("nodeId"), Node); - Result->SetStringField(TEXT("pinName"), PinName); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); Result->SetBoolField(TEXT("saved"), bSaved); } @@ -1184,14 +1162,16 @@ public: if (!MaterialFunction.IsEmpty()) { - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); AssetDisplayName = MaterialObj->GetName(); } @@ -1414,9 +1394,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set expression value on node '%s' (%s) in '%s': %s"), *Node, *ExprType, *AssetDisplayName, *NewValueStr); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("expressionType"), ExprType); Result->SetStringField(TEXT("newValue"), NewValueStr); Result->SetBoolField(TEXT("saved"), bSaved); @@ -1470,14 +1448,16 @@ public: if (!MaterialFunction.IsEmpty()) { - MatFunc = UMCPAssetFinder::LoadAsset(MaterialFunction, Result); - if (!MatFunc) return; + MCPAssets MFAssets; + if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return; + MatFunc = MFAssets.Object(); AssetDisplayName = MatFunc->GetName(); } else { - MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets MatAssets; + if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + MaterialObj = MatAssets.Object(); AssetDisplayName = MaterialObj->GetName(); } @@ -1510,12 +1490,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would move node '%s' to (%d, %d) in '%s'"), *Node, PosX, PosY, *AssetDisplayName); - Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("nodeId"), Node); - Result->SetNumberField(TEXT("posX"), PosX); - Result->SetNumberField(TEXT("posY"), PosY); return; } @@ -1540,11 +1516,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Moved node '%s' to (%d, %d) in '%s' (saved: %s)"), *Node, PosX, PosY, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); - Result->SetStringField(TEXT("nodeId"), Node); - Result->SetNumberField(TEXT("posX"), PosX); - Result->SetNumberField(TEXT("posY"), PosY); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -1581,13 +1553,8 @@ public: } // Check if asset already exists - FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UMaterialFunction::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UMaterialFunction::StaticClass(), FullAssetPath)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Material Function '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } + MCPAssets ExistCheck; + if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Material Function '%s' in '%s'"), *Name, *PackagePath); @@ -1620,13 +1587,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Function '%s' (saved: %s)"), *Name, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), MF->GetPathName()); - if (!Description.IsEmpty()) - { - Result->SetStringField(TEXT("description"), Description); - } Result->SetBoolField(TEXT("saved"), bSaved); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialRead.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialRead.h index 9a2613ae..8b19036e 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialRead.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_MaterialRead.h @@ -61,60 +61,24 @@ public: bool bIncludeMaterials = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("material"); bool bIncludeInstances = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("instance"); + MCPAssets Assets; + if (bIncludeMaterials) Assets.Scan(UMaterial::StaticClass()); + if (bIncludeInstances) Assets.Scan(UMaterialInstanceConstant::StaticClass()); + Assets.Substring(Filter).NoDerived().Info(); + TArray> Entries; - - if (bIncludeMaterials) + for (const FAssetData& Asset : Assets.AllData()) { - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterial::StaticClass())) - { - FString Name = Asset.AssetName.ToString(); - FString Path = Asset.PackageName.ToString(); - - if (!Filter.IsEmpty()) - { - if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && - !Path.Contains(Filter, ESearchCase::IgnoreCase)) - { - continue; - } - } - - TSharedRef Entry = MakeShared(); - Entry->SetStringField(TEXT("name"), Name); - Entry->SetStringField(TEXT("path"), Path); - Entry->SetStringField(TEXT("type"), TEXT("Material")); - Entries.Add(MakeShared(Entry)); - } + TSharedRef Entry = MakeShared(); + Entry->SetStringField(TEXT("name"), Asset.AssetName.ToString()); + Entry->SetStringField(TEXT("path"), Asset.PackageName.ToString()); + Entry->SetStringField(TEXT("type"), + Asset.AssetClassPath.GetAssetName() == TEXT("MaterialInstanceConstant") + ? TEXT("MaterialInstance") : TEXT("Material")); + Entries.Add(MakeShared(Entry)); } - if (bIncludeInstances) - { - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterialInstanceConstant::StaticClass())) - { - FString Name = Asset.AssetName.ToString(); - FString Path = Asset.PackageName.ToString(); - - if (!Filter.IsEmpty()) - { - if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && - !Path.Contains(Filter, ESearchCase::IgnoreCase)) - { - continue; - } - } - - TSharedRef Entry = MakeShared(); - Entry->SetStringField(TEXT("name"), Name); - Entry->SetStringField(TEXT("path"), Path); - Entry->SetStringField(TEXT("type"), TEXT("MaterialInstance")); - Entries.Add(MakeShared(Entry)); - } - } - - int32 Total = UMCPAssetFinder::GetAssets(UMaterial::StaticClass()).Num() + UMCPAssetFinder::GetAssets(UMaterialInstanceConstant::StaticClass()).Num(); - Result->SetNumberField(TEXT("count"), Entries.Num()); - Result->SetNumberField(TEXT("total"), Total); Result->SetArrayField(TEXT("materials"), Entries); } }; @@ -141,10 +105,14 @@ public: { FString DecodedName = MCPUtils::UrlDecode(Material); - // Try loading as UMaterial first - FString LoadError; - UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset(DecodedName, LoadError); - if (MaterialObj) + // Try loading as UMaterial or UMaterialInstanceConstant + MCPAssets Assets; + Assets.Scan(UMaterial::StaticClass()); + Assets.Scan(UMaterialInstanceConstant::StaticClass()); + if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return; + UMaterialInterface* LoadedObj = Assets.Object(); + + if (UMaterial* MaterialObj = Cast(LoadedObj)) { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *MaterialObj->GetName()); @@ -296,10 +264,7 @@ public: return; } - // Try loading as MaterialInstance - FString MILoadError; - UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset(DecodedName, MILoadError); - if (MI) + if (UMaterialInstanceConstant* MI = Cast(LoadedObj)) { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName()); @@ -396,8 +361,9 @@ public: { FString DecodedName = MCPUtils::UrlDecode(Material); - UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset(DecodedName, Result); - if (!MaterialObj) return; + MCPAssets Assets; + if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return; + UMaterial* MaterialObj = Assets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialGraph — material '%s'"), *MaterialObj->GetName()); @@ -451,8 +417,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets Assets; + if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + UMaterial* MaterialObj = Assets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *MaterialObj->GetName()); @@ -613,7 +580,6 @@ public: InputDescriptions.Add(MakeShared(InputObj)); } - Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), MaterialObj->GetName()); Result->SetStringField(TEXT("materialPath"), MaterialObj->GetPathName()); Result->SetArrayField(TEXT("inputs"), InputDescriptions); @@ -668,7 +634,10 @@ public: TArray> Results; - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterial::StaticClass())) + MCPAssets AllMaterials; + AllMaterials.Info(); + + for (const FAssetData& Asset : AllMaterials.AllData()) { if (Results.Num() >= MaxResults) break; @@ -757,27 +726,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Try to find the material's package path - FString PackagePath; - FAssetData* MatAsset = UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), Material); - if (MatAsset) - { - PackagePath = MatAsset->PackageName.ToString(); - } - else - { - // Try material instance - FAssetData* MIAsset = UMCPAssetFinder::FindAsset(UMaterialInstanceConstant::StaticClass(), Material); - if (MIAsset) - { - PackagePath = MIAsset->PackageName.ToString(); - } - } - - if (PackagePath.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' not found. Use list_materials to see available assets."), *Material)); - } + // Try to find the material's package path (search both Material and MaterialInstance) + MCPAssets Assets; + Assets.Scan().Scan(); + if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Info()) return; + FString PackagePath = Assets.OneData().PackageName.ToString(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindMaterialReferences — '%s' (package: %s)"), *Material, *PackagePath); @@ -795,8 +748,6 @@ public: RefArray.Add(MakeShared(RefStr)); } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("material"), Material); Result->SetStringField(TEXT("packagePath"), PackagePath); Result->SetNumberField(TEXT("totalReferencers"), RefArray.Num()); Result->SetArrayField(TEXT("referencers"), RefArray); @@ -823,30 +774,19 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { + MCPAssets Assets; + Assets.Substring(Filter).Info(); + TArray> Entries; - - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterialFunction::StaticClass())) + for (const FAssetData& Asset : Assets.AllData()) { - FString Name = Asset.AssetName.ToString(); - FString Path = Asset.PackageName.ToString(); - - if (!Filter.IsEmpty()) - { - if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && - !Path.Contains(Filter, ESearchCase::IgnoreCase)) - { - continue; - } - } - TSharedRef Entry = MakeShared(); - Entry->SetStringField(TEXT("name"), Name); - Entry->SetStringField(TEXT("path"), Path); + Entry->SetStringField(TEXT("name"), Asset.AssetName.ToString()); + Entry->SetStringField(TEXT("path"), Asset.PackageName.ToString()); Entries.Add(MakeShared(Entry)); } Result->SetNumberField(TEXT("count"), Entries.Num()); - Result->SetNumberField(TEXT("total"), UMCPAssetFinder::GetAssets(UMaterialFunction::StaticClass()).Num()); Result->SetArrayField(TEXT("functions"), Entries); } }; @@ -873,8 +813,9 @@ public: { FString DecodedName = MCPUtils::UrlDecode(MaterialFunction); - UMaterialFunction* MF = UMCPAssetFinder::LoadAsset(DecodedName, Result); - if (!MF) return; + MCPAssets Assets; + if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return; + UMaterialFunction* MF = Assets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName()); @@ -962,8 +903,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Load material - UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset(Material, Result); - if (!MaterialObj) return; + MCPAssets Assets; + if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; + UMaterial* MaterialObj = Assets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *MaterialObj->GetName()); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h index 7890df60..6e532a81 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h @@ -90,12 +90,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TArray> Results; int32 SuccessCount = 0; @@ -113,8 +110,6 @@ public: continue; } - EntryResult->SetStringField(TEXT("nodeId"), Entry.Node); - UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); if (!Node) { @@ -126,7 +121,6 @@ public: int32 OldY = Node->NodePosY; Node->NodePosX = Entry.X; Node->NodePosY = Entry.Y; - EntryResult->SetBoolField(TEXT("success"), true); EntryResult->SetNumberField(TEXT("oldX"), OldX); EntryResult->SetNumberField(TEXT("oldY"), OldY); EntryResult->SetNumberField(TEXT("newX"), Node->NodePosX); @@ -139,8 +133,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: MoveNode — %d/%d succeeded"), SuccessCount, Nodes.Array.Num()); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("movedCount"), SuccessCount); Result->SetNumberField(TEXT("totalRequested"), Nodes.Array.Num()); Result->SetArrayField(TEXT("results"), Results); @@ -182,12 +174,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the target graph FString DecodedGraphName = MCPUtils::UrlDecode(Graph); @@ -294,9 +283,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Duplicated %d node(s)"), DuplicatedNodes.Num()); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("graph"), DecodedGraphName); Result->SetNumberField(TEXT("duplicatedCount"), DuplicatedNodes.Num()); Result->SetArrayField(TEXT("nodes"), DuplicatedNodes); @@ -358,12 +344,9 @@ public: { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the target graph FString DecodedGraphName = MCPUtils::UrlDecode(Graph); @@ -408,8 +391,6 @@ public: continue; } - EntryResult->SetStringField(TEXT("actionName"), Entry.ActionName); - // Find the spawner by exact full name TArray Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true, TargetGraph); if (Matches.Num() == 0) @@ -455,7 +436,6 @@ public: // Serialize result TSharedPtr NodeState = MCPUtils::SerializeNode(NewNode); - EntryResult->SetBoolField(TEXT("success"), true); EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString()); EntryResult->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); EntryResult->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView).ToString()); @@ -471,9 +451,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SpawnNode — %d/%d succeeded in graph '%s' of '%s'"), SuccessCount, Nodes.Array.Num(), *DecodedGraphName, *Blueprint); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("graph"), DecodedGraphName); Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("totalCount"), Nodes.Array.Num()); Result->SetArrayField(TEXT("results"), Results); @@ -504,12 +481,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); if (!FoundNode) @@ -517,9 +491,6 @@ public: return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node)); } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("comment"), FoundNode->NodeComment); Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible); } @@ -552,12 +523,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); if (!FoundNode) @@ -580,11 +548,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"), *Node, *Blueprint); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("oldComment"), OldComment); - Result->SetStringField(TEXT("newComment"), Comment); } }; @@ -613,12 +577,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UEdGraph* Graph = nullptr; UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); @@ -671,9 +632,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Node deleted")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("nodeClass"), NodeClass); Result->SetStringField(TEXT("nodeTitle"), NodeTitle); Result->SetStringField(TEXT("graph"), GraphName); @@ -712,12 +670,9 @@ public: { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the new class — try several search strategies UClass* NewClassPtr = nullptr; @@ -913,8 +868,6 @@ public: if (DryRun) { - Result->SetBoolField(TEXT("dryRun"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("wouldReplaceCount"), ReplacedCount); Result->SetNumberField(TEXT("connectionsAtRisk"), BrokenConnections.Num()); Result->SetArrayField(TEXT("connectionsAtRisk"), BrokenConnections); @@ -927,14 +880,12 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Replaced %d function call(s)"), ReplacedCount); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("replacedCount"), ReplacedCount); Result->SetNumberField(TEXT("brokenConnectionCount"), BrokenConnections.Num()); Result->SetArrayField(TEXT("brokenConnections"), BrokenConnections); return; } - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("replacedCount"), 0); Result->SetStringField(TEXT("message"), FString::Printf( TEXT("No function call nodes found targeting class '%s'"), *OldClass)); @@ -964,12 +915,9 @@ public: { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Count graphs and nodes before refresh TArray AllGraphs; @@ -1053,8 +1001,6 @@ public: } } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("graphCount"), GraphCount); Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetNumberField(TEXT("orphanedPinsRemoved"), OrphanedPinsRemoved); @@ -1092,12 +1038,9 @@ public: { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find node UEdGraph* Graph = nullptr; @@ -1281,9 +1224,6 @@ public: // Return updated node state TSharedPtr UpdatedNodeState = MCPUtils::SerializeNode(FoundNode); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("nodeId"), Node); Result->SetStringField(TEXT("newStructType"), NewStruct->GetName()); Result->SetStringField(TEXT("nodeClass"), FoundNode->GetClass()->GetName()); Result->SetNumberField(TEXT("reconnected"), Reconnected); @@ -1325,12 +1265,9 @@ public: { // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); if (!BP->GeneratedClass) { @@ -1377,11 +1314,12 @@ public: // Try loading as a Blueprint asset if (!ResolvedClass) { - FString BPLoadError; - UBlueprint* ValueBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Value, BPLoadError); - if (ValueBP && ValueBP->GeneratedClass) + MCPAssets ValueAssets; + if (!ValueAssets.Exact(Value).AllContent().Errors(Result).ETwo().Load()) return; + if (!ValueAssets.Objects().IsEmpty()) { - ResolvedClass = ValueBP->GeneratedClass; + if (ValueAssets.Object()->GeneratedClass) + ResolvedClass = ValueAssets.Object()->GeneratedClass; } } @@ -1417,17 +1355,10 @@ public: UObject* ResolvedObj = nullptr; // Try loading as a Blueprint asset - FString ObjLoadError; - UBlueprint* ValueBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Value, ObjLoadError); - if (ValueBP && ValueBP->GeneratedClass) - { - ResolvedObj = ValueBP->GeneratedClass->GetDefaultObject(); - } - - if (!ResolvedObj) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Could not resolve '%s' to an object"), *Value)); - } + MCPAssets ValueAssets; + if (!ValueAssets.Exact(Value).AllContent().Errors(Result).ENone().ETwo().Load()) return; + if (ValueAssets.Object()->GeneratedClass) + ResolvedObj = ValueAssets.Object()->GeneratedClass->GetDefaultObject(); ObjProp->SetPropertyValue_InContainer(CDO, ResolvedObj); ActualNewValue = ResolvedObj->GetName(); @@ -1465,9 +1396,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set '%s.%s' from '%s' to '%s' (saved: %s)"), *Blueprint, *Property, *OldValue, *ActualNewValue, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("property"), Property); Result->SetStringField(TEXT("oldValue"), OldValue); Result->SetStringField(TEXT("newValue"), ActualNewValue); Result->SetStringField(TEXT("propertyType"), Prop->GetCPPType()); @@ -1512,8 +1440,9 @@ public: UEdGraph* GraphFilter = nullptr; if (!Blueprint.IsEmpty() && !Graph.IsEmpty()) { - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result); - if (!BP) return; + MCPAssets BPAssets; + if (!BPAssets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = BPAssets.Object(); FString DecodedGraphName = MCPUtils::UrlDecode(Graph); TArray AllGraphs; @@ -1540,7 +1469,6 @@ public: ResultArray.Add(MakeShared(MCPUtils::NodeSpawnerFullName(Spawner))); } - Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("count"), ResultArray.Num()); Result->SetArrayField(TEXT("results"), ResultArray); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Params.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Params.h index dd0f8c0d..8ec6551f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Params.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Params.h @@ -45,13 +45,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references) FEdGraphPinType NewPinType; @@ -193,10 +189,6 @@ public: } Result->SetBoolField(TEXT("dryRun"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("functionName"), FunctionName); - Result->SetStringField(TEXT("paramName"), ParamName); - Result->SetStringField(TEXT("newType"), NewType); Result->SetStringField(TEXT("nodeType"), FoundNodeType); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetNumberField(TEXT("connectionsAtRisk"), AffectedPins.Num()); @@ -225,11 +217,6 @@ public: // Serialize the updated entry node state TSharedPtr UpdatedNodeState = MCPUtils::SerializeNode(EntryNode); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("functionName"), FunctionName); - Result->SetStringField(TEXT("paramName"), ParamName); - Result->SetStringField(TEXT("newType"), NewType); Result->SetStringField(TEXT("nodeType"), FoundNodeType); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetBoolField(TEXT("saved"), bSaved); @@ -266,13 +253,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the entry node UK2Node_EditablePinBase* EntryNode = nullptr; @@ -402,10 +385,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Parameter removed, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("functionName"), FunctionName); - Result->SetStringField(TEXT("paramName"), ParamName); Result->SetStringField(TEXT("nodeType"), FoundNodeType); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetBoolField(TEXT("saved"), bSaved); @@ -441,13 +420,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Resolve param type FEdGraphPinType PinType; @@ -595,11 +570,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added parameter '%s' to '%s' in '%s' (saved: %s)"), *ParamName, *FunctionName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("functionName"), FunctionName); - Result->SetStringField(TEXT("paramName"), ParamName); - Result->SetStringField(TEXT("paramType"), ParamType); Result->SetStringField(TEXT("nodeType"), NodeType); Result->SetBoolField(TEXT("saved"), bSaved); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_PinMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_PinMutation.h index 6a7b379b..d5411633 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_PinMutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_PinMutation.h @@ -5,6 +5,7 @@ #include "MCPAssetFinder.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" +#include "Engine/World.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" @@ -69,17 +70,10 @@ public: continue; } - EntryResult->SetStringField(TEXT("blueprint"), Entry.Blueprint); - EntryResult->SetStringField(TEXT("nodeId"), Entry.Node); - EntryResult->SetStringField(TEXT("pinName"), Entry.PinName); - - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Entry.Blueprint, LoadError); - if (!BP) - { - EntryResult->SetStringField(TEXT("error"), LoadError); + MCPAssets Assets; + if (!Assets.Scan().Scan().Exact(Entry.Blueprint).Errors(&*EntryResult).ENone().ETwo().Load()) continue; - } + UBlueprint* BP = Assets.Object(); UEdGraph* Graph = nullptr; UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node, &Graph); @@ -117,9 +111,7 @@ public: FString OldValue = Pin->DefaultValue; Pin->DefaultValue = Entry.Value; - EntryResult->SetBoolField(TEXT("success"), true); EntryResult->SetStringField(TEXT("oldValue"), OldValue); - EntryResult->SetStringField(TEXT("newValue"), Pin->DefaultValue); SuccessCount++; ModifiedNodes.Add(Node); ModifiedBlueprints.Add(BP); @@ -138,7 +130,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetPinDefault — %d/%d succeeded"), SuccessCount, Pins.Array.Num()); - Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("totalCount"), Pins.Array.Num()); Result->SetArrayField(TEXT("results"), Results); @@ -188,12 +179,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TArray> Results; int32 SuccessCount = 0; @@ -211,11 +199,6 @@ public: continue; } - EntryResult->SetStringField(TEXT("sourceNodeId"), Entry.SourceNode); - EntryResult->SetStringField(TEXT("sourcePinName"), Entry.SourcePinName); - EntryResult->SetStringField(TEXT("targetNodeId"), Entry.TargetNode); - EntryResult->SetStringField(TEXT("targetPinName"), Entry.TargetPinName); - UEdGraph* SourceGraph = nullptr; UEdGraphNode* SourceNode = MCPUtils::FindNodeByGuid(BP, Entry.SourceNode, &SourceGraph); if (!SourceNode) @@ -262,7 +245,6 @@ public: continue; } - EntryResult->SetBoolField(TEXT("success"), true); SuccessCount++; } @@ -273,9 +255,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"), SuccessCount, Connections.Array.Num(), *Blueprint); - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num()); Result->SetArrayField(TEXT("results"), Results); @@ -326,12 +305,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TArray> Results; int32 SuccessCount = 0; @@ -350,9 +326,6 @@ public: continue; } - EntryResult->SetStringField(TEXT("nodeId"), Entry.Node); - EntryResult->SetStringField(TEXT("pinName"), Entry.PinName); - UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); if (!Node) { @@ -403,7 +376,6 @@ public: } } - EntryResult->SetBoolField(TEXT("success"), true); EntryResult->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount); SuccessCount++; TotalDisconnected += DisconnectedCount; @@ -417,8 +389,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DisconnectPin — %d/%d succeeded, %d links broken in '%s'"), SuccessCount, Disconnections.Array.Num(), TotalDisconnected, *Blueprint); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("totalCount"), Disconnections.Array.Num()); Result->SetNumberField(TEXT("totalDisconnected"), TotalDisconnected); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Read.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Read.h index 616d3813..5048a51c 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Read.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Read.h @@ -52,9 +52,14 @@ public: bool bIncludeRegular = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("regular"); bool bIncludeLevel = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("level"); + MCPAssets AllBlueprints; + AllBlueprints.Info(); + MCPAssets AllWorlds; + AllWorlds.Info(); + TArray> Entries; if (bIncludeRegular) - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) + for (const FAssetData& Asset : AllBlueprints.AllData()) { FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); @@ -94,7 +99,7 @@ public: // Also include level blueprints from maps if (bIncludeLevel) - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) + for (const FAssetData& Asset : AllWorlds.AllData()) { FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); @@ -126,7 +131,7 @@ public: } Result->SetNumberField(TEXT("count"), Entries.Num()); - Result->SetNumberField(TEXT("total"), UMCPAssetFinder::GetAssets(UBlueprint::StaticClass()).Num() + UMCPAssetFinder::GetAssets(UWorld::StaticClass()).Num()); + Result->SetNumberField(TEXT("total"), AllBlueprints.AllData().Num() + AllWorlds.AllData().Num()); Result->SetArrayField(TEXT("blueprints"), Entries); } }; @@ -151,10 +156,10 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result); - if (!BP) return; + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - TSharedRef Tmp = MCPUtils::SerializeBlueprint(BP); + TSharedRef Tmp = MCPUtils::SerializeBlueprint(Assets.Object()); MCPUtils::CopyJsonFields(&*Tmp, Result); } }; @@ -185,12 +190,9 @@ public: // URL-decode graph name to handle spaces encoded as '+' or '%20' FString DecodedGraphName = MCPUtils::UrlDecode(Graph); - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); TArray AllGraphs; BP->GetAllGraphs(AllGraphs); @@ -310,8 +312,13 @@ public: } }; + MCPAssets AllBlueprints; + AllBlueprints.Info(); + MCPAssets AllWorlds; + AllWorlds.Info(); + TArray> Results; - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) + for (const FAssetData& Asset : AllBlueprints.AllData()) { if (Results.Num() >= EffectiveMaxResults) break; @@ -328,7 +335,7 @@ public: } // Also search level blueprints - for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) + for (const FAssetData& MapAsset : AllWorlds.AllData()) { if (Results.Num() >= EffectiveMaxResults) break; @@ -352,7 +359,6 @@ public: } } - Result->SetStringField(TEXT("query"), Query); Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetArrayField(TEXT("results"), Results); } @@ -384,12 +390,9 @@ public: { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Blueprint); - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"), *BP->GetName(), @@ -398,7 +401,6 @@ public: // Attempt save with NO modifications bool bSaved = MCPUtils::SaveBlueprintPackage(BP); - Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName()); Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr); Result->SetBoolField(TEXT("saved"), bSaved); @@ -435,8 +437,10 @@ public: Registry.GetReferencers(FName(*AssetPath), Referencers); // Build set of known Blueprint package names for filtering + MCPAssets AllBlueprints; + AllBlueprints.Info(); TSet BlueprintPackages; - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) + for (const FAssetData& Asset : AllBlueprints.AllData()) { BlueprintPackages.Add(Asset.PackageName.ToString()); } @@ -456,7 +460,6 @@ public: } } - Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num()); Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num()); Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs); @@ -680,8 +683,13 @@ public: } }; + MCPAssets AllBlueprints; + AllBlueprints.Info(); + MCPAssets AllWorlds; + AllWorlds.Info(); + // Search regular blueprints - for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) + for (const FAssetData& Asset : AllBlueprints.AllData()) { if (Results.Num() >= EffectiveMaxResults) break; @@ -701,7 +709,7 @@ public: } // Search level blueprints from maps - for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) + for (const FAssetData& MapAsset : AllWorlds.AllData()) { if (Results.Num() >= EffectiveMaxResults) break; @@ -722,7 +730,6 @@ public: SearchOneBlueprint(MapName, AssetPath, LevelBP, true); } - Result->SetStringField(TEXT("typeName"), DecodedTypeName); Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetArrayField(TEXT("results"), Results); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_StateMachine.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_StateMachine.h index bd76f7d5..ac6b0928 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_StateMachine.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_StateMachine.h @@ -55,9 +55,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); // Check for duplicate state name if (MCPUtils::FindStateByName(SMGraph, StateName, nullptr)) @@ -88,8 +90,8 @@ public: if (!AnimationAsset.IsEmpty() && NewState->GetBoundGraph()) { // Try to find the animation asset and create a sequence player in the state's inner graph - FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimationAsset); - UAnimSequence* AnimSeq = FoundAnimAsset ? Cast(FoundAnimAsset->GetAsset()) : nullptr; + MCPAssets AnimAssets; + UAnimSequence* AnimSeq = AnimAssets.Exact(AnimationAsset).ENone().ETwo().Load() ? AnimAssets.Object() : nullptr; if (AnimSeq) { @@ -108,9 +110,6 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("stateName"), StateName); - Result->SetStringField(TEXT("graph"), Graph); Result->SetStringField(TEXT("nodeId"), NewState->NodeGuid.ToString()); Result->SetBoolField(TEXT("saved"), bSaved); } @@ -142,9 +141,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result); if (!StateNode) return; @@ -177,8 +178,6 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("removedState"), StateName); Result->SetNumberField(TEXT("removedTransitions"), RemovedTransitions); Result->SetBoolField(TEXT("saved"), bSaved); } @@ -222,9 +221,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); UAnimStateNode* FromStateNode = MCPUtils::FindStateByName(SMGraph, FromState, Result); if (!FromStateNode) return; @@ -266,13 +267,7 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("fromState"), FromState); - Result->SetStringField(TEXT("toState"), ToState); Result->SetStringField(TEXT("nodeId"), TransNode->NodeGuid.ToString()); - Result->SetNumberField(TEXT("crossfadeDuration"), TransNode->CrossfadeDuration); - Result->SetNumberField(TEXT("priorityOrder"), TransNode->PriorityOrder); - Result->SetBoolField(TEXT("bBidirectional"), TransNode->Bidirectional); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -321,9 +316,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); UAnimStateTransitionNode* TransNode = MCPUtils::FindTransition(SMGraph, FromState, ToState); if (!TransNode) @@ -371,15 +368,7 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("fromState"), FromState); - Result->SetStringField(TEXT("toState"), ToState); Result->SetNumberField(TEXT("propertiesChanged"), ChangedCount); - Result->SetNumberField(TEXT("crossfadeDuration"), TransNode->CrossfadeDuration); - Result->SetNumberField(TEXT("blendMode"), (int32)TransNode->BlendMode); - Result->SetNumberField(TEXT("priorityOrder"), TransNode->PriorityOrder); - Result->SetNumberField(TEXT("logicType"), (int32)TransNode->LogicType.GetValue()); - Result->SetBoolField(TEXT("bBidirectional"), TransNode->Bidirectional); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -413,9 +402,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result); if (!StateNode) return; @@ -427,8 +418,9 @@ public: } // Find the animation asset - UAnimSequence* AnimSeq = UMCPAssetFinder::LoadAsset(AnimationAsset, Result); - if (!AnimSeq) return; + MCPAssets AnimAssets; + if (!AnimAssets.Exact(AnimationAsset).Errors(Result).ENone().ETwo().Load()) return; + UAnimSequence* AnimSeq = AnimAssets.Object(); // Find existing SequencePlayer or create one UAnimGraphNode_SequencePlayer* SeqNode = nullptr; @@ -457,9 +449,6 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("stateName"), StateName); - Result->SetStringField(TEXT("animationAsset"), AnimSeq->GetName()); Result->SetBoolField(TEXT("createdNewNode"), bCreatedNew); Result->SetBoolField(TEXT("saved"), bSaved); } @@ -501,9 +490,11 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); - if (!SMGraph) return; - UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter(); + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(Assets.Object(), Graph); + if (!SMGraph) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *Graph, *Blueprint)); return; } + UAnimBlueprint* AnimBP = Assets.Object(); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result); if (!StateNode) return; @@ -515,8 +506,9 @@ public: } // Find the blend space asset - UBlendSpace* BlendSpaceAsset = UMCPAssetFinder::LoadAsset(BlendSpace, Result); - if (!BlendSpaceAsset) return; + MCPAssets BlendSpaceAssets; + if (!BlendSpaceAssets.Exact(BlendSpace).Errors(Result).ENone().ETwo().Load()) return; + UBlendSpace* BlendSpaceAsset = BlendSpaceAssets.Object(); // Find existing BlendSpacePlayer or create one UAnimGraphNode_BlendSpacePlayer* BSNode = nullptr; @@ -665,9 +657,6 @@ public: FKismetEditorUtilities::CompileBlueprint(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("stateName"), StateName); - Result->SetStringField(TEXT("blendSpace"), BlendSpaceAsset->GetName()); Result->SetStringField(TEXT("nodeId"), BSNode->NodeGuid.ToString()); Result->SetBoolField(TEXT("saved"), bSaved); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_UserTypes.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_UserTypes.h index 8c156f57..ba5f4a7d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_UserTypes.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_UserTypes.h @@ -123,8 +123,6 @@ public: // Save bool bSaved = MCPUtils::SaveGenericPackage(NewStruct); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("assetName"), AssetName); Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded); Result->SetBoolField(TEXT("saved"), bSaved); @@ -203,8 +201,6 @@ public: // Save bool bSaved = MCPUtils::SaveGenericPackage(NewEnum); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("assetName"), AssetName); Result->SetNumberField(TEXT("valueCount"), EnumValues.Num()); Result->SetBoolField(TEXT("saved"), bSaved); @@ -238,8 +234,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Find the struct - UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset(AssetPath, Result); - if (!Struct) return; + MCPAssets Assets; + if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return; + UUserDefinedStruct* Struct = Assets.Object(); // Resolve type FEdGraphPinType PinType; @@ -272,10 +269,6 @@ public: // Save bool bSaved = MCPUtils::SaveGenericPackage(Struct); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("assetPath"), AssetPath); - Result->SetStringField(TEXT("propertyName"), Name); - Result->SetStringField(TEXT("propertyType"), Type); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -304,8 +297,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { // Find the struct - UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset(AssetPath, Result); - if (!Struct) return; + MCPAssets Assets; + if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return; + UUserDefinedStruct* Struct = Assets.Object(); // Find the property GUID by name FGuid TargetGuid; @@ -342,9 +336,6 @@ public: // Save bool bSaved = MCPUtils::SaveGenericPackage(Struct); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("assetPath"), AssetPath); - Result->SetStringField(TEXT("removedProperty"), Name); Result->SetBoolField(TEXT("saved"), bSaved); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Validation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Validation.h index 828fd490..7c581cf6 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Validation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Validation.h @@ -136,21 +136,21 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - TArray MatchingAssets; + MCPAssets Finder; + Finder.Scan().Scan(); if (!Blueprint.IsEmpty()) { - FAssetData* Asset = UMCPAssetFinder::FindAsset(UMCPAssetFinder::BlueprintsAndMaps, Blueprint); - if (!Asset) + if (!Finder.Exact(Blueprint).ETwo().Info() || Finder.AllData().IsEmpty()) { return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blueprint '%s' not found."), *Blueprint)); } - MatchingAssets.Add(Asset); } else { - MatchingAssets = UMCPAssetFinder::SearchAssets(UMCPAssetFinder::BlueprintsAndMaps, Query); + Finder.Substring(Query).Info(); } + const TArray& MatchingAssets = Finder.AllData(); int32 TotalMatching = MatchingAssets.Num(); // countOnly: return count without compiling anything @@ -179,17 +179,18 @@ public: for (int32 Idx = StartIdx; Idx < EndIdx; Idx++) { - FAssetData* Asset = MatchingAssets[Idx]; - FString AssetName = Asset->AssetName.ToString(); - FString PackagePath = Asset->PackageName.ToString(); + const FAssetData& Asset = MatchingAssets[Idx]; + FString AssetName = Asset.AssetName.ToString(); + FString PackagePath = Asset.PackageName.ToString(); // Load the Blueprint (handles both regular and level blueprints) - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(PackagePath, LoadError); - if (!BP) + MCPAssets Loader; + Loader.Scan().Scan(); + if (!Loader.Exact(PackagePath).ENone().ETwo().Load()) { continue; } + UBlueprint* BP = Loader.Object(); TotalChecked++; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Variables.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Variables.h index 2b3f1883..10711784 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Variables.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Variables.h @@ -45,13 +45,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Verify variable exists bool bVarFound = false; @@ -170,9 +166,6 @@ public: if (DryRun) { Result->SetBoolField(TEXT("dryRun"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("variable"), Variable); - Result->SetStringField(TEXT("newType"), NewType); Result->SetStringField(TEXT("typeCategory"), ResolvedTypeCategory); Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num()); Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes); @@ -209,10 +202,6 @@ public: } } - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("variable"), Variable); - Result->SetStringField(TEXT("newType"), NewType); Result->SetStringField(TEXT("typeCategory"), ResolvedTypeCategory); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar); @@ -255,13 +244,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Check for duplicate variable name FName VarFName(*VariableName); @@ -308,15 +293,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"), *VariableName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("variableName"), VariableName); - Result->SetStringField(TEXT("variableType"), VariableType); - if (!Category.IsEmpty()) - { - Result->SetStringField(TEXT("category"), Category); - } - Result->SetBoolField(TEXT("isArray"), IsArray); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -344,13 +320,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find variable by name (case-insensitive) FName VarFName(*VariableName); @@ -392,9 +364,6 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"), *VariableName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("variableName"), VariableName); Result->SetBoolField(TEXT("saved"), bSaved); } }; @@ -441,13 +410,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - // Load Blueprint - FString LoadError; - UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); - if (!BP) - { - return MCPUtils::MakeErrorJson(Result, LoadError); - } + MCPAssets Assets; + if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + UBlueprint* BP = Assets.Object(); // Find the variable FName VarFName(*Variable); @@ -616,9 +581,6 @@ public: FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), Blueprint); - Result->SetStringField(TEXT("variable"), Variable); Result->SetArrayField(TEXT("changes"), Changes); Result->SetBoolField(TEXT("saved"), bSaved); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPAssetFinder.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPAssetFinder.h index 857f9cf1..3af9773b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPAssetFinder.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPAssetFinder.h @@ -6,14 +6,16 @@ #include "StructUtils/UserDefinedStruct.h" #include "Engine/UserDefinedEnum.h" #include "MCPUtils.h" +#include "Engine/Blueprint.h" +#include "Engine/LevelScriptBlueprint.h" +#include "Engine/World.h" #include "MCPAssetFinder.generated.h" - -class UBlueprint; class UMaterial; class UMaterialInstanceConstant; class UMaterialFunction; class UAnimationStateMachineGraph; class IAssetRegistry; +struct FARFilter; /** * Engine subsystem that caches asset registry data for the BlueprintMCP server. @@ -104,3 +106,79 @@ private: // Empty array returned when subsystem is unavailable static const TArray EmptyAssetArray; }; + +// ============================================================ +// MCPAssetsBase — non-template base for MCPAssets +// ============================================================ + +class MCPAssetsBase +{ +public: + MCPAssetsBase(UClass* InTargetClass); + MCPAssetsBase& Scan(UClass* Class) { Classes.Add(Class); return *this; } + template MCPAssetsBase& Scan() { return Scan(T::StaticClass()); } + MCPAssetsBase& Exact(const FString& InName); + MCPAssetsBase& Substring(const FString& InFilter); + MCPAssetsBase& Limit(int32 Count) { MaxResults = Count; return *this; } + MCPAssetsBase& NoDerived(); + MCPAssetsBase& AllContent(); + MCPAssetsBase& Errors(MCPErrorCallback InCB); + MCPAssetsBase& EAny() { bErrorIfAny = true; return *this; } + MCPAssetsBase& ENone() { bErrorIfNone = true; return *this; } + MCPAssetsBase& ETwo() { bErrorIfTwo = true; return *this; } + + bool Load(); + bool Info(); + + const TArray& AllData() const { return AssetResults; } + const FAssetData& OneData() const { return AssetResults[0]; } + +private: + bool Execute(bool bLoad); + void ConfigureFilterClassPaths(FARFilter &Filter); + bool AssetMatches(const FAssetData &Data); + UObject *TryLoadAsset(const FAssetData &Asset); + void SetError(const FString &Msg); + +protected: + UClass* TargetClass; + TArray> Classes; + TArray AssetResults; + TArray UObjectResults; + FString MatchName; + bool bExactMatch = false; + bool bPatternHasSlash = false; + bool bNoDerived = false; + bool bAllContent = false; + bool bErrorIfAny = false; + bool bErrorIfNone = false; + bool bErrorIfTwo = false; + int32 MaxResults = 50; + MCPErrorCallback ErrorCB = MCPErrorCallback(nullptr); +}; + +// ============================================================ +// MCPAssets — builder-pattern asset finder +// +// Usage: +// MCPAssets Assets; +// if (!Assets.Exact(Name).Errors(Result).ENone().ETwo().Load()) return; +// UBlueprint* BP = Assets.Object(); +// +// MCPAssets Materials; +// if (!Materials.Substring(Filter).Limit(100).Load()) return; +// for (UMaterial* Mat : Materials.Objects()) { ... } +// ============================================================ + +template +class MCPAssets : public MCPAssetsBase +{ +public: + MCPAssets() : MCPAssetsBase(T::StaticClass()) {} + + TArrayView Objects() const + { + return TArrayView(reinterpret_cast(UObjectResults.GetData()), UObjectResults.Num()); + } + T* Object() const { return static_cast(UObjectResults[0]); } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index 03aa2cc1..cceff657 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -141,6 +141,7 @@ struct MCPErrorCallback { TFunction Func; + MCPErrorCallback(std::nullptr_t); MCPErrorCallback(FString& OutError); MCPErrorCallback(FJsonObject* Result);