More MCP refactors

This commit is contained in:
2026-03-08 01:47:15 -05:00
parent 4befd070db
commit 0fe0cfa1c2
22 changed files with 661 additions and 777 deletions

View File

@@ -243,6 +243,179 @@ UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOr
return LoadBlueprintOrLevelBlueprint(*Asset, Error); 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<FAssetRegistryModule>("AssetRegistry").Get();
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.1f);
FARFilter Filter;
TArray<FAssetData> 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<FAssetData> 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<UWorld>(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( UAnimationStateMachineGraph* UMCPAssetFinder::LoadAnimStateMachineGraph(
const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error) const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error)
{ {

View File

@@ -59,16 +59,13 @@ public:
// Check if asset already exists // Check if asset already exists
FString FullAssetPath = PackagePath / Name; FString FullAssetPath = PackagePath / Name;
if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name)) MCPAssets<UBlueprint> ExistCheck;
{ if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
// Resolve skeleton // Resolve skeleton
USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset<USkeleton>(Skeleton, Result); MCPAssets<USkeleton> SkeletonAssets;
if (!SkeletonObj) return; if (!SkeletonAssets.Exact(Skeleton).Errors(Result).ENone().ETwo().Load()) return;
USkeleton* SkeletonObj = SkeletonAssets.Object();
// Resolve parent class (default: UAnimInstance) // Resolve parent class (default: UAnimInstance)
UClass* ParentClassObj = UAnimInstance::StaticClass(); UClass* ParentClassObj = UAnimInstance::StaticClass();
@@ -136,13 +133,9 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"),
*Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); *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("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName()); Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName());
Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName());
Result->SetBoolField(TEXT("isAnimBlueprint"), true);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames); Result->SetArrayField(TEXT("graphs"), GraphNames);
} }
@@ -173,8 +166,9 @@ public:
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
} }
UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset<UAnimBlueprint>(Blueprint, Result); MCPAssets<UAnimBlueprint> Assets;
if (!AnimBP) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = Assets.Object();
// Walk all anim nodes to collect slot names // Walk all anim nodes to collect slot names
TSet<FString> SlotNames; TSet<FString> SlotNames;
@@ -208,8 +202,6 @@ public:
SlotsArr.Add(MakeShared<FJsonValueString>(Slot)); SlotsArr.Add(MakeShared<FJsonValueString>(Slot));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetArrayField(TEXT("slots"), SlotsArr); Result->SetArrayField(TEXT("slots"), SlotsArr);
Result->SetNumberField(TEXT("count"), SlotsArr.Num()); Result->SetNumberField(TEXT("count"), SlotsArr.Num());
} }
@@ -240,8 +232,9 @@ public:
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
} }
UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset<UAnimBlueprint>(Blueprint, Result); MCPAssets<UAnimBlueprint> Assets;
if (!AnimBP) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = Assets.Object();
// Walk all anim nodes to collect sync group names // Walk all anim nodes to collect sync group names
TSet<FString> SyncGroupNames; TSet<FString> SyncGroupNames;
@@ -275,8 +268,6 @@ public:
GroupsArr.Add(MakeShared<FJsonValueString>(Group)); GroupsArr.Add(MakeShared<FJsonValueString>(Group));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetArrayField(TEXT("syncGroups"), GroupsArr); Result->SetArrayField(TEXT("syncGroups"), GroupsArr);
Result->SetNumberField(TEXT("count"), GroupsArr.Num()); Result->SetNumberField(TEXT("count"), GroupsArr.Num());
} }
@@ -321,16 +312,13 @@ public:
// Check if asset already exists // Check if asset already exists
FString FullAssetPath = PackagePath / Name; FString FullAssetPath = PackagePath / Name;
if (UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), Name)) MCPAssets<UBlendSpace> ExistCheck;
{ if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
// Resolve skeleton // Resolve skeleton
USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset<USkeleton>(Skeleton, Result); MCPAssets<USkeleton> SkeletonAssets;
if (!SkeletonObj) return; 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'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blend Space '%s' in '%s' with skeleton '%s'"),
*Name, *PackagePath, *SkeletonObj->GetName()); *Name, *PackagePath, *SkeletonObj->GetName());
@@ -360,7 +348,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false")); *Name, bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("skeleton"), SkeletonObj->GetName()); Result->SetStringField(TEXT("skeleton"), SkeletonObj->GetName());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -415,8 +402,9 @@ public:
} }
// Load the blend space // Load the blend space
UBlendSpace* BS = UMCPAssetFinder::LoadAsset<UBlendSpace>(BlendSpace, Result); MCPAssets<UBlendSpace> Assets;
if (!BS) return; if (!Assets.Exact(BlendSpace).Errors(Result).ENone().ETwo().Load()) return;
UBlendSpace* BS = Assets.Object();
// Set axis parameters // Set axis parameters
BS->PreEditChange(nullptr); BS->PreEditChange(nullptr);
@@ -458,11 +446,9 @@ public:
UAnimSequence* AnimSeq = nullptr; UAnimSequence* AnimSeq = nullptr;
if (!AnimAssetName.IsEmpty()) if (!AnimAssetName.IsEmpty())
{ {
FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName); MCPAssets<UAnimSequence> AnimAssets;
if (FoundAnimAsset) if (AnimAssets.Exact(AnimAssetName).Load())
{ AnimSeq = AnimAssets.Object();
AnimSeq = Cast<UAnimSequence>(FoundAnimAsset->GetAsset());
}
} }
FVector SampleValue(X, Y, 0.0f); 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set %d samples on Blend Space '%s' (saved: %s)"),
SamplesSet, *BS->GetName(), bSaved ? TEXT("true") : TEXT("false")); SamplesSet, *BS->GetName(), bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blendSpace"), BS->GetPathName()); Result->SetStringField(TEXT("blendSpace"), BS->GetPathName());
Result->SetNumberField(TEXT("samplesSet"), SamplesSet); Result->SetNumberField(TEXT("samplesSet"), SamplesSet);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);

View File

@@ -152,10 +152,7 @@ public:
} }
} }
Result->SetBoolField(TEXT("success"), bDeleted);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("filename"), PackageFilename); Result->SetStringField(TEXT("filename"), PackageFilename);
Result->SetBoolField(TEXT("forced"), Force);
if (!bDeleted) if (!bDeleted)
{ {
MCPUtils::MakeErrorJson(Result, TEXT("Failed to delete file from disk")); MCPUtils::MakeErrorJson(Result, TEXT("Failed to delete file from disk"));
@@ -201,17 +198,9 @@ public:
TArray<FAssetRenameData> RenameData; TArray<FAssetRenameData> RenameData;
// We need to load the asset to get the object // We need to load the asset to get the object
FAssetData* FoundAsset = UMCPAssetFinder::FindAnyAsset(AssetPath); MCPAssets<UObject> Assets;
if (!FoundAsset) if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return;
{ UObject* AssetObj = Assets.Object();
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));
}
// Parse new path into package path and asset name // Parse new path into package path and asset name
FString NewPackagePath, NewAssetName; FString NewPackagePath, NewAssetName;
@@ -244,9 +233,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Rename %s"), bSuccess ? TEXT("succeeded") : TEXT("failed")); 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("newPackagePath"), NewPackagePath);
Result->SetStringField(TEXT("newAssetName"), NewAssetName); Result->SetStringField(TEXT("newAssetName"), NewAssetName);
if (!bSuccess) if (!bSuccess)

View File

@@ -33,12 +33,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript; USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS) if (!SCS)
@@ -101,7 +98,6 @@ public:
ComponentsArr.Add(MakeShared<FJsonValueObject>(CompObj)); ComponentsArr.Add(MakeShared<FJsonValueObject>(CompObj));
} }
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), ComponentsArr.Num()); Result->SetNumberField(TEXT("count"), ComponentsArr.Num());
Result->SetArrayField(TEXT("components"), ComponentsArr); Result->SetArrayField(TEXT("components"), ComponentsArr);
} }
@@ -137,12 +133,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript; USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS) if (!SCS)
@@ -265,8 +258,6 @@ public:
ParentSCSNode ? *ParentComponent : TEXT("(root)"), ParentSCSNode ? *ParentComponent : TEXT("(root)"),
bSaved ? TEXT("true") : TEXT("false")); 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("component"), NewNode->GetVariableName().ToString());
Result->SetStringField(TEXT("componentClass"), ComponentClassObj->GetName()); Result->SetStringField(TEXT("componentClass"), ComponentClassObj->GetName());
if (ParentSCSNode) if (ParentSCSNode)
@@ -300,12 +291,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript; USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS) if (!SCS)
@@ -368,9 +356,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed component '%s' from '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed component '%s' from '%s' (saved: %s)"),
*Component, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); *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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };

View File

@@ -41,12 +41,13 @@ public:
{ {
// Load both blueprints // Load both blueprints
FString LoadErrorA, LoadErrorB; MCPAssets<UBlueprint> AssetsA;
UBlueprint* BPA = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintA, LoadErrorA); if (!AssetsA.Exact(BlueprintA).Errors(Result).ENone().ETwo().Load()) return;
if (!BPA) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; } UBlueprint* BPA = AssetsA.Object();
UBlueprint* BPB = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(BlueprintB, LoadErrorB); MCPAssets<UBlueprint> AssetsB;
if (!BPB) { MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; } if (!AssetsB.Exact(BlueprintB).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BPB = AssetsB.Object();
// Helper to gather graphs from a Blueprint // Helper to gather graphs from a Blueprint
auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*> auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*>
@@ -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); Result->SetArrayField(TEXT("graphs"), GraphDiffs);
if (VarsOnlyInA.Num() > 0) Result->SetArrayField(TEXT("variablesOnlyInA"), VarsOnlyInA); if (VarsOnlyInA.Num() > 0) Result->SetArrayField(TEXT("variablesOnlyInA"), VarsOnlyInA);

View File

@@ -43,12 +43,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UEdGraph* Graph = nullptr; UEdGraph* Graph = nullptr;
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph);
@@ -78,9 +75,6 @@ public:
return; 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("pinName"), Pin->PinName.ToString());
Result->SetStringField(TEXT("direction"), Pin->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")); Result->SetStringField(TEXT("direction"), Pin->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"));
Result->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString()); Result->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString());
@@ -168,12 +162,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UEdGraph* SourceGraph = nullptr; UEdGraph* SourceGraph = nullptr;
UEdGraphNode* FoundSourceNode = MCPUtils::FindNodeByGuid(BP, SourceNode, &SourceGraph); UEdGraphNode* FoundSourceNode = MCPUtils::FindNodeByGuid(BP, SourceNode, &SourceGraph);
@@ -209,9 +200,6 @@ public:
// Check compatibility using the schema // Check compatibility using the schema
const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); 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); bool bCompatible = (Response.Response != ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW);
Result->SetBoolField(TEXT("compatible"), bCompatible); Result->SetBoolField(TEXT("compatible"), bCompatible);
@@ -371,7 +359,6 @@ public:
ClassList.Add(MakeShared<FJsonValueObject>(ClassObj)); ClassList.Add(MakeShared<FJsonValueObject>(ClassObj));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetNumberField(TEXT("count"), ClassList.Num()); Result->SetNumberField(TEXT("count"), ClassList.Num());
Result->SetNumberField(TEXT("totalMatched"), TotalMatched); Result->SetNumberField(TEXT("totalMatched"), TotalMatched);
if (TotalMatched > Limit) if (TotalMatched > Limit)
@@ -494,7 +481,6 @@ public:
FuncList.Add(MakeShared<FJsonValueObject>(FuncObj)); FuncList.Add(MakeShared<FJsonValueObject>(FuncObj));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetStringField(TEXT("className"), FoundClass->GetName());
Result->SetNumberField(TEXT("count"), FuncList.Num()); Result->SetNumberField(TEXT("count"), FuncList.Num());
Result->SetArrayField(TEXT("functions"), FuncList); Result->SetArrayField(TEXT("functions"), FuncList);
@@ -587,7 +573,6 @@ public:
PropList.Add(MakeShared<FJsonValueObject>(PropObj)); PropList.Add(MakeShared<FJsonValueObject>(PropObj));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetStringField(TEXT("className"), FoundClass->GetName());
Result->SetNumberField(TEXT("count"), PropList.Num()); Result->SetNumberField(TEXT("count"), PropList.Num());
Result->SetArrayField(TEXT("properties"), PropList); Result->SetArrayField(TEXT("properties"), PropList);
@@ -604,6 +589,9 @@ class UMCPHandler_ShowCommands : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
bool Verbose = false;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("List all available commands with their descriptions."); return TEXT("List all available commands with their descriptions.");
@@ -611,7 +599,8 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
TArray<TSharedPtr<FJsonValue>> CommandsArray; // Collect all handler classes sorted by tool name
TArray<TPair<FString, UClass*>> Handlers;
for (TObjectIterator<UClass> It; It; ++It) for (TObjectIterator<UClass> It; It; ++It)
{ {
UClass* Class = *It; UClass* Class = *It;
@@ -620,9 +609,41 @@ public:
if (!Handler) continue; if (!Handler) continue;
const FString& ToolName = Class->GetMetaData(TEXT("ToolName")); const FString& ToolName = Class->GetMetaData(TEXT("ToolName"));
if (ToolName.IsEmpty()) continue; if (ToolName.IsEmpty()) continue;
Handlers.Add({ToolName, Class});
}
Handlers.Sort();
if (!Verbose)
{
// Compact format: "command_name(param1,param2,?optional)"
TArray<TSharedPtr<FJsonValue>> Lines;
for (const auto& Pair : Handlers)
{
FString Line = Pair.Key + TEXT("(");
bool bFirst = true;
for (TFieldIterator<FProperty> 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<FJsonValueString>(Line));
}
Result->SetNumberField(TEXT("count"), Lines.Num());
Result->SetArrayField(TEXT("commands"), Lines);
return;
}
TArray<TSharedPtr<FJsonValue>> CommandsArray;
for (const auto& Pair : Handlers)
{
UClass* Class = Pair.Value;
const IMCPHandler* Handler = Cast<IMCPHandler>(Class->GetDefaultObject());
TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("command"), ToolName); Entry->SetStringField(TEXT("command"), Pair.Key);
Entry->SetStringField(TEXT("description"), Handler->GetDescription()); Entry->SetStringField(TEXT("description"), Handler->GetDescription());
// Document parameters from UPROPERTY fields // Document parameters from UPROPERTY fields

View File

@@ -39,12 +39,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
FName DispatcherFName(*DispatcherName); 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)"), 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")); *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->SetArrayField(TEXT("parameters"), AddedParamsJson);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
@@ -183,12 +177,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TSet<FName> DelegateNameSet; TSet<FName> DelegateNameSet;
FBlueprintEditorUtils::GetDelegateNameList(BP, DelegateNameSet); FBlueprintEditorUtils::GetDelegateNameList(BP, DelegateNameSet);
@@ -236,7 +227,6 @@ public:
DispatchersArr.Add(MakeShared<FJsonValueObject>(DispObj)); DispatchersArr.Add(MakeShared<FJsonValueObject>(DispObj));
} }
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), DispatchersArr.Num()); Result->SetNumberField(TEXT("count"), DispatchersArr.Num());
Result->SetArrayField(TEXT("dispatchers"), DispatchersArr); Result->SetArrayField(TEXT("dispatchers"), DispatchersArr);
} }

View File

@@ -38,12 +38,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None"); 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 not found as C++ class, try loading as a Blueprint asset
if (!NewParentClassObj) if (!NewParentClassObj)
{ {
FString ParentLoadError; MCPAssets<UBlueprint> ParentAssets;
UBlueprint* ParentBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(NewParentClass, ParentLoadError); if (!ParentAssets.Exact(NewParentClass).AllContent().Errors(Result).ETwo().Load()) return;
if (ParentBP && ParentBP->GeneratedClass) 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"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"),
bSaved ? TEXT("succeeded") : TEXT("failed")); bSaved ? TEXT("succeeded") : TEXT("failed"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("oldParentClass"), OldParentName); Result->SetStringField(TEXT("oldParentClass"), OldParentName);
Result->SetStringField(TEXT("newParentClass"), NewParentActualName); Result->SetStringField(TEXT("newParentClass"), NewParentActualName);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -154,12 +150,8 @@ public:
// Check if asset already exists // Check if asset already exists
FString FullAssetPath = PackagePath / Blueprint; FString FullAssetPath = PackagePath / Blueprint;
if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Blueprint) || UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), FullAssetPath)) MCPAssets<UBlueprint> ExistCheck;
{ if (!ExistCheck.Exact(Blueprint).Errors(Result).EAny().Info()) return;
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
*Blueprint));
}
// Resolve parent class — try C++ class first, then Blueprint // Resolve parent class — try C++ class first, then Blueprint
UClass* ParentClassObj = nullptr; UClass* ParentClassObj = nullptr;
@@ -175,11 +167,12 @@ public:
if (!ParentClassObj) if (!ParentClassObj)
{ {
FString ParentLoadError; MCPAssets<UBlueprint> ParentAssets;
UBlueprint* ParentBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(ParentClass, ParentLoadError); if (!ParentAssets.Exact(ParentClass).AllContent().Errors(Result).ETwo().Load()) return;
if (ParentBP && ParentBP->GeneratedClass) 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"),
*Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); *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("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName());
Result->SetStringField(TEXT("blueprintType"), BlueprintType.IsEmpty() ? TEXT("Normal") : *BlueprintType);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames); Result->SetArrayField(TEXT("graphs"), GraphNames);
} }
@@ -316,12 +305,9 @@ public:
} }
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Check graph name uniqueness // Check graph name uniqueness
TArray<UEdGraph*> AllGraphs; TArray<UEdGraph*> AllGraphs;
@@ -410,10 +396,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"),
*GraphType, *Graph, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); *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); Result->SetBoolField(TEXT("saved"), bSaved);
if (!CreatedNodeId.IsEmpty()) if (!CreatedNodeId.IsEmpty())
{ {
@@ -445,12 +427,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the graph // Find the graph
UEdGraph* TargetGraph = nullptr; UEdGraph* TargetGraph = nullptr;
@@ -508,9 +487,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"),
*Graph, NodeCount, bSaved ? TEXT("true") : TEXT("false")); *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->SetStringField(TEXT("graphType"), GraphType);
Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetNumberField(TEXT("nodeCount"), NodeCount);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -543,12 +519,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Check if it's an UbergraphPage — disallow rename // Check if it's an UbergraphPage — disallow rename
for (UEdGraph* CandidateGraph : BP->UbergraphPages) for (UEdGraph* CandidateGraph : BP->UbergraphPages)
@@ -615,9 +588,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"),
*Graph, *NewName, bSaved ? TEXT("true") : TEXT("false")); *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("newName"), TargetGraph->GetName());
Result->SetStringField(TEXT("graphType"), GraphType); Result->SetStringField(TEXT("graphType"), GraphType);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);

View File

@@ -33,12 +33,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<TSharedPtr<FJsonValue>> InterfacesArr; TArray<TSharedPtr<FJsonValue>> InterfacesArr;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
@@ -65,7 +62,6 @@ public:
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj)); InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
} }
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), InterfacesArr.Num()); Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
Result->SetArrayField(TEXT("interfaces"), InterfacesArr); Result->SetArrayField(TEXT("interfaces"), InterfacesArr);
} }
@@ -96,12 +92,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Resolve the interface class // Resolve the interface class
UClass* InterfaceClass = nullptr; UClass* InterfaceClass = nullptr;
@@ -137,11 +130,13 @@ public:
// Strategy 2: Try loading as a Blueprint Interface asset // Strategy 2: Try loading as a Blueprint Interface asset
if (!InterfaceClass) if (!InterfaceClass)
{ {
FString IfaceLoadError; MCPAssets<UBlueprint> IfaceAssets;
UBlueprint* IfaceBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(InterfaceName, IfaceLoadError); if (!IfaceAssets.Exact(InterfaceName).AllContent().Errors(Result).ETwo().Load()) return;
if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass())) 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"),
*InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num()); *InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num());
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName()); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
@@ -241,12 +234,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the interface in ImplementedInterfaces by name (case-insensitive) // Find the interface in ImplementedInterfaces by name (case-insensitive)
UClass* FoundInterface = nullptr; UClass* FoundInterface = nullptr;
@@ -308,9 +298,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"),
*FoundInterface->GetName(), *Blueprint); *FoundInterface->GetName(), *Blueprint);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions);
} }
}; };

View File

@@ -50,30 +50,25 @@ public:
} }
// Check if asset already exists // 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( MCPAssets<UMaterialInstanceConstant> ExistCheck;
TEXT("Material Instance '%s' already exists. Use a different name or delete the existing asset first."), if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
*Name));
} }
// Load parent material — try as Material first, then as Material Instance // Load parent material — try as Material first, then as Material Instance
UMaterialInterface* ParentMaterialObj = nullptr; UMaterialInterface* ParentMaterialObj = nullptr;
{ {
FString LoadError; MCPAssets<UMaterial> MatAssets;
UMaterial* ParentMat = UMCPAssetFinder::LoadAsset<UMaterial>(ParentMaterial, LoadError); if (MatAssets.Exact(ParentMaterial).ETwo().Load() && !MatAssets.Objects().IsEmpty())
if (ParentMat)
{ {
ParentMaterialObj = ParentMat; ParentMaterialObj = MatAssets.Object();
} }
else else
{ {
FString MILoadError; MCPAssets<UMaterialInstanceConstant> MIAssets;
UMaterialInstanceConstant* ParentMI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(ParentMaterial, MILoadError); if (MIAssets.Exact(ParentMaterial).ETwo().Load() && !MIAssets.Objects().IsEmpty())
if (ParentMI)
{ {
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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Instance '%s' with parent '%s' (saved: %s)"),
*Name, *ParentMaterialObj->GetName(), bSaved ? TEXT("true") : TEXT("false")); *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("path"), MI->GetPathName());
Result->SetStringField(TEXT("parent"), ParentMaterialObj->GetPathName()); Result->SetStringField(TEXT("parent"), ParentMaterialObj->GetPathName());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -169,8 +162,9 @@ public:
} }
// Load the Material Instance // Load the Material Instance
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MaterialInstance, Result); MCPAssets<UMaterialInstanceConstant> Assets;
if (!MI) return; if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return;
UMaterialInstanceConstant* MI = Assets.Object();
// Determine the parameter type — explicit or auto-detect from parent // Determine the parameter type — explicit or auto-detect from parent
FString TypeStr = Type; FString TypeStr = Type;
@@ -359,9 +353,6 @@ public:
DryRun ? TEXT("[DRY RUN] Would set") : TEXT("Set"), DryRun ? TEXT("[DRY RUN] Would set") : TEXT("Set"),
*ParameterName, *NewValueDescription, *MaterialInstance); *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("type"), TypeStr);
Result->SetStringField(TEXT("newValue"), NewValueDescription); Result->SetStringField(TEXT("newValue"), NewValueDescription);
if (DryRun) if (DryRun)
@@ -391,8 +382,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MaterialInstance, Result); MCPAssets<UMaterialInstanceConstant> Assets;
if (!MI) return; if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return;
UMaterialInstanceConstant* MI = Assets.Object();
Result->SetStringField(TEXT("name"), MI->GetName()); Result->SetStringField(TEXT("name"), MI->GetName());
Result->SetStringField(TEXT("path"), MI->GetPathName()); Result->SetStringField(TEXT("path"), MI->GetPathName());
@@ -643,8 +635,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load the Material Instance // Load the Material Instance
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MaterialInstance, Result); MCPAssets<UMaterialInstanceConstant> Assets;
if (!MI) return; if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return;
UMaterialInstanceConstant* MI = Assets.Object();
// Capture old parent // Capture old parent
FString OldParentPath = MI->Parent ? MI->Parent->GetPathName() : TEXT("None"); 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 // Load new parent — try as Material first, then as Material Instance
UMaterialInterface* NewParentObj = nullptr; UMaterialInterface* NewParentObj = nullptr;
{ {
FString MatLoadError; MCPAssets<UMaterial> MatAssets;
UMaterial* NewParentMat = UMCPAssetFinder::LoadAsset<UMaterial>(NewParent, MatLoadError); if (MatAssets.Exact(NewParent).ETwo().Load() && !MatAssets.Objects().IsEmpty())
if (NewParentMat)
{ {
NewParentObj = NewParentMat; NewParentObj = MatAssets.Object();
} }
else else
{ {
FString MILoadError; MCPAssets<UMaterialInstanceConstant> MIAssets;
UMaterialInstanceConstant* NewParentMI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(NewParent, MILoadError); if (MIAssets.Exact(NewParent).ETwo().Load() && !MIAssets.Objects().IsEmpty())
if (NewParentMI)
{ {
NewParentObj = NewParentMI; NewParentObj = MIAssets.Object();
} }
} }
} }
@@ -721,8 +712,6 @@ public:
*MaterialInstance, bSaved ? TEXT("true") : TEXT("false")); *MaterialInstance, bSaved ? TEXT("true") : TEXT("false"));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("materialInstance"), MaterialInstance);
Result->SetStringField(TEXT("oldParent"), OldParentPath); Result->SetStringField(TEXT("oldParent"), OldParentPath);
Result->SetStringField(TEXT("newParent"), NewParentObj->GetPathName()); Result->SetStringField(TEXT("newParent"), NewParentObj->GetPathName());
if (DryRun) if (DryRun)

View File

@@ -92,13 +92,8 @@ public:
} }
// Check if asset already exists // Check if asset already exists
FString FullAssetPath = PackagePath / Name; MCPAssets<UMaterial> ExistCheck;
if (UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), FullAssetPath)) if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Material '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Material '%s' in '%s'"), *Name, *PackagePath); 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false")); *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("path"), MaterialObj->GetPathName());
Result->SetStringField(TEXT("domain"), DomainToString(MaterialObj->MaterialDomain)); Result->SetStringField(TEXT("domain"), DomainToString(MaterialObj->MaterialDomain));
Result->SetStringField(TEXT("blendMode"), BlendModeToString(MaterialObj->BlendMode)); Result->SetStringField(TEXT("blendMode"), BlendModeToString(MaterialObj->BlendMode));
@@ -240,8 +233,9 @@ public:
} }
// Load material // Load material
UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> Assets;
if (!MaterialObj) return; if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
UMaterial* MaterialObj = Assets.Object();
FString OldValue; FString OldValue;
FString NewValue; FString NewValue;
@@ -490,9 +484,7 @@ public:
DryRun ? TEXT("[DRY RUN] ") : TEXT(""), DryRun ? TEXT("[DRY RUN] ") : TEXT(""),
*Property, *Material, *OldValue, *NewValue); *Property, *Material, *OldValue, *NewValue);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), MaterialObj->GetName()); Result->SetStringField(TEXT("material"), MaterialObj->GetName());
Result->SetStringField(TEXT("property"), Property);
Result->SetStringField(TEXT("oldValue"), OldValue); Result->SetStringField(TEXT("oldValue"), OldValue);
Result->SetStringField(TEXT("newValue"), NewValue); Result->SetStringField(TEXT("newValue"), NewValue);
Result->SetBoolField(TEXT("dryRun"), DryRun); Result->SetBoolField(TEXT("dryRun"), DryRun);
@@ -593,15 +585,17 @@ public:
{ {
return MCPUtils::MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both")); return MCPUtils::MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both"));
} }
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
Owner = MatFunc; Owner = MatFunc;
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
Owner = MaterialObj; Owner = MaterialObj;
AssetDisplayName = MaterialObj->GetName(); 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would add expression '%s' to '%s' at (%d, %d)"),
*ExpressionClass, *AssetDisplayName, PosX, PosY); *ExpressionClass, *AssetDisplayName, PosX, PosY);
Result->SetBoolField(TEXT("success"), true);
Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("expressionClass"), ExpressionClass);
Result->SetNumberField(TEXT("posX"), PosX);
Result->SetNumberField(TEXT("posY"), PosY);
return; return;
} }
@@ -687,12 +677,8 @@ public:
// Serialize the expression details // Serialize the expression details
TSharedPtr<FJsonObject> ExprDetails = MCPUtils::SerializeMaterialExpression(NewExpr); TSharedPtr<FJsonObject> ExprDetails = MCPUtils::SerializeMaterialExpression(NewExpr);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("expressionClass"), ExpressionClass);
Result->SetStringField(TEXT("nodeId"), NodeGuid); Result->SetStringField(TEXT("nodeId"), NodeGuid);
Result->SetNumberField(TEXT("posX"), PosX);
Result->SetNumberField(TEXT("posY"), PosY);
if (ExprDetails.IsValid()) if (ExprDetails.IsValid())
{ {
Result->SetObjectField(TEXT("expression"), ExprDetails); Result->SetObjectField(TEXT("expression"), ExprDetails);
@@ -742,14 +728,16 @@ public:
if (!MaterialFunction.IsEmpty()) if (!MaterialFunction.IsEmpty())
{ {
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
AssetDisplayName = MaterialObj->GetName(); AssetDisplayName = MaterialObj->GetName();
} }
@@ -792,10 +780,8 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would delete expression '%s' (nodeId: %s) from '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would delete expression '%s' (nodeId: %s) from '%s'"),
*DeletedExprClass, *Node, *AssetDisplayName); *DeletedExprClass, *Node, *AssetDisplayName);
Result->SetBoolField(TEXT("success"), true);
Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("deletedNode"), Node);
Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle);
Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass);
return; return;
@@ -827,9 +813,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted expression '%s' (nodeId: %s) from '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted expression '%s' (nodeId: %s) from '%s' (saved: %s)"),
*DeletedExprClass, *Node, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false")); *DeletedExprClass, *Node, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("deletedNode"), Node);
Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle);
Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -886,14 +870,16 @@ public:
if (!MaterialFunction.IsEmpty()) if (!MaterialFunction.IsEmpty())
{ {
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
AssetDisplayName = MaterialObj->GetName(); AssetDisplayName = MaterialObj->GetName();
} }
@@ -967,9 +953,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would connect %s.%s -> %s.%s in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would connect %s.%s -> %s.%s in '%s'"),
*SourceNode, *SourcePinName, *TargetNode, *TargetPinName, *AssetDisplayName); *SourceNode, *SourcePinName, *TargetNode, *TargetPinName, *AssetDisplayName);
Result->SetBoolField(TEXT("success"), true);
Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("dryRun"), true);
Result->SetBoolField(TEXT("connected"), false);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
return; return;
} }
@@ -986,8 +970,6 @@ public:
bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin); bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin);
Result->SetBoolField(TEXT("success"), bConnected);
Result->SetBoolField(TEXT("connected"), bConnected);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
if (!bConnected) if (!bConnected)
@@ -1050,14 +1032,16 @@ public:
if (!MaterialFunction.IsEmpty()) if (!MaterialFunction.IsEmpty())
{ {
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
AssetDisplayName = MaterialObj->GetName(); 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would disconnect pin '%s' on node '%s' in '%s' (%d links)"),
*PinName, *Node, *AssetDisplayName, BrokenCount); *PinName, *Node, *AssetDisplayName, BrokenCount);
Result->SetBoolField(TEXT("success"), true);
Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("nodeId"), Node);
Result->SetStringField(TEXT("pinName"), PinName);
Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount);
return; return;
} }
@@ -1131,10 +1112,7 @@ public:
// Save // Save
bool bSaved = MaterialObj ? MCPUtils::SaveMaterialPackage(MaterialObj) : MCPUtils::SaveGenericPackage(MatFunc); bool bSaved = MaterialObj ? MCPUtils::SaveMaterialPackage(MaterialObj) : MCPUtils::SaveGenericPackage(MatFunc);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("nodeId"), Node);
Result->SetStringField(TEXT("pinName"), PinName);
Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
@@ -1184,14 +1162,16 @@ public:
if (!MaterialFunction.IsEmpty()) if (!MaterialFunction.IsEmpty())
{ {
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
AssetDisplayName = MaterialObj->GetName(); AssetDisplayName = MaterialObj->GetName();
} }
@@ -1414,9 +1394,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set expression value on node '%s' (%s) in '%s': %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set expression value on node '%s' (%s) in '%s': %s"),
*Node, *ExprType, *AssetDisplayName, *NewValueStr); *Node, *ExprType, *AssetDisplayName, *NewValueStr);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("nodeId"), Node);
Result->SetStringField(TEXT("expressionType"), ExprType); Result->SetStringField(TEXT("expressionType"), ExprType);
Result->SetStringField(TEXT("newValue"), NewValueStr); Result->SetStringField(TEXT("newValue"), NewValueStr);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -1470,14 +1448,16 @@ public:
if (!MaterialFunction.IsEmpty()) if (!MaterialFunction.IsEmpty())
{ {
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunction, Result); MCPAssets<UMaterialFunction> MFAssets;
if (!MatFunc) return; if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
AssetDisplayName = MatFunc->GetName(); AssetDisplayName = MatFunc->GetName();
} }
else else
{ {
MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> MatAssets;
if (!MaterialObj) return; if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
AssetDisplayName = MaterialObj->GetName(); 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'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would move node '%s' to (%d, %d) in '%s'"),
*Node, PosX, PosY, *AssetDisplayName); *Node, PosX, PosY, *AssetDisplayName);
Result->SetBoolField(TEXT("success"), true);
Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("material"), AssetDisplayName);
Result->SetStringField(TEXT("nodeId"), Node);
Result->SetNumberField(TEXT("posX"), PosX);
Result->SetNumberField(TEXT("posY"), PosY);
return; return;
} }
@@ -1540,11 +1516,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Moved node '%s' to (%d, %d) in '%s' (saved: %s)"), 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")); *Node, PosX, PosY, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), AssetDisplayName); 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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -1581,13 +1553,8 @@ public:
} }
// Check if asset already exists // Check if asset already exists
FString FullAssetPath = PackagePath / Name; MCPAssets<UMaterialFunction> ExistCheck;
if (UMCPAssetFinder::FindAsset(UMaterialFunction::StaticClass(), Name) || UMCPAssetFinder::FindAsset(UMaterialFunction::StaticClass(), FullAssetPath)) if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Material Function '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Material Function '%s' in '%s'"), *Name, *PackagePath); 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)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Function '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false")); *Name, bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("name"), Name);
Result->SetStringField(TEXT("path"), MF->GetPathName()); Result->SetStringField(TEXT("path"), MF->GetPathName());
if (!Description.IsEmpty())
{
Result->SetStringField(TEXT("description"), Description);
}
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };

View File

@@ -61,60 +61,24 @@ public:
bool bIncludeMaterials = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("material"); bool bIncludeMaterials = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("material");
bool bIncludeInstances = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("instance"); bool bIncludeInstances = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("instance");
MCPAssets<UMaterial> Assets;
if (bIncludeMaterials) Assets.Scan(UMaterial::StaticClass());
if (bIncludeInstances) Assets.Scan(UMaterialInstanceConstant::StaticClass());
Assets.Substring(Filter).NoDerived().Info();
TArray<TSharedPtr<FJsonValue>> Entries; TArray<TSharedPtr<FJsonValue>> Entries;
for (const FAssetData& Asset : Assets.AllData())
if (bIncludeMaterials)
{ {
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterial::StaticClass())) TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
{ Entry->SetStringField(TEXT("name"), Asset.AssetName.ToString());
FString Name = Asset.AssetName.ToString(); Entry->SetStringField(TEXT("path"), Asset.PackageName.ToString());
FString Path = Asset.PackageName.ToString(); Entry->SetStringField(TEXT("type"),
Asset.AssetClassPath.GetAssetName() == TEXT("MaterialInstanceConstant")
if (!Filter.IsEmpty()) ? TEXT("MaterialInstance") : TEXT("Material"));
{ Entries.Add(MakeShared<FJsonValueObject>(Entry));
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
}
TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("name"), Name);
Entry->SetStringField(TEXT("path"), Path);
Entry->SetStringField(TEXT("type"), TEXT("Material"));
Entries.Add(MakeShared<FJsonValueObject>(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<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("name"), Name);
Entry->SetStringField(TEXT("path"), Path);
Entry->SetStringField(TEXT("type"), TEXT("MaterialInstance"));
Entries.Add(MakeShared<FJsonValueObject>(Entry));
}
}
int32 Total = UMCPAssetFinder::GetAssets(UMaterial::StaticClass()).Num() + UMCPAssetFinder::GetAssets(UMaterialInstanceConstant::StaticClass()).Num();
Result->SetNumberField(TEXT("count"), Entries.Num()); Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), Total);
Result->SetArrayField(TEXT("materials"), Entries); Result->SetArrayField(TEXT("materials"), Entries);
} }
}; };
@@ -141,10 +105,14 @@ public:
{ {
FString DecodedName = MCPUtils::UrlDecode(Material); FString DecodedName = MCPUtils::UrlDecode(Material);
// Try loading as UMaterial first // Try loading as UMaterial or UMaterialInstanceConstant
FString LoadError; MCPAssets<UMaterialInterface> Assets;
UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, LoadError); Assets.Scan(UMaterial::StaticClass());
if (MaterialObj) Assets.Scan(UMaterialInstanceConstant::StaticClass());
if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return;
UMaterialInterface* LoadedObj = Assets.Object();
if (UMaterial* MaterialObj = Cast<UMaterial>(LoadedObj))
{ {
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *MaterialObj->GetName()); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *MaterialObj->GetName());
@@ -296,10 +264,7 @@ public:
return; return;
} }
// Try loading as MaterialInstance if (UMaterialInstanceConstant* MI = Cast<UMaterialInstanceConstant>(LoadedObj))
FString MILoadError;
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(DecodedName, MILoadError);
if (MI)
{ {
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName()); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName());
@@ -396,8 +361,9 @@ public:
{ {
FString DecodedName = MCPUtils::UrlDecode(Material); FString DecodedName = MCPUtils::UrlDecode(Material);
UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, Result); MCPAssets<UMaterial> Assets;
if (!MaterialObj) return; 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()); 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 virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> Assets;
if (!MaterialObj) return; if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
UMaterial* MaterialObj = Assets.Object();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *MaterialObj->GetName()); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *MaterialObj->GetName());
@@ -613,7 +580,6 @@ public:
InputDescriptions.Add(MakeShared<FJsonValueObject>(InputObj)); InputDescriptions.Add(MakeShared<FJsonValueObject>(InputObj));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), MaterialObj->GetName()); Result->SetStringField(TEXT("material"), MaterialObj->GetName());
Result->SetStringField(TEXT("materialPath"), MaterialObj->GetPathName()); Result->SetStringField(TEXT("materialPath"), MaterialObj->GetPathName());
Result->SetArrayField(TEXT("inputs"), InputDescriptions); Result->SetArrayField(TEXT("inputs"), InputDescriptions);
@@ -668,7 +634,10 @@ public:
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterial::StaticClass())) MCPAssets<UMaterial> AllMaterials;
AllMaterials.Info();
for (const FAssetData& Asset : AllMaterials.AllData())
{ {
if (Results.Num() >= MaxResults) break; if (Results.Num() >= MaxResults) break;
@@ -757,27 +726,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Try to find the material's package path // Try to find the material's package path (search both Material and MaterialInstance)
FString PackagePath; MCPAssets<UMaterial> Assets;
FAssetData* MatAsset = UMCPAssetFinder::FindAsset(UMaterial::StaticClass(), Material); Assets.Scan<UMaterial>().Scan<UMaterialInstanceConstant>();
if (MatAsset) if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Info()) return;
{ FString PackagePath = Assets.OneData().PackageName.ToString();
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));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindMaterialReferences — '%s' (package: %s)"), *Material, *PackagePath); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindMaterialReferences — '%s' (package: %s)"), *Material, *PackagePath);
@@ -795,8 +748,6 @@ public:
RefArray.Add(MakeShared<FJsonValueString>(RefStr)); RefArray.Add(MakeShared<FJsonValueString>(RefStr));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), Material);
Result->SetStringField(TEXT("packagePath"), PackagePath); Result->SetStringField(TEXT("packagePath"), PackagePath);
Result->SetNumberField(TEXT("totalReferencers"), RefArray.Num()); Result->SetNumberField(TEXT("totalReferencers"), RefArray.Num());
Result->SetArrayField(TEXT("referencers"), RefArray); Result->SetArrayField(TEXT("referencers"), RefArray);
@@ -823,30 +774,19 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UMaterialFunction> Assets;
Assets.Substring(Filter).Info();
TArray<TSharedPtr<FJsonValue>> Entries; TArray<TSharedPtr<FJsonValue>> Entries;
for (const FAssetData& Asset : Assets.AllData())
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UMaterialFunction::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<FJsonObject> Entry = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> Entry = MakeShared<FJsonObject>();
Entry->SetStringField(TEXT("name"), Name); Entry->SetStringField(TEXT("name"), Asset.AssetName.ToString());
Entry->SetStringField(TEXT("path"), Path); Entry->SetStringField(TEXT("path"), Asset.PackageName.ToString());
Entries.Add(MakeShared<FJsonValueObject>(Entry)); Entries.Add(MakeShared<FJsonValueObject>(Entry));
} }
Result->SetNumberField(TEXT("count"), Entries.Num()); Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), UMCPAssetFinder::GetAssets(UMaterialFunction::StaticClass()).Num());
Result->SetArrayField(TEXT("functions"), Entries); Result->SetArrayField(TEXT("functions"), Entries);
} }
}; };
@@ -873,8 +813,9 @@ public:
{ {
FString DecodedName = MCPUtils::UrlDecode(MaterialFunction); FString DecodedName = MCPUtils::UrlDecode(MaterialFunction);
UMaterialFunction* MF = UMCPAssetFinder::LoadAsset<UMaterialFunction>(DecodedName, Result); MCPAssets<UMaterialFunction> Assets;
if (!MF) return; if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return;
UMaterialFunction* MF = Assets.Object();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName()); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName());
@@ -962,8 +903,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load material // Load material
UMaterial* MaterialObj = UMCPAssetFinder::LoadAsset<UMaterial>(Material, Result); MCPAssets<UMaterial> Assets;
if (!MaterialObj) return; 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()); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *MaterialObj->GetName());

View File

@@ -90,12 +90,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0; int32 SuccessCount = 0;
@@ -113,8 +110,6 @@ public:
continue; continue;
} }
EntryResult->SetStringField(TEXT("nodeId"), Entry.Node);
UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node);
if (!Node) if (!Node)
{ {
@@ -126,7 +121,6 @@ public:
int32 OldY = Node->NodePosY; int32 OldY = Node->NodePosY;
Node->NodePosX = Entry.X; Node->NodePosX = Entry.X;
Node->NodePosY = Entry.Y; Node->NodePosY = Entry.Y;
EntryResult->SetBoolField(TEXT("success"), true);
EntryResult->SetNumberField(TEXT("oldX"), OldX); EntryResult->SetNumberField(TEXT("oldX"), OldX);
EntryResult->SetNumberField(TEXT("oldY"), OldY); EntryResult->SetNumberField(TEXT("oldY"), OldY);
EntryResult->SetNumberField(TEXT("newX"), Node->NodePosX); EntryResult->SetNumberField(TEXT("newX"), Node->NodePosX);
@@ -139,8 +133,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: MoveNode — %d/%d succeeded"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: MoveNode — %d/%d succeeded"),
SuccessCount, Nodes.Array.Num()); SuccessCount, Nodes.Array.Num());
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("movedCount"), SuccessCount); Result->SetNumberField(TEXT("movedCount"), SuccessCount);
Result->SetNumberField(TEXT("totalRequested"), Nodes.Array.Num()); Result->SetNumberField(TEXT("totalRequested"), Nodes.Array.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
@@ -182,12 +174,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the target graph // Find the target graph
FString DecodedGraphName = MCPUtils::UrlDecode(Graph); FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
@@ -294,9 +283,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Duplicated %d node(s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Duplicated %d node(s)"),
DuplicatedNodes.Num()); DuplicatedNodes.Num());
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("graph"), DecodedGraphName);
Result->SetNumberField(TEXT("duplicatedCount"), DuplicatedNodes.Num()); Result->SetNumberField(TEXT("duplicatedCount"), DuplicatedNodes.Num());
Result->SetArrayField(TEXT("nodes"), DuplicatedNodes); Result->SetArrayField(TEXT("nodes"), DuplicatedNodes);
@@ -358,12 +344,9 @@ public:
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the target graph // Find the target graph
FString DecodedGraphName = MCPUtils::UrlDecode(Graph); FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
@@ -408,8 +391,6 @@ public:
continue; continue;
} }
EntryResult->SetStringField(TEXT("actionName"), Entry.ActionName);
// Find the spawner by exact full name // Find the spawner by exact full name
TArray<UBlueprintNodeSpawner*> Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true, TargetGraph); TArray<UBlueprintNodeSpawner*> Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true, TargetGraph);
if (Matches.Num() == 0) if (Matches.Num() == 0)
@@ -455,7 +436,6 @@ public:
// Serialize result // Serialize result
TSharedPtr<FJsonObject> NodeState = MCPUtils::SerializeNode(NewNode); TSharedPtr<FJsonObject> NodeState = MCPUtils::SerializeNode(NewNode);
EntryResult->SetBoolField(TEXT("success"), true);
EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString()); EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString());
EntryResult->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); EntryResult->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName());
EntryResult->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView).ToString()); 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'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SpawnNode — %d/%d succeeded in graph '%s' of '%s'"),
SuccessCount, Nodes.Array.Num(), *DecodedGraphName, *Blueprint); 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("successCount"), SuccessCount);
Result->SetNumberField(TEXT("totalCount"), Nodes.Array.Num()); Result->SetNumberField(TEXT("totalCount"), Nodes.Array.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
@@ -504,12 +481,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node);
if (!FoundNode) if (!FoundNode)
@@ -517,9 +491,6 @@ public:
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node)); 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->SetStringField(TEXT("comment"), FoundNode->NodeComment);
Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible); Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible);
} }
@@ -552,12 +523,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node);
if (!FoundNode) if (!FoundNode)
@@ -580,11 +548,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"),
*Node, *Blueprint); *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("oldComment"), OldComment);
Result->SetStringField(TEXT("newComment"), Comment);
} }
}; };
@@ -613,12 +577,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UEdGraph* Graph = nullptr; UEdGraph* Graph = nullptr;
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph);
@@ -671,9 +632,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Node deleted")); 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("nodeClass"), NodeClass);
Result->SetStringField(TEXT("nodeTitle"), NodeTitle); Result->SetStringField(TEXT("nodeTitle"), NodeTitle);
Result->SetStringField(TEXT("graph"), GraphName); Result->SetStringField(TEXT("graph"), GraphName);
@@ -712,12 +670,9 @@ public:
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the new class — try several search strategies // Find the new class — try several search strategies
UClass* NewClassPtr = nullptr; UClass* NewClassPtr = nullptr;
@@ -913,8 +868,6 @@ public:
if (DryRun) if (DryRun)
{ {
Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("wouldReplaceCount"), ReplacedCount); Result->SetNumberField(TEXT("wouldReplaceCount"), ReplacedCount);
Result->SetNumberField(TEXT("connectionsAtRisk"), BrokenConnections.Num()); Result->SetNumberField(TEXT("connectionsAtRisk"), BrokenConnections.Num());
Result->SetArrayField(TEXT("connectionsAtRisk"), BrokenConnections); Result->SetArrayField(TEXT("connectionsAtRisk"), BrokenConnections);
@@ -927,14 +880,12 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Replaced %d function call(s)"), ReplacedCount); 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("replacedCount"), ReplacedCount);
Result->SetNumberField(TEXT("brokenConnectionCount"), BrokenConnections.Num()); Result->SetNumberField(TEXT("brokenConnectionCount"), BrokenConnections.Num());
Result->SetArrayField(TEXT("brokenConnections"), BrokenConnections); Result->SetArrayField(TEXT("brokenConnections"), BrokenConnections);
return; return;
} }
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("replacedCount"), 0); Result->SetNumberField(TEXT("replacedCount"), 0);
Result->SetStringField(TEXT("message"), FString::Printf( Result->SetStringField(TEXT("message"), FString::Printf(
TEXT("No function call nodes found targeting class '%s'"), *OldClass)); TEXT("No function call nodes found targeting class '%s'"), *OldClass));
@@ -964,12 +915,9 @@ public:
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Count graphs and nodes before refresh // Count graphs and nodes before refresh
TArray<UEdGraph*> AllGraphs; TArray<UEdGraph*> AllGraphs;
@@ -1053,8 +1001,6 @@ public:
} }
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("graphCount"), GraphCount); Result->SetNumberField(TEXT("graphCount"), GraphCount);
Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetNumberField(TEXT("nodeCount"), NodeCount);
Result->SetNumberField(TEXT("orphanedPinsRemoved"), OrphanedPinsRemoved); Result->SetNumberField(TEXT("orphanedPinsRemoved"), OrphanedPinsRemoved);
@@ -1092,12 +1038,9 @@ public:
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find node // Find node
UEdGraph* Graph = nullptr; UEdGraph* Graph = nullptr;
@@ -1281,9 +1224,6 @@ public:
// Return updated node state // Return updated node state
TSharedPtr<FJsonObject> UpdatedNodeState = MCPUtils::SerializeNode(FoundNode); TSharedPtr<FJsonObject> 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("newStructType"), NewStruct->GetName());
Result->SetStringField(TEXT("nodeClass"), FoundNode->GetClass()->GetName()); Result->SetStringField(TEXT("nodeClass"), FoundNode->GetClass()->GetName());
Result->SetNumberField(TEXT("reconnected"), Reconnected); Result->SetNumberField(TEXT("reconnected"), Reconnected);
@@ -1325,12 +1265,9 @@ public:
{ {
// Load Blueprint // Load Blueprint
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
if (!BP->GeneratedClass) if (!BP->GeneratedClass)
{ {
@@ -1377,11 +1314,12 @@ public:
// Try loading as a Blueprint asset // Try loading as a Blueprint asset
if (!ResolvedClass) if (!ResolvedClass)
{ {
FString BPLoadError; MCPAssets<UBlueprint> ValueAssets;
UBlueprint* ValueBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Value, BPLoadError); if (!ValueAssets.Exact(Value).AllContent().Errors(Result).ETwo().Load()) return;
if (ValueBP && ValueBP->GeneratedClass) if (!ValueAssets.Objects().IsEmpty())
{ {
ResolvedClass = ValueBP->GeneratedClass; if (ValueAssets.Object()->GeneratedClass)
ResolvedClass = ValueAssets.Object()->GeneratedClass;
} }
} }
@@ -1417,17 +1355,10 @@ public:
UObject* ResolvedObj = nullptr; UObject* ResolvedObj = nullptr;
// Try loading as a Blueprint asset // Try loading as a Blueprint asset
FString ObjLoadError; MCPAssets<UBlueprint> ValueAssets;
UBlueprint* ValueBP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Value, ObjLoadError); if (!ValueAssets.Exact(Value).AllContent().Errors(Result).ENone().ETwo().Load()) return;
if (ValueBP && ValueBP->GeneratedClass) if (ValueAssets.Object()->GeneratedClass)
{ ResolvedObj = ValueAssets.Object()->GeneratedClass->GetDefaultObject();
ResolvedObj = ValueBP->GeneratedClass->GetDefaultObject();
}
if (!ResolvedObj)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Could not resolve '%s' to an object"), *Value));
}
ObjProp->SetPropertyValue_InContainer(CDO, ResolvedObj); ObjProp->SetPropertyValue_InContainer(CDO, ResolvedObj);
ActualNewValue = ResolvedObj->GetName(); ActualNewValue = ResolvedObj->GetName();
@@ -1465,9 +1396,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set '%s.%s' from '%s' to '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set '%s.%s' from '%s' to '%s' (saved: %s)"),
*Blueprint, *Property, *OldValue, *ActualNewValue, bSaved ? TEXT("true") : TEXT("false")); *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("oldValue"), OldValue);
Result->SetStringField(TEXT("newValue"), ActualNewValue); Result->SetStringField(TEXT("newValue"), ActualNewValue);
Result->SetStringField(TEXT("propertyType"), Prop->GetCPPType()); Result->SetStringField(TEXT("propertyType"), Prop->GetCPPType());
@@ -1512,8 +1440,9 @@ public:
UEdGraph* GraphFilter = nullptr; UEdGraph* GraphFilter = nullptr;
if (!Blueprint.IsEmpty() && !Graph.IsEmpty()) if (!Blueprint.IsEmpty() && !Graph.IsEmpty())
{ {
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result); MCPAssets<UBlueprint> BPAssets;
if (!BP) return; if (!BPAssets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = BPAssets.Object();
FString DecodedGraphName = MCPUtils::UrlDecode(Graph); FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
TArray<UEdGraph*> AllGraphs; TArray<UEdGraph*> AllGraphs;
@@ -1540,7 +1469,6 @@ public:
ResultArray.Add(MakeShared<FJsonValueString>(MCPUtils::NodeSpawnerFullName(Spawner))); ResultArray.Add(MakeShared<FJsonValueString>(MCPUtils::NodeSpawnerFullName(Spawner)));
} }
Result->SetBoolField(TEXT("success"), true);
Result->SetNumberField(TEXT("count"), ResultArray.Num()); Result->SetNumberField(TEXT("count"), ResultArray.Num());
Result->SetArrayField(TEXT("results"), ResultArray); Result->SetArrayField(TEXT("results"), ResultArray);
} }

View File

@@ -45,13 +45,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references) // Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references)
FEdGraphPinType NewPinType; FEdGraphPinType NewPinType;
@@ -193,10 +189,6 @@ public:
} }
Result->SetBoolField(TEXT("dryRun"), true); 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("nodeType"), FoundNodeType);
Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString());
Result->SetNumberField(TEXT("connectionsAtRisk"), AffectedPins.Num()); Result->SetNumberField(TEXT("connectionsAtRisk"), AffectedPins.Num());
@@ -225,11 +217,6 @@ public:
// Serialize the updated entry node state // Serialize the updated entry node state
TSharedPtr<FJsonObject> UpdatedNodeState = MCPUtils::SerializeNode(EntryNode); TSharedPtr<FJsonObject> 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("nodeType"), FoundNodeType);
Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -266,13 +253,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the entry node // Find the entry node
UK2Node_EditablePinBase* EntryNode = nullptr; UK2Node_EditablePinBase* EntryNode = nullptr;
@@ -402,10 +385,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Parameter removed, save %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Parameter removed, save %s"),
bSaved ? TEXT("succeeded") : TEXT("failed")); 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("nodeType"), FoundNodeType);
Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -441,13 +420,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Resolve param type // Resolve param type
FEdGraphPinType PinType; FEdGraphPinType PinType;
@@ -595,11 +570,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added parameter '%s' to '%s' in '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added parameter '%s' to '%s' in '%s' (saved: %s)"),
*ParamName, *FunctionName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); *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->SetStringField(TEXT("nodeType"), NodeType);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -5,6 +5,7 @@
#include "MCPAssetFinder.h" #include "MCPAssetFinder.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
@@ -69,17 +70,10 @@ public:
continue; continue;
} }
EntryResult->SetStringField(TEXT("blueprint"), Entry.Blueprint); MCPAssets<UBlueprint> Assets;
EntryResult->SetStringField(TEXT("nodeId"), Entry.Node); if (!Assets.Scan<UBlueprint>().Scan<UWorld>().Exact(Entry.Blueprint).Errors(&*EntryResult).ENone().ETwo().Load())
EntryResult->SetStringField(TEXT("pinName"), Entry.PinName);
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Entry.Blueprint, LoadError);
if (!BP)
{
EntryResult->SetStringField(TEXT("error"), LoadError);
continue; continue;
} UBlueprint* BP = Assets.Object();
UEdGraph* Graph = nullptr; UEdGraph* Graph = nullptr;
UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node, &Graph); UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node, &Graph);
@@ -117,9 +111,7 @@ public:
FString OldValue = Pin->DefaultValue; FString OldValue = Pin->DefaultValue;
Pin->DefaultValue = Entry.Value; Pin->DefaultValue = Entry.Value;
EntryResult->SetBoolField(TEXT("success"), true);
EntryResult->SetStringField(TEXT("oldValue"), OldValue); EntryResult->SetStringField(TEXT("oldValue"), OldValue);
EntryResult->SetStringField(TEXT("newValue"), Pin->DefaultValue);
SuccessCount++; SuccessCount++;
ModifiedNodes.Add(Node); ModifiedNodes.Add(Node);
ModifiedBlueprints.Add(BP); ModifiedBlueprints.Add(BP);
@@ -138,7 +130,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetPinDefault — %d/%d succeeded"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetPinDefault — %d/%d succeeded"),
SuccessCount, Pins.Array.Num()); SuccessCount, Pins.Array.Num());
Result->SetBoolField(TEXT("success"), true);
Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("successCount"), SuccessCount);
Result->SetNumberField(TEXT("totalCount"), Pins.Array.Num()); Result->SetNumberField(TEXT("totalCount"), Pins.Array.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
@@ -188,12 +179,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0; int32 SuccessCount = 0;
@@ -211,11 +199,6 @@ public:
continue; 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; UEdGraph* SourceGraph = nullptr;
UEdGraphNode* SourceNode = MCPUtils::FindNodeByGuid(BP, Entry.SourceNode, &SourceGraph); UEdGraphNode* SourceNode = MCPUtils::FindNodeByGuid(BP, Entry.SourceNode, &SourceGraph);
if (!SourceNode) if (!SourceNode)
@@ -262,7 +245,6 @@ public:
continue; continue;
} }
EntryResult->SetBoolField(TEXT("success"), true);
SuccessCount++; SuccessCount++;
} }
@@ -273,9 +255,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"),
SuccessCount, Connections.Array.Num(), *Blueprint); SuccessCount, Connections.Array.Num(), *Blueprint);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("successCount"), SuccessCount);
Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num()); Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
@@ -326,12 +305,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0; int32 SuccessCount = 0;
@@ -350,9 +326,6 @@ public:
continue; continue;
} }
EntryResult->SetStringField(TEXT("nodeId"), Entry.Node);
EntryResult->SetStringField(TEXT("pinName"), Entry.PinName);
UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node);
if (!Node) if (!Node)
{ {
@@ -403,7 +376,6 @@ public:
} }
} }
EntryResult->SetBoolField(TEXT("success"), true);
EntryResult->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount); EntryResult->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount);
SuccessCount++; SuccessCount++;
TotalDisconnected += DisconnectedCount; TotalDisconnected += DisconnectedCount;
@@ -417,8 +389,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DisconnectPin — %d/%d succeeded, %d links broken in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DisconnectPin — %d/%d succeeded, %d links broken in '%s'"),
SuccessCount, Disconnections.Array.Num(), TotalDisconnected, *Blueprint); SuccessCount, Disconnections.Array.Num(), TotalDisconnected, *Blueprint);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("successCount"), SuccessCount);
Result->SetNumberField(TEXT("totalCount"), Disconnections.Array.Num()); Result->SetNumberField(TEXT("totalCount"), Disconnections.Array.Num());
Result->SetNumberField(TEXT("totalDisconnected"), TotalDisconnected); Result->SetNumberField(TEXT("totalDisconnected"), TotalDisconnected);

View File

@@ -52,9 +52,14 @@ public:
bool bIncludeRegular = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("regular"); bool bIncludeRegular = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("regular");
bool bIncludeLevel = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("level"); bool bIncludeLevel = Type.IsEmpty() || Type == TEXT("all") || Type == TEXT("level");
MCPAssets<UBlueprint> AllBlueprints;
AllBlueprints.Info();
MCPAssets<UWorld> AllWorlds;
AllWorlds.Info();
TArray<TSharedPtr<FJsonValue>> Entries; TArray<TSharedPtr<FJsonValue>> Entries;
if (bIncludeRegular) if (bIncludeRegular)
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) for (const FAssetData& Asset : AllBlueprints.AllData())
{ {
FString Name = Asset.AssetName.ToString(); FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString(); FString Path = Asset.PackageName.ToString();
@@ -94,7 +99,7 @@ public:
// Also include level blueprints from maps // Also include level blueprints from maps
if (bIncludeLevel) if (bIncludeLevel)
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) for (const FAssetData& Asset : AllWorlds.AllData())
{ {
FString Name = Asset.AssetName.ToString(); FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString(); FString Path = Asset.PackageName.ToString();
@@ -126,7 +131,7 @@ public:
} }
Result->SetNumberField(TEXT("count"), Entries.Num()); 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); Result->SetArrayField(TEXT("blueprints"), Entries);
} }
}; };
@@ -151,10 +156,10 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result); MCPAssets<UBlueprint> Assets;
if (!BP) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(BP); TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(Assets.Object());
MCPUtils::CopyJsonFields(&*Tmp, Result); MCPUtils::CopyJsonFields(&*Tmp, Result);
} }
}; };
@@ -185,12 +190,9 @@ public:
// URL-decode graph name to handle spaces encoded as '+' or '%20' // URL-decode graph name to handle spaces encoded as '+' or '%20'
FString DecodedGraphName = MCPUtils::UrlDecode(Graph); FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TArray<UEdGraph*> AllGraphs; TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs); BP->GetAllGraphs(AllGraphs);
@@ -310,8 +312,13 @@ public:
} }
}; };
MCPAssets<UBlueprint> AllBlueprints;
AllBlueprints.Info();
MCPAssets<UWorld> AllWorlds;
AllWorlds.Info();
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) for (const FAssetData& Asset : AllBlueprints.AllData())
{ {
if (Results.Num() >= EffectiveMaxResults) break; if (Results.Num() >= EffectiveMaxResults) break;
@@ -328,7 +335,7 @@ public:
} }
// Also search level blueprints // Also search level blueprints
for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) for (const FAssetData& MapAsset : AllWorlds.AllData())
{ {
if (Results.Num() >= EffectiveMaxResults) break; if (Results.Num() >= EffectiveMaxResults) break;
@@ -352,7 +359,6 @@ public:
} }
} }
Result->SetStringField(TEXT("query"), Query);
Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
} }
@@ -384,12 +390,9 @@ public:
{ {
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Blueprint); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Blueprint);
FString LoadError; MCPAssets<UBlueprint> Assets;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
if (!BP) UBlueprint* BP = Assets.Object();
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"),
*BP->GetName(), *BP->GetName(),
@@ -398,7 +401,6 @@ public:
// Attempt save with NO modifications // Attempt save with NO modifications
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName()); Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName());
Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr); Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -435,8 +437,10 @@ public:
Registry.GetReferencers(FName(*AssetPath), Referencers); Registry.GetReferencers(FName(*AssetPath), Referencers);
// Build set of known Blueprint package names for filtering // Build set of known Blueprint package names for filtering
MCPAssets<UBlueprint> AllBlueprints;
AllBlueprints.Info();
TSet<FString> BlueprintPackages; TSet<FString> BlueprintPackages;
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) for (const FAssetData& Asset : AllBlueprints.AllData())
{ {
BlueprintPackages.Add(Asset.PackageName.ToString()); BlueprintPackages.Add(Asset.PackageName.ToString());
} }
@@ -456,7 +460,6 @@ public:
} }
} }
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num()); Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num());
Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num()); Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num());
Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs); Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs);
@@ -680,8 +683,13 @@ public:
} }
}; };
MCPAssets<UBlueprint> AllBlueprints;
AllBlueprints.Info();
MCPAssets<UWorld> AllWorlds;
AllWorlds.Info();
// Search regular blueprints // Search regular blueprints
for (const FAssetData& Asset : UMCPAssetFinder::GetAssets(UBlueprint::StaticClass())) for (const FAssetData& Asset : AllBlueprints.AllData())
{ {
if (Results.Num() >= EffectiveMaxResults) break; if (Results.Num() >= EffectiveMaxResults) break;
@@ -701,7 +709,7 @@ public:
} }
// Search level blueprints from maps // Search level blueprints from maps
for (const FAssetData& MapAsset : UMCPAssetFinder::GetAssets(UWorld::StaticClass())) for (const FAssetData& MapAsset : AllWorlds.AllData())
{ {
if (Results.Num() >= EffectiveMaxResults) break; if (Results.Num() >= EffectiveMaxResults) break;
@@ -722,7 +730,6 @@ public:
SearchOneBlueprint(MapName, AssetPath, LevelBP, true); SearchOneBlueprint(MapName, AssetPath, LevelBP, true);
} }
Result->SetStringField(TEXT("typeName"), DecodedTypeName);
Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results); Result->SetArrayField(TEXT("results"), Results);
} }

View File

@@ -55,9 +55,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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 // Check for duplicate state name
if (MCPUtils::FindStateByName(SMGraph, StateName, nullptr)) if (MCPUtils::FindStateByName(SMGraph, StateName, nullptr))
@@ -88,8 +90,8 @@ public:
if (!AnimationAsset.IsEmpty() && NewState->GetBoundGraph()) if (!AnimationAsset.IsEmpty() && NewState->GetBoundGraph())
{ {
// Try to find the animation asset and create a sequence player in the state's inner graph // Try to find the animation asset and create a sequence player in the state's inner graph
FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimationAsset); MCPAssets<UAnimSequence> AnimAssets;
UAnimSequence* AnimSeq = FoundAnimAsset ? Cast<UAnimSequence>(FoundAnimAsset->GetAsset()) : nullptr; UAnimSequence* AnimSeq = AnimAssets.Exact(AnimationAsset).ENone().ETwo().Load() ? AnimAssets.Object() : nullptr;
if (AnimSeq) if (AnimSeq)
{ {
@@ -108,9 +110,6 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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->SetStringField(TEXT("nodeId"), NewState->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
@@ -142,9 +141,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
if (!StateNode) return; if (!StateNode) return;
@@ -177,8 +178,6 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("removedState"), StateName);
Result->SetNumberField(TEXT("removedTransitions"), RemovedTransitions); Result->SetNumberField(TEXT("removedTransitions"), RemovedTransitions);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
@@ -222,9 +221,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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); UAnimStateNode* FromStateNode = MCPUtils::FindStateByName(SMGraph, FromState, Result);
if (!FromStateNode) return; if (!FromStateNode) return;
@@ -266,13 +267,7 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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->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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -321,9 +316,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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); UAnimStateTransitionNode* TransNode = MCPUtils::FindTransition(SMGraph, FromState, ToState);
if (!TransNode) if (!TransNode)
@@ -371,15 +368,7 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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("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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -413,9 +402,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
if (!StateNode) return; if (!StateNode) return;
@@ -427,8 +418,9 @@ public:
} }
// Find the animation asset // Find the animation asset
UAnimSequence* AnimSeq = UMCPAssetFinder::LoadAsset<UAnimSequence>(AnimationAsset, Result); MCPAssets<UAnimSequence> AnimAssets;
if (!AnimSeq) return; if (!AnimAssets.Exact(AnimationAsset).Errors(Result).ENone().ETwo().Load()) return;
UAnimSequence* AnimSeq = AnimAssets.Object();
// Find existing SequencePlayer or create one // Find existing SequencePlayer or create one
UAnimGraphNode_SequencePlayer* SeqNode = nullptr; UAnimGraphNode_SequencePlayer* SeqNode = nullptr;
@@ -457,9 +449,6 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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("createdNewNode"), bCreatedNew);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }
@@ -501,9 +490,11 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
UAnimationStateMachineGraph* SMGraph = UMCPAssetFinder::LoadAnimStateMachineGraph(Blueprint, Graph, Result); MCPAssets<UAnimBlueprint> Assets;
if (!SMGraph) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UAnimBlueprint* AnimBP = SMGraph->GetTypedOuter<UAnimBlueprint>(); 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); UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName, Result);
if (!StateNode) return; if (!StateNode) return;
@@ -515,8 +506,9 @@ public:
} }
// Find the blend space asset // Find the blend space asset
UBlendSpace* BlendSpaceAsset = UMCPAssetFinder::LoadAsset<UBlendSpace>(BlendSpace, Result); MCPAssets<UBlendSpace> BlendSpaceAssets;
if (!BlendSpaceAsset) return; if (!BlendSpaceAssets.Exact(BlendSpace).Errors(Result).ENone().ETwo().Load()) return;
UBlendSpace* BlendSpaceAsset = BlendSpaceAssets.Object();
// Find existing BlendSpacePlayer or create one // Find existing BlendSpacePlayer or create one
UAnimGraphNode_BlendSpacePlayer* BSNode = nullptr; UAnimGraphNode_BlendSpacePlayer* BSNode = nullptr;
@@ -665,9 +657,6 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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->SetStringField(TEXT("nodeId"), BSNode->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -123,8 +123,6 @@ public:
// Save // Save
bool bSaved = MCPUtils::SaveGenericPackage(NewStruct); bool bSaved = MCPUtils::SaveGenericPackage(NewStruct);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("assetName"), AssetName); Result->SetStringField(TEXT("assetName"), AssetName);
Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded); Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -203,8 +201,6 @@ public:
// Save // Save
bool bSaved = MCPUtils::SaveGenericPackage(NewEnum); bool bSaved = MCPUtils::SaveGenericPackage(NewEnum);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("assetName"), AssetName); Result->SetStringField(TEXT("assetName"), AssetName);
Result->SetNumberField(TEXT("valueCount"), EnumValues.Num()); Result->SetNumberField(TEXT("valueCount"), EnumValues.Num());
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
@@ -238,8 +234,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Find the struct // Find the struct
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result); MCPAssets<UUserDefinedStruct> Assets;
if (!Struct) return; if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return;
UUserDefinedStruct* Struct = Assets.Object();
// Resolve type // Resolve type
FEdGraphPinType PinType; FEdGraphPinType PinType;
@@ -272,10 +269,6 @@ public:
// Save // Save
bool bSaved = MCPUtils::SaveGenericPackage(Struct); 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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -304,8 +297,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Find the struct // Find the struct
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result); MCPAssets<UUserDefinedStruct> Assets;
if (!Struct) return; if (!Assets.Exact(AssetPath).Errors(Result).ENone().ETwo().Load()) return;
UUserDefinedStruct* Struct = Assets.Object();
// Find the property GUID by name // Find the property GUID by name
FGuid TargetGuid; FGuid TargetGuid;
@@ -342,9 +336,6 @@ public:
// Save // Save
bool bSaved = MCPUtils::SaveGenericPackage(Struct); 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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };

View File

@@ -136,21 +136,21 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
TArray<FAssetData*> MatchingAssets; MCPAssets<UBlueprint> Finder;
Finder.Scan<UBlueprint>().Scan<UWorld>();
if (!Blueprint.IsEmpty()) if (!Blueprint.IsEmpty())
{ {
FAssetData* Asset = UMCPAssetFinder::FindAsset(UMCPAssetFinder::BlueprintsAndMaps, Blueprint); if (!Finder.Exact(Blueprint).ETwo().Info() || Finder.AllData().IsEmpty())
if (!Asset)
{ {
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blueprint '%s' not found."), *Blueprint)); return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blueprint '%s' not found."), *Blueprint));
} }
MatchingAssets.Add(Asset);
} }
else else
{ {
MatchingAssets = UMCPAssetFinder::SearchAssets(UMCPAssetFinder::BlueprintsAndMaps, Query); Finder.Substring(Query).Info();
} }
const TArray<FAssetData>& MatchingAssets = Finder.AllData();
int32 TotalMatching = MatchingAssets.Num(); int32 TotalMatching = MatchingAssets.Num();
// countOnly: return count without compiling anything // countOnly: return count without compiling anything
@@ -179,17 +179,18 @@ public:
for (int32 Idx = StartIdx; Idx < EndIdx; Idx++) for (int32 Idx = StartIdx; Idx < EndIdx; Idx++)
{ {
FAssetData* Asset = MatchingAssets[Idx]; const FAssetData& Asset = MatchingAssets[Idx];
FString AssetName = Asset->AssetName.ToString(); FString AssetName = Asset.AssetName.ToString();
FString PackagePath = Asset->PackageName.ToString(); FString PackagePath = Asset.PackageName.ToString();
// Load the Blueprint (handles both regular and level blueprints) // Load the Blueprint (handles both regular and level blueprints)
FString LoadError; MCPAssets<UBlueprint> Loader;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(PackagePath, LoadError); Loader.Scan<UBlueprint>().Scan<UWorld>();
if (!BP) if (!Loader.Exact(PackagePath).ENone().ETwo().Load())
{ {
continue; continue;
} }
UBlueprint* BP = Loader.Object();
TotalChecked++; TotalChecked++;

View File

@@ -45,13 +45,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Verify variable exists // Verify variable exists
bool bVarFound = false; bool bVarFound = false;
@@ -170,9 +166,6 @@ public:
if (DryRun) if (DryRun)
{ {
Result->SetBoolField(TEXT("dryRun"), true); 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->SetStringField(TEXT("typeCategory"), ResolvedTypeCategory);
Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num()); Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num());
Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes); 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->SetStringField(TEXT("typeCategory"), ResolvedTypeCategory);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar); Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar);
@@ -255,13 +244,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Check for duplicate variable name // Check for duplicate variable name
FName VarFName(*VariableName); FName VarFName(*VariableName);
@@ -308,15 +293,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"),
*VariableName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); *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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -344,13 +320,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find variable by name (case-insensitive) // Find variable by name (case-insensitive)
FName VarFName(*VariableName); FName VarFName(*VariableName);
@@ -392,9 +364,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"),
*VariableName, *Blueprint, bSaved ? TEXT("true") : TEXT("false")); *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); Result->SetBoolField(TEXT("saved"), bSaved);
} }
}; };
@@ -441,13 +410,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
// Load Blueprint MCPAssets<UBlueprint> Assets;
FString LoadError; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError); UBlueprint* BP = Assets.Object();
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
// Find the variable // Find the variable
FName VarFName(*Variable); FName VarFName(*Variable);
@@ -616,9 +581,6 @@ public:
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = MCPUtils::SaveBlueprintPackage(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->SetArrayField(TEXT("changes"), Changes);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -6,14 +6,16 @@
#include "StructUtils/UserDefinedStruct.h" #include "StructUtils/UserDefinedStruct.h"
#include "Engine/UserDefinedEnum.h" #include "Engine/UserDefinedEnum.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Engine/World.h"
#include "MCPAssetFinder.generated.h" #include "MCPAssetFinder.generated.h"
class UBlueprint;
class UMaterial; class UMaterial;
class UMaterialInstanceConstant; class UMaterialInstanceConstant;
class UMaterialFunction; class UMaterialFunction;
class UAnimationStateMachineGraph; class UAnimationStateMachineGraph;
class IAssetRegistry; class IAssetRegistry;
struct FARFilter;
/** /**
* Engine subsystem that caches asset registry data for the BlueprintMCP server. * Engine subsystem that caches asset registry data for the BlueprintMCP server.
@@ -104,3 +106,79 @@ private:
// Empty array returned when subsystem is unavailable // Empty array returned when subsystem is unavailable
static const TArray<FAssetData> EmptyAssetArray; static const TArray<FAssetData> EmptyAssetArray;
}; };
// ============================================================
// MCPAssetsBase — non-template base for MCPAssets<T>
// ============================================================
class MCPAssetsBase
{
public:
MCPAssetsBase(UClass* InTargetClass);
MCPAssetsBase& Scan(UClass* Class) { Classes.Add(Class); return *this; }
template<class T> 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<FAssetData>& 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<UClass*, TInlineAllocator<4>> Classes;
TArray<FAssetData> AssetResults;
TArray<UObject*> 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<T> — builder-pattern asset finder
//
// Usage:
// MCPAssets<UBlueprint> Assets;
// if (!Assets.Exact(Name).Errors(Result).ENone().ETwo().Load()) return;
// UBlueprint* BP = Assets.Object();
//
// MCPAssets<UMaterial> Materials;
// if (!Materials.Substring(Filter).Limit(100).Load()) return;
// for (UMaterial* Mat : Materials.Objects()) { ... }
// ============================================================
template<class T>
class MCPAssets : public MCPAssetsBase
{
public:
MCPAssets() : MCPAssetsBase(T::StaticClass()) {}
TArrayView<T* const> Objects() const
{
return TArrayView<T* const>(reinterpret_cast<T* const*>(UObjectResults.GetData()), UObjectResults.Num());
}
T* Object() const { return static_cast<T*>(UObjectResults[0]); }
};

View File

@@ -141,6 +141,7 @@ struct MCPErrorCallback
{ {
TFunction<void(const FString&)> Func; TFunction<void(const FString&)> Func;
MCPErrorCallback(std::nullptr_t); MCPErrorCallback(std::nullptr_t);
MCPErrorCallback(FString& OutError); MCPErrorCallback(FString& OutError);
MCPErrorCallback(FJsonObject* Result); MCPErrorCallback(FJsonObject* Result);