Delete MCPAssets and the things that use it.

This commit is contained in:
2026-03-17 23:29:24 -04:00
parent 26de2351db
commit 42fb0a8453
5 changed files with 0 additions and 316 deletions

View File

@@ -1,79 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPServer.h"
#include "MCPHandler.h"
#include "MCPAssets.h"
#include "MCPUtils.h"
#include "MCPTypes.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Blueprint_Search.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Blueprint_Search : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for blueprint name or path"))
FString Query;
UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)"))
FString ParentClass;
virtual FString GetDescription() const override
{
return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type.");
}
virtual void Handle() override
{
MCPAssets<UObject> Assets;
Assets.Scan<UBlueprint>().Substring(Query).Limit(500);
if (!Assets.Info()) return;
UClass *Parent = nullptr;
if (!ParentClass.IsEmpty())
{
Parent = UMCPTypes::TextToOneObjectType(ParentClass);
if (!Parent) return;
}
int32 Count = 0;
for (const FAssetData& Asset : Assets.AllData())
{
// Extract parent class name from asset tags
FString ParentClassName;
Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName);
int32 DotIndex;
if (ParentClassName.FindLastChar('.', DotIndex))
{
ParentClassName = ParentClassName.Mid(DotIndex + 1);
}
ParentClassName.RemoveFromEnd(TEXT("'"));
// Apply parent class filter
if (!ParentClass.IsEmpty())
{
if (!ParentClassName.Equals(ParentClass, ESearchCase::IgnoreCase))
{
continue;
}
}
UMCPServer::Printf(TEXT("%30s %s\n"), *ParentClassName, *Asset.PackageName.ToString());
Count++;
}
if (Count == 0)
{
UMCPServer::Print(TEXT("No blueprint assets found.\n"));
}
}
};

View File

@@ -1,127 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPServer.h"
#include "MCPHandler.h"
#include "MCPAssets.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Engine/Level.h"
#include "Engine/LevelScriptBlueprint.h"
#include "EdGraph/EdGraphNode.h"
#include "K2Node_CallFunction.h"
#include "K2Node_Event.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Blueprint_SearchContents.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Blueprint_SearchContents : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Search query string to match against node titles, function names, event names, and variable names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Filter results to blueprints whose path contains this substring"))
FString Path;
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (default 50, max 200)"))
int32 MaxResults = 0;
virtual FString GetDescription() const override
{
return TEXT("Search across all Blueprint graphs for nodes matching a query string.");
}
virtual void Handle() override
{
int32 Limit = (MaxResults > 0) ? FMath::Clamp(MaxResults, 1, 200) : 50;
int32 Count = 0;
// Search one blueprint's nodes for the query string.
auto SearchBlueprint = [&](UBlueprint* BP, bool bIsLevelBP)
{
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
{
if (Count >= Limit) return;
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
FString FuncName, EventName, VarName;
if (auto* CF = Cast<UK2Node_CallFunction>(Node))
{
FuncName = CF->FunctionReference.GetMemberName().ToString();
}
else if (auto* Ev = Cast<UK2Node_Event>(Node))
{
EventName = Ev->EventReference.GetMemberName().ToString();
}
else if (auto* CE = Cast<UK2Node_CustomEvent>(Node))
{
EventName = CE->CustomFunctionName.ToString();
}
else if (auto* VG = Cast<UK2Node_VariableGet>(Node))
{
VarName = VG->GetVarName().ToString();
}
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
{
VarName = VS->GetVarName().ToString();
}
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
if (!bMatch) continue;
Count++;
UMCPServer::Printf(TEXT("blueprint: %s\n"), *MCPUtils::FormatName(BP));
UMCPServer::Printf(TEXT(" graph: %s\n"), *MCPUtils::FormatName(Node->GetGraph()));
UMCPServer::Printf(TEXT(" node: %s\n"), *MCPUtils::FormatName(Node));
UMCPServer::Printf(TEXT(" class: %s\n"), *MCPUtils::FormatName(Node->GetClass()));
if (!FuncName.IsEmpty()) UMCPServer::Printf(TEXT(" function: %s\n"), *FuncName);
if (!EventName.IsEmpty()) UMCPServer::Printf(TEXT(" event: %s\n"), *EventName);
if (!VarName.IsEmpty()) UMCPServer::Printf(TEXT(" variable: %s\n"), *VarName);
if (bIsLevelBP) UMCPServer::Print(TEXT(" level-blueprint: true\n"));
UMCPServer::Print(TEXT("\n"));
}
};
// Search regular blueprints
MCPAssets<UBlueprint> AllBlueprints;
if (!Path.IsEmpty()) AllBlueprints.Substring(Path);
AllBlueprints.Load();
for (UBlueprint* BP : AllBlueprints.Objects())
{
if (Count >= Limit) break;
SearchBlueprint(BP, false);
}
// Search level blueprints
MCPAssets<UWorld> AllWorlds;
if (!Path.IsEmpty()) AllWorlds.Substring(Path);
AllWorlds.Load();
for (UWorld* World : AllWorlds.Objects())
{
if (Count >= Limit) break;
if (!World || !World->PersistentLevel) continue;
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(false);
if (!LevelBP) continue;
SearchBlueprint(LevelBP, true);
}
UMCPServer::Printf(TEXT("Results: %d\n"), Count);
if (Count >= Limit) UMCPServer::Printf(TEXT("(limit %d reached, use MaxResults to increase)\n"), Limit);
}
};

View File

@@ -1,169 +0,0 @@
#include "MCPAssets.h"
#include "MCPServer.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Engine/Level.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Materials/Material.h"
#include "MCPUtils.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
// ============================================================
// MCPAssetsBase
// ============================================================
MCPAssetsBase::MCPAssetsBase(UClass* InTargetClass)
: TargetClass(InTargetClass)
{
Scans.Add(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;
}
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();
TArray<FAssetData> Candidates;
AR.GetAssets(ConfigureFilter(), 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) continue;
// If this is a material open in the editor, use the editor's transient copy.
if (UMaterial* Mat = Cast<UMaterial>(Obj))
Obj = MCPUtils::ReplaceMaterialWithTransientCopy(Mat);
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;
}
FARFilter MCPAssetsBase::ConfigureFilter()
{
FARFilter Filter;
for (UClass* C : Scans) Filter.ClassPaths.Add(C->GetClassPathName());
Filter.bRecursiveClasses = !bNoDerived;
if (!bAllContent)
{
Filter.PackagePaths.Add(FName(TEXT("/Game")));
Filter.bRecursivePaths = true;
}
return Filter;
}
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();
UMCPServer::Printf(TEXT("ERROR: %s\n"), *Msg);
}

View File

@@ -3,7 +3,6 @@
#include "MCPJson.h"
#include "LogCapture.h"
#include "MCPUtils.h"
#include "MCPAssets.h"
#include "UObject/StrongObjectPtr.h"
#include "Materials/MaterialExpression.h"
#include "AssetRegistry/AssetRegistryModule.h"

View File

@@ -1,146 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "AssetRegistry/AssetData.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Engine/World.h"
struct FARFilter;
////////////////////////////////////////////////////////////
//
// MCPAssets - search for assets.
//
//
// Construct an object of class MCPAssets like this:
//
// MCPAssets<UBlueprint> Assets;
//
// The UBlueprint template parameter means that this example
// Assets loader is capable of storing pointers to
// UBlueprint.
//
// It also means that by default, it will scan
// all UBlueprint assets. You can narrow that:
//
// Assets.NoScans();
// Assets.Scan<UAnimBlueprint>();
// Assets.Scan<ULuprexBlueprint>();
//
// To get string matching, call either 'Exact' or
// 'Substring'. If you don't call either of these, there's
// no string filter. If the string you pass in contains a
// slash, then the search is by asset-path, otherwise, by
// asset-name:
//
// Assets.Substring(TEXT("MyAsset"));
//
// By default, the asset finder limits itself to assets in
// the /Game folder. You can expand that:
//
// Assets.AllContent();
//
// You can specify that you don't want to see derived
// classes:
//
// Assets.NoDerived()
//
// You can specify a limit on the number of results
// returned:
//
// Assets.Limit(100)
//
// You can specify what constitutes an error condition. If
// the asset finder detects an error, then it will report
// it:
//
// Assets.ENone() - it's an error if nothing is found
// Assets.EAny() - it's an error if anything is found
// Assets.ETwo() - it's an error if two or more are found
//
// Errors are reported via UMCPServer::Printf.
//
// Once the Assets object is configured, it's time to scan
// the assets. Use 'Info' if you just want to see
// FAssetData. Use 'Load' if you want to load the assets
// into memory. Both of these functions return true if
// there were no errors, or false if there was one.
//
// bool ok = Assets.Load();
//
// Once you've scanned the assets, you can examine the
// results using the following methods. The objects array
// will be empty if you called 'Info' instead of 'Load':
//
// const TArray<FAssetData>& AllData();
// const FAssetData& OneData();
// const TArray<UBlueprint*> Objects();
// UBlueprint* Object();
//
//
// MCPAssets configuration methods can be chained:
//
// Assets.Limit(100).ENone().ETwo();
//
////////////////////////////////////////////////////////////
class MCPAssetsBase
{
public:
MCPAssetsBase& NoScans() { Scans.Empty(); return *this; }
MCPAssetsBase& Scan(UClass* Class) { Scans.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& 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:
FARFilter ConfigureFilter();
bool AssetMatches(const FAssetData &Data);
UObject *TryLoadAsset(const FAssetData &Asset);
void SetError(const FString &Msg);
protected:
MCPAssetsBase(UClass* InTargetClass);
UClass* TargetClass;
TSet<UClass*> Scans;
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;
};
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 UObjectResults.IsEmpty() ? nullptr : static_cast<T*>(UObjectResults[0]); }
};