Lots of refactoring

This commit is contained in:
2026-03-13 14:26:04 -04:00
parent e3b5d32345
commit 7cfe73eca8
65 changed files with 246 additions and 364 deletions

View File

@@ -1,4 +1,5 @@
#include "MCPAssetFinder.h"
#include "MCPAssets.h"
#include "MCPServer.h"
#include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "Engine/Level.h"
@@ -46,12 +47,6 @@ MCPAssetsBase& MCPAssetsBase::AllContent()
return *this;
}
MCPAssetsBase& MCPAssetsBase::Errors(MCPErrorCallback InCB)
{
ErrorCB = InCB;
return *this;
}
bool MCPAssetsBase::Info()
{
@@ -171,5 +166,5 @@ void MCPAssetsBase::SetError(const FString &Msg)
{
AssetResults.Empty();
UObjectResults.Empty();
ErrorCB.SetError(Msg);
UMCPServer::Printf(TEXT("ERROR: %s\n"), *Msg);
}

View File

@@ -1,5 +1,6 @@
#include "MCPProperty.h"
#include "MCPUtils.h"
#include "MCPServer.h"
#include "Materials/MaterialExpression.h"
#include "MaterialGraph/MaterialGraphNode.h"
@@ -14,14 +15,14 @@ FString MCPProperty::GetText() const
return Result;
}
bool MCPProperty::SetText(const FString& Value, MCPErrorCallback Error)
bool MCPProperty::SetText(const FString& Value)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
if (!ImportResult)
{
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
*Value, *MCPUtils::FormatName(Prop), *Prop->GetCPPType()));
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
*Value, *MCPUtils::FormatName(Prop), *Prop->GetCPPType());
return false;
}
@@ -82,19 +83,19 @@ TArray<MCPProperty> MCPProperty::GetAllExactMatch(UObject* Obj, EPropertyFlags F
return Result;
}
MCPProperty MCPProperty::GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name, MCPErrorCallback Error)
MCPProperty MCPProperty::GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name)
{
TArray<MCPProperty> Matches = GetAllExactMatch(Obj, Flags, Name);
if (Matches.Num() == 0)
{
Error.SetError(FString::Printf(TEXT("Property '%s' not found on %s"),
*Name, *MCPUtils::FormatName(Obj->GetClass())));
UMCPServer::Printf(TEXT("ERROR: Property '%s' not found on %s\n"),
*Name, *MCPUtils::FormatName(Obj->GetClass()));
return MCPProperty();
}
if (Matches.Num() > 1)
{
Error.SetError(FString::Printf(TEXT("Ambiguous property '%s' on %s"),
*Name, *MCPUtils::FormatName(Obj->GetClass())));
UMCPServer::Printf(TEXT("ERROR: Ambiguous property '%s' on %s\n"),
*Name, *MCPUtils::FormatName(Obj->GetClass()));
return MCPProperty();
}
return Matches[0];

View File

@@ -2,7 +2,7 @@
#include "MCPHandler.h"
#include "LogCapture.h"
#include "MCPUtils.h"
#include "MCPAssetFinder.h"
#include "MCPAssets.h"
#include "UObject/StrongObjectPtr.h"
#include "Materials/MaterialExpression.h"
#include "AssetRegistry/AssetRegistryModule.h"
@@ -289,12 +289,14 @@ FString UMCPServer::HandleRequest(const FString& Line)
IMCPHandler* Handler = Cast<IMCPHandler>(HandlerObj.Get());
// Populate the handler object with the request parameters.
TStringBuilder<4096> PopulateError;
if (!MCPUtils::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request, PopulateError))
HandlerOutput.Reset();
if (!MCPUtils::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request))
{
PopulateError.Append(TEXT("\nUsage:\n"));
MCPUtils::FormatCommandHelp(*HandlerClass, PopulateError);
return PopulateError.ToString();
HandlerOutput.Append(TEXT("\nUsage:\n"));
MCPUtils::FormatCommandHelp(*HandlerClass, HandlerOutput);
FString Result = HandlerOutput.ToString();
HandlerOutput.Reset();
return Result;
}
// Invoke the handler with log capture.

View File

@@ -1,4 +1,5 @@
#include "MCPUtils.h"
#include "MCPServer.h"
#include "MCPHandler.h"
#include "Dom/JsonValue.h"
#include "Serialization/JsonReader.h"
@@ -90,22 +91,6 @@ extern int32 TrySavePackageSEH(
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
#endif
// ============================================================
// MCPErrorCallback
// ============================================================
MCPErrorCallback::MCPErrorCallback(std::nullptr_t)
: Func([](const FString&) {})
{}
MCPErrorCallback::MCPErrorCallback(FString& OutError)
: Func([&OutError](const FString& Msg) { OutError = Msg; })
{}
MCPErrorCallback::MCPErrorCallback(FStringBuilderBase& OutResult)
: Func([&OutResult](const FString& Msg) { OutResult.Appendf(TEXT("ERROR: %s\n"), *Msg); })
{}
// ============================================================
// Name Formatting
// ============================================================
@@ -343,12 +328,12 @@ FString MCPUtils::EnumToString(UEnum* Enum, int64 Value, const FString& Prefix)
return Full;
}
bool MCPUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, MCPErrorCallback Error, const FString& Prefix)
bool MCPUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, const FString& Prefix)
{
OutValue = Enum->GetValueByNameString(Prefix + Str);
if (OutValue == INDEX_NONE)
{
Error.SetError(FString::Printf(TEXT("Invalid value '%s' for %s"), *Str, *Enum->GetName()));
UMCPServer::Printf(TEXT("ERROR: Invalid value '%s' for %s\n"), *Str, *Enum->GetName());
return false;
}
return true;
@@ -541,7 +526,7 @@ UClass* MCPUtils::FindClassByName(const FString& ClassName)
}
bool MCPUtils::ResolveTypeFromString(
const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error)
const FString& TypeName, FEdGraphPinType& OutPinType)
{
FString TypeLower = TypeName.ToLower();
@@ -614,7 +599,7 @@ bool MCPUtils::ResolveTypeFromString(
UClass* FoundClass = FindClassByName(ClassName);
if (!FoundClass)
{
Error.SetError(FString::Printf(TEXT("Class '%s' not found for object reference type"), *ClassName));
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for object reference type\n"), *ClassName);
return false;
}
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
@@ -626,7 +611,7 @@ bool MCPUtils::ResolveTypeFromString(
UClass* FoundClass = FindClassByName(ClassName);
if (!FoundClass)
{
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft object reference type"), *ClassName));
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for soft object reference type\n"), *ClassName);
return false;
}
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
@@ -638,7 +623,7 @@ bool MCPUtils::ResolveTypeFromString(
UClass* FoundClass = FindClassByName(ClassName);
if (!FoundClass)
{
Error.SetError(FString::Printf(TEXT("Class '%s' not found for class reference type (TSubclassOf)"), *ClassName));
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for class reference type (TSubclassOf)\n"), *ClassName);
return false;
}
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Class;
@@ -650,7 +635,7 @@ bool MCPUtils::ResolveTypeFromString(
UClass* FoundClass = FindClassByName(ClassName);
if (!FoundClass)
{
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft class reference type"), *ClassName));
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for soft class reference type\n"), *ClassName);
return false;
}
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
@@ -662,7 +647,7 @@ bool MCPUtils::ResolveTypeFromString(
UClass* FoundClass = FindClassByName(ClassName);
if (!FoundClass)
{
Error.SetError(FString::Printf(TEXT("Class '%s' not found for interface reference type"), *ClassName));
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for interface reference type\n"), *ClassName);
return false;
}
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Interface;
@@ -738,9 +723,9 @@ bool MCPUtils::ResolveTypeFromString(
}
else
{
Error.SetError(FString::Printf(
TEXT("Unknown type '%s'. Use: bool, int, float, string, name, text, byte, vector, rotator, transform, object, a struct/enum name (e.g. FVector, EMyEnum), or colon syntax for references (object:Actor, softobject:Actor, class:Actor, softclass:Actor, interface:MyInterface)"),
*TypeName));
UMCPServer::Printf(
TEXT("ERROR: Unknown type '%s'. Use: bool, int, float, string, name, text, byte, vector, rotator, transform, object, a struct/enum name (e.g. FVector, EMyEnum), or colon syntax for references (object:Actor, softobject:Actor, class:Actor, softclass:Actor, interface:MyInterface)\n"),
*TypeName);
return false;
}
}
@@ -798,54 +783,6 @@ UMaterial* MCPUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
return Material;
}
// ============================================================
// PreEdit / PostEdit
// ============================================================
void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
{
for (UObject* Obj : Objects)
Obj->PreEditChange(nullptr);
}
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
{
TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs;
TSet<UMaterial*> Materials;
TSet<UBlueprint*> Blueprints;
for (int32 i = Objects.Num() - 1; i >= 0; --i)
{
UObject* Obj = Objects[i];
Obj->PostEditChange();
Obj->MarkPackageDirty();
if (UEdGraphNode* Node = Cast<UEdGraphNode>(Obj))
Nodes.Add(Node);
if (UEdGraph* Graph = Cast<UEdGraph>(Obj))
Graphs.Add(Graph);
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
Blueprints.Add(BP);
if (UMaterialInterface* MatIface = Cast<UMaterialInterface>(Obj))
if (UMaterial* BaseMat = MatIface->GetMaterial())
Materials.Add(BaseMat);
}
for (UEdGraphNode* Node : Nodes)
Node->ReconstructNode();
for (UEdGraph* Graph : Graphs)
Graph->NotifyGraphChanged();
for (UMaterial *Material : Materials)
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
for (UBlueprint *Blueprint : Blueprints)
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
if (GEditor)
GEditor->RedrawAllViewports();
}
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPUtils::GetMaterialParameters(UMaterialInterface* Material)
{
@@ -860,7 +797,7 @@ TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPUtils::GetMaterialPa
return Result;
}
bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, MCPErrorCallback Error)
bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation)
{
if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase))
OutAssociation = GlobalParameter;
@@ -870,7 +807,7 @@ bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialPa
OutAssociation = BlendParameter;
else
{
Error.SetError(FString::Printf(TEXT("Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')"), *Str));
UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str);
return false;
}
return true;
@@ -984,7 +921,7 @@ UAnimationStateMachineGraph* MCPUtils::FindStateMachineGraph(UBlueprint* BP, con
return nullptr;
}
UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, MCPErrorCallback Error)
UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName)
{
for (UEdGraphNode* Node : SMGraph->Nodes)
{
@@ -996,7 +933,7 @@ UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph,
}
}
}
Error.SetError(FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *SMGraph->GetName()));
UMCPServer::Printf(TEXT("ERROR: State '%s' not found in graph '%s'\n"), *StateName, *SMGraph->GetName());
return nullptr;
}
@@ -1229,22 +1166,20 @@ FString MCPUtils::SetPropertyFromJson(
bool MCPUtils::PopulateFromJson(
UStruct* StructType,
void* Container,
const TSharedPtr<FJsonValue>& JsonValue,
MCPErrorCallback Error)
const TSharedPtr<FJsonValue>& JsonValue)
{
if (!JsonValue.IsValid() || (JsonValue->Type != EJson::Object))
{
Error.SetError(TEXT("Expected a JSON object"));
UMCPServer::Print(TEXT("ERROR: Expected a JSON object\n"));
return false;
}
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get(), Error);
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get());
}
bool MCPUtils::PopulateFromJson(
UStruct* StructType,
void* Container,
const FJsonObject* Json,
MCPErrorCallback Error)
const FJsonObject* Json)
{
// Build a set of known property names (as JSON keys) for the unknown-field check.
TSet<FString> KnownKeys;
@@ -1262,7 +1197,7 @@ bool MCPUtils::PopulateFromJson(
{
if (!KnownKeys.Contains(KV.Key))
{
Error.SetError(FString::Printf(TEXT("Unknown parameter '%s'"), *KV.Key));
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
return false;
}
}
@@ -1277,7 +1212,7 @@ bool MCPUtils::PopulateFromJson(
{
if (!bOptional)
{
Error.SetError(FString::Printf(TEXT("Missing required parameter '%s'"), *JsonKey));
UMCPServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
return false;
}
continue;
@@ -1286,7 +1221,7 @@ bool MCPUtils::PopulateFromJson(
FString PropError = SetPropertyFromJson(Container, Prop, JsonKey, Json);
if (!PropError.IsEmpty())
{
Error.SetError(PropError);
UMCPServer::Printf(TEXT("ERROR: %s\n"), *PropError);
return false;
}
}
@@ -1369,11 +1304,11 @@ FString MCPUtils::FormatPropertyType(FProperty* Prop)
// FindPropertyByName
// ============================================================
FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error)
FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name)
{
if (!Obj)
{
Error.SetError(TEXT("Object is null"));
UMCPServer::Print(TEXT("ERROR: Object is null\n"));
return nullptr;
}
@@ -1383,14 +1318,14 @@ FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name, MCPEr
if (!Identifies(Name, *PropIt)) continue;
if (Found)
{
Error.SetError(FString::Printf(TEXT("Ambiguous property '%s' on %s"), *Name, *FormatName(Obj->GetClass())));
UMCPServer::Printf(TEXT("ERROR: Ambiguous property '%s' on %s\n"), *Name, *FormatName(Obj->GetClass()));
return nullptr;
}
Found = *PropIt;
}
if (!Found)
Error.SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Name, *FormatName(Obj->GetClass())));
UMCPServer::Printf(TEXT("ERROR: Property '%s' not found on %s\n"), *Name, *FormatName(Obj->GetClass()));
return Found;
}
@@ -1411,27 +1346,27 @@ FString MCPUtils::GetPropertyValueText(UObject* Container, FProperty* Prop)
// SetPropertyValueText
// ============================================================
bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error)
bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Container, PPF_None);
if (!ImportResult)
{
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
*Value, *FormatName(Prop), *Prop->GetCPPType()));
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
*Value, *FormatName(Prop), *Prop->GetCPPType());
return false;
}
return true;
}
bool MCPUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner, MCPErrorCallback Error)
bool MCPUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Owner, PPF_None);
if (!ImportResult)
{
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
*Value, *FormatName(Prop), *Prop->GetCPPType()));
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
*Value, *FormatName(Prop), *Prop->GetCPPType());
return false;
}
return true;