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);
}
// ============================================================
// 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(
const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error)
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,12 +39,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
// Load Blueprint
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
MCPAssets<UBlueprint> Assets;
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = Assets.Object();
FName DispatcherFName(*DispatcherName);
@@ -155,9 +152,6 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added event dispatcher '%s' to '%s' with %d params (saved: %s)"),
*DispatcherName, *Blueprint, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("dispatcherName"), DispatcherName);
Result->SetArrayField(TEXT("parameters"), AddedParamsJson);
Result->SetBoolField(TEXT("saved"), bSaved);
}
@@ -183,12 +177,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
MCPAssets<UBlueprint> Assets;
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = Assets.Object();
TSet<FName> DelegateNameSet;
FBlueprintEditorUtils::GetDelegateNameList(BP, DelegateNameSet);
@@ -236,7 +227,6 @@ public:
DispatchersArr.Add(MakeShared<FJsonValueObject>(DispObj));
}
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), DispatchersArr.Num());
Result->SetArrayField(TEXT("dispatchers"), DispatchersArr);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,14 +6,16 @@
#include "StructUtils/UserDefinedStruct.h"
#include "Engine/UserDefinedEnum.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Engine/World.h"
#include "MCPAssetFinder.generated.h"
class UBlueprint;
class UMaterial;
class UMaterialInstanceConstant;
class UMaterialFunction;
class UAnimationStateMachineGraph;
class IAssetRegistry;
struct FARFilter;
/**
* Engine subsystem that caches asset registry data for the BlueprintMCP server.
@@ -104,3 +106,79 @@ private:
// Empty array returned when subsystem is unavailable
static const TArray<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;
MCPErrorCallback(std::nullptr_t);
MCPErrorCallback(FString& OutError);
MCPErrorCallback(FJsonObject* Result);