2026-03-06 15:06:12 -05:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include "CoreMinimal.h"
|
|
|
|
|
#include "Dom/JsonObject.h"
|
|
|
|
|
#include "EdGraph/EdGraphPin.h"
|
|
|
|
|
|
|
|
|
|
class UBlueprint;
|
|
|
|
|
class UEdGraph;
|
|
|
|
|
class UEdGraphNode;
|
|
|
|
|
class UEdGraphPin;
|
|
|
|
|
class UMaterial;
|
2026-03-10 03:02:41 -04:00
|
|
|
class UMaterialInstance;
|
|
|
|
|
class UMaterialFunction;
|
2026-03-06 15:06:12 -05:00
|
|
|
class UMaterialExpression;
|
2026-03-10 20:15:59 -04:00
|
|
|
struct FEdGraphSchemaAction;
|
2026-03-06 15:06:12 -05:00
|
|
|
class UAnimationStateMachineGraph;
|
|
|
|
|
class UAnimStateNode;
|
|
|
|
|
class UAnimStateTransitionNode;
|
2026-03-10 00:22:56 -04:00
|
|
|
class UActorComponent;
|
|
|
|
|
class UWorld;
|
2026-03-10 03:02:41 -04:00
|
|
|
class UStaticMesh;
|
|
|
|
|
class USkeletalMesh;
|
|
|
|
|
class UAnimSequence;
|
|
|
|
|
class UBlendSpace;
|
|
|
|
|
class UTexture;
|
|
|
|
|
class UScriptStruct;
|
|
|
|
|
class UEnum;
|
2026-03-10 00:22:56 -04:00
|
|
|
struct FMemberReference;
|
|
|
|
|
struct FBPVariableDescription;
|
2026-03-06 15:06:12 -05:00
|
|
|
|
2026-03-06 21:46:03 -05:00
|
|
|
// ----- Log capture -----
|
|
|
|
|
|
|
|
|
|
class FLogCaptureOutputDevice : public FOutputDevice
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TArray<FString> CapturedErrors;
|
|
|
|
|
TArray<FString> CapturedWarnings;
|
|
|
|
|
|
|
|
|
|
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const FName& Category) override
|
|
|
|
|
{
|
|
|
|
|
FString Msg(V);
|
|
|
|
|
|
|
|
|
|
if (Verbosity == ELogVerbosity::Error || Verbosity == ELogVerbosity::Fatal)
|
|
|
|
|
{
|
|
|
|
|
CapturedErrors.Add(Msg);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Verbosity == ELogVerbosity::Warning)
|
|
|
|
|
{
|
|
|
|
|
if (!Msg.Contains(TEXT("BlueprintMCP:")))
|
|
|
|
|
{
|
|
|
|
|
CapturedWarnings.Add(Msg);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const TCHAR* ErrorPatterns[] = {
|
|
|
|
|
TEXT("Can't connect pins"),
|
|
|
|
|
TEXT("Fixed up function"),
|
|
|
|
|
TEXT("is not compatible with"),
|
|
|
|
|
TEXT("could not find a pin"),
|
|
|
|
|
TEXT("has an invalid"),
|
|
|
|
|
TEXT("orphaned pin"),
|
|
|
|
|
TEXT("is deprecated"),
|
|
|
|
|
TEXT("does not implement"),
|
|
|
|
|
TEXT("Missing function"),
|
|
|
|
|
TEXT("Unable to find"),
|
|
|
|
|
TEXT("Failed to resolve"),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const TCHAR* Pattern : ErrorPatterns)
|
|
|
|
|
{
|
|
|
|
|
if (Msg.Contains(Pattern))
|
|
|
|
|
{
|
|
|
|
|
CapturedWarnings.Add(Msg);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-08 05:00:07 -04:00
|
|
|
// ----- Error callback -----
|
|
|
|
|
|
|
|
|
|
struct MCPErrorCallback
|
|
|
|
|
{
|
|
|
|
|
TFunction<void(const FString&)> Func;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MCPErrorCallback(std::nullptr_t);
|
|
|
|
|
MCPErrorCallback(FString& OutError);
|
|
|
|
|
MCPErrorCallback(FJsonObject* Result);
|
2026-03-10 01:42:43 -04:00
|
|
|
MCPErrorCallback(const TSharedRef<FJsonObject>& Result) : MCPErrorCallback(&*Result) {}
|
2026-03-08 21:28:47 -04:00
|
|
|
MCPErrorCallback(FStringBuilderBase& OutResult);
|
2026-03-08 05:00:07 -04:00
|
|
|
|
|
|
|
|
void SetError(const FString& Msg) const { Func(Msg); }
|
|
|
|
|
};
|
2026-03-07 01:55:32 -05:00
|
|
|
|
2026-03-06 15:06:12 -05:00
|
|
|
// Stateless utility functions used by MCP handlers and the MCP server.
|
|
|
|
|
// This is effectively a namespace — all methods are static.
|
|
|
|
|
class MCPUtils
|
|
|
|
|
{
|
|
|
|
|
public:
|
2026-03-10 00:22:56 -04:00
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
//
|
|
|
|
|
// Name Formatting
|
|
|
|
|
//
|
|
|
|
|
// The goal here is to centralize the code that outputs
|
|
|
|
|
// names, and have everybody use it, so that names are
|
|
|
|
|
// used consistently. The secondary goal is to choose
|
|
|
|
|
// names that are as uniquely-identifying as is practical.
|
|
|
|
|
// It's not always 100% possible to get perfectly unique
|
|
|
|
|
// names, though, so our code needs to check for ambiguity.
|
|
|
|
|
//
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
|
2026-03-10 01:42:43 -04:00
|
|
|
static FString FormatName(const UWorld *World);
|
|
|
|
|
static FString FormatName(const UBlueprint *BP);
|
|
|
|
|
static FString FormatName(const UActorComponent *C);
|
|
|
|
|
static FString FormatName(const UEdGraph *Graph);
|
|
|
|
|
static FString FormatName(const UEdGraphNode* Node);
|
|
|
|
|
static FString FormatName(const UEdGraphPin *Pin);
|
2026-03-10 00:22:56 -04:00
|
|
|
static FString FormatName(const FMemberReference &Ref);
|
|
|
|
|
static FString FormatName(const FBPVariableDescription &Var);
|
2026-03-10 20:15:59 -04:00
|
|
|
static FString FormatName(const UStruct *Struct);
|
2026-03-10 03:02:41 -04:00
|
|
|
static FString FormatName(const UMaterial *Material);
|
|
|
|
|
static FString FormatName(const UMaterialInstance *MaterialInstance);
|
|
|
|
|
static FString FormatName(const UMaterialFunction *MaterialFunction);
|
|
|
|
|
static FString FormatName(const UMaterialExpression *Expression);
|
|
|
|
|
static FString FormatName(const UStaticMesh *Mesh);
|
|
|
|
|
static FString FormatName(const USkeletalMesh *Mesh);
|
|
|
|
|
static FString FormatName(const UAnimSequence *Anim);
|
|
|
|
|
static FString FormatName(const UBlendSpace *BlendSpace);
|
|
|
|
|
static FString FormatName(const UTexture *Texture);
|
|
|
|
|
static FString FormatName(const UScriptStruct *Struct);
|
|
|
|
|
static FString FormatName(const UEnum *Enum);
|
2026-03-10 20:15:59 -04:00
|
|
|
static FString FormatName(const FProperty *Prop);
|
2026-03-10 00:22:56 -04:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
//
|
|
|
|
|
// Identifies
|
|
|
|
|
//
|
|
|
|
|
// Return true if the name identifies the object. The
|
|
|
|
|
// FormatName functions, above, always return names that
|
|
|
|
|
// identify the object. However, there may be other
|
|
|
|
|
// names that also identify the object. Identifying names
|
|
|
|
|
// aren't 100% guaranteed to be unique, but very likely.
|
|
|
|
|
//
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
|
2026-03-10 01:42:43 -04:00
|
|
|
static bool Identifies(const FString &Name, const UWorld *World);
|
|
|
|
|
static bool Identifies(const FString &Name, const UBlueprint *BP);
|
|
|
|
|
static bool Identifies(const FString &Name, const UActorComponent *C);
|
|
|
|
|
static bool Identifies(const FString &Name, const UEdGraph *Graph);
|
|
|
|
|
static bool Identifies(const FString &Name, const UEdGraphNode* Node);
|
|
|
|
|
static bool Identifies(const FString &Name, const UEdGraphPin *Pin);
|
2026-03-10 00:22:56 -04:00
|
|
|
static bool Identifies(const FString &Name, const FMemberReference &Ref);
|
2026-03-10 20:15:59 -04:00
|
|
|
static bool Identifies(const FString &Name, const FBPVariableDescription &Var);
|
|
|
|
|
static bool Identifies(const FString &Name, const UStruct *Struct);
|
2026-03-10 03:02:41 -04:00
|
|
|
static bool Identifies(const FString &Name, const UMaterial *Material);
|
|
|
|
|
static bool Identifies(const FString &Name, const UMaterialInstance *MaterialInstance);
|
|
|
|
|
static bool Identifies(const FString &Name, const UMaterialFunction *MaterialFunction);
|
|
|
|
|
static bool Identifies(const FString &Name, const UMaterialExpression *Expression);
|
|
|
|
|
static bool Identifies(const FString &Name, const UStaticMesh *Mesh);
|
|
|
|
|
static bool Identifies(const FString &Name, const USkeletalMesh *Mesh);
|
|
|
|
|
static bool Identifies(const FString &Name, const UAnimSequence *Anim);
|
|
|
|
|
static bool Identifies(const FString &Name, const UBlendSpace *BlendSpace);
|
|
|
|
|
static bool Identifies(const FString &Name, const UTexture *Texture);
|
|
|
|
|
static bool Identifies(const FString &Name, const UScriptStruct *Struct);
|
|
|
|
|
static bool Identifies(const FString &Name, const UEnum *Enum);
|
2026-03-10 20:15:59 -04:00
|
|
|
static bool Identifies(const FString &Name, const FProperty *Prop);
|
2026-03-10 00:22:56 -04:00
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
static FString FormatPinType(const FEdGraphPinType& PinType);
|
|
|
|
|
static FString FormatPinType(UEdGraphPin* Pin);
|
|
|
|
|
|
2026-03-06 22:23:10 -05:00
|
|
|
// ----- Asset path helpers -----
|
|
|
|
|
// Splits "/Game/Foo/Bar" into PackagePath="/Game/Foo" and AssetName="Bar".
|
|
|
|
|
// Returns false if the path has no slash or the asset name is empty.
|
|
|
|
|
static bool SplitAssetPath(const FString& AssetPath, FString& OutPackagePath, FString& OutAssetName)
|
|
|
|
|
{
|
|
|
|
|
int32 LastSlash;
|
|
|
|
|
if (!AssetPath.FindLastChar('/', LastSlash))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPackagePath = AssetPath.Left(LastSlash);
|
|
|
|
|
OutAssetName = AssetPath.Mid(LastSlash + 1);
|
|
|
|
|
return !OutAssetName.IsEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 15:06:12 -05:00
|
|
|
// ----- JSON helpers -----
|
|
|
|
|
static TSharedPtr<FJsonObject> ParseBodyJson(const FString& Body);
|
|
|
|
|
static FString UrlDecode(const FString& EncodedString);
|
|
|
|
|
|
2026-03-08 05:00:07 -04:00
|
|
|
// ----- Enum helpers -----
|
|
|
|
|
// Convert enum value to string. If Prefix is specified, strip "Prefix_" from the front.
|
2026-03-08 21:28:47 -04:00
|
|
|
template<typename T>
|
|
|
|
|
static FString EnumToString(TEnumAsByte<T> Value, const FString& Prefix = FString())
|
|
|
|
|
{
|
|
|
|
|
return EnumToString<T>((T)Value, Prefix);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 05:00:07 -04:00
|
|
|
template<typename T>
|
|
|
|
|
static FString EnumToString(T Value, const FString& Prefix = FString())
|
|
|
|
|
{
|
|
|
|
|
UEnum* Enum = StaticEnum<T>();
|
|
|
|
|
FString Full = Enum->GetNameStringByValue((int64)Value);
|
|
|
|
|
if (!Prefix.IsEmpty() && Full.StartsWith(Prefix))
|
|
|
|
|
return Full.Mid(Prefix.Len());
|
|
|
|
|
return Full;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert string to enum value. If Prefix is specified, prepend it before lookup.
|
|
|
|
|
// Returns false and sets error if the string doesn't match any value.
|
|
|
|
|
template<typename T>
|
|
|
|
|
static bool StringToEnum(const FString& Str, T& OutValue, MCPErrorCallback Error, const FString& Prefix = FString())
|
|
|
|
|
{
|
|
|
|
|
UEnum* Enum = StaticEnum<T>();
|
|
|
|
|
int64 Value = Enum->GetValueByNameString(Prefix + Str);
|
|
|
|
|
if (Value == INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Invalid value '%s' for %s"), *Str, *Enum->GetName()));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutValue = (T)Value;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 15:06:12 -05:00
|
|
|
// ----- Blueprint helpers -----
|
2026-03-08 03:44:27 -04:00
|
|
|
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
|
|
|
|
|
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);
|
|
|
|
|
static TArray<UEdGraphNode*> AllNodes(UBlueprint* BP);
|
|
|
|
|
template<class T> static TArray<T*> AllNodes(UBlueprint* BP)
|
|
|
|
|
{
|
|
|
|
|
TArray<T*> Result;
|
|
|
|
|
for (UEdGraph* Graph : AllGraphs(BP))
|
|
|
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
|
|
|
if (T* Typed = Cast<T>(Node))
|
|
|
|
|
Result.Add(Typed);
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
template<class T> static TArray<T*> AllNodes(UEdGraph* Graph)
|
|
|
|
|
{
|
|
|
|
|
TArray<T*> Result;
|
|
|
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
|
|
|
if (T* Typed = Cast<T>(Node))
|
|
|
|
|
Result.Add(Typed);
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
static TArray<TSharedPtr<FJsonValue>> AllGraphNamesJson(UBlueprint* BP);
|
2026-03-06 15:06:12 -05:00
|
|
|
static UEdGraphNode* FindNodeByGuid(UBlueprint* BP, const FString& GuidString, UEdGraph** OutGraph = nullptr);
|
|
|
|
|
static bool SaveBlueprintPackage(UBlueprint* BP);
|
|
|
|
|
|
|
|
|
|
// ----- Serialization -----
|
|
|
|
|
static TSharedRef<FJsonObject> SerializeBlueprint(UBlueprint* BP);
|
|
|
|
|
static TSharedPtr<FJsonObject> SerializeNode(UEdGraphNode* Node);
|
|
|
|
|
static TSharedPtr<FJsonObject> SerializePin(UEdGraphPin* Pin);
|
|
|
|
|
static TSharedPtr<FJsonObject> SerializeMaterialExpression(UMaterialExpression* Expression);
|
|
|
|
|
|
|
|
|
|
// ----- Type resolution -----
|
|
|
|
|
static UClass* FindClassByName(const FString& ClassName);
|
2026-03-07 01:55:32 -05:00
|
|
|
static bool ResolveTypeFromString(const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error);
|
2026-03-06 15:06:12 -05:00
|
|
|
|
|
|
|
|
// ----- Material helpers -----
|
|
|
|
|
static void EnsureMaterialGraph(UMaterial* Material);
|
|
|
|
|
static bool SaveMaterialPackage(UMaterial* Material);
|
|
|
|
|
static bool SaveGenericPackage(UObject* Asset);
|
|
|
|
|
|
2026-03-10 20:15:59 -04:00
|
|
|
// If the material editor has a transient preview copy of this material,
|
|
|
|
|
// return that copy (which is what the editor is actually working on).
|
|
|
|
|
// Otherwise return the original.
|
|
|
|
|
static UMaterial* ReplaceMaterialWithTransientCopy(UMaterial* Material);
|
|
|
|
|
|
2026-03-06 15:06:12 -05:00
|
|
|
// ----- Anim blueprint helpers -----
|
|
|
|
|
static UAnimationStateMachineGraph* FindStateMachineGraph(UBlueprint* BP, const FString& GraphName);
|
2026-03-07 02:36:43 -05:00
|
|
|
static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, MCPErrorCallback Error);
|
2026-03-06 15:06:12 -05:00
|
|
|
static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph, const FString& FromStateName, const FString& ToStateName);
|
|
|
|
|
|
2026-03-10 20:15:59 -04:00
|
|
|
// ----- Graph actions (node spawning) -----
|
|
|
|
|
static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action);
|
|
|
|
|
static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false);
|
|
|
|
|
|
|
|
|
|
// ----- Editable template -----
|
|
|
|
|
// Given an object, returns the appropriate template object for generic
|
|
|
|
|
// property editing, or nullptr if the type isn't whitelisted.
|
|
|
|
|
// UBlueprint → CDO; UMaterial, UActorComponent, etc. → as-is.
|
|
|
|
|
static UObject* GetEditableTemplate(UObject* Obj, MCPErrorCallback Error);
|
|
|
|
|
static TArray<FProperty*> BlueprintVisibleProperties(UObject* Obj, const FString& Query = FString());
|
|
|
|
|
|
|
|
|
|
static FProperty* FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error = nullptr);
|
|
|
|
|
static FString GetPropertyValueText(UObject* Container, FProperty* Prop);
|
|
|
|
|
static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error = nullptr);
|
2026-03-06 15:06:12 -05:00
|
|
|
|
|
|
|
|
// ----- Property population -----
|
2026-03-06 19:03:33 -05:00
|
|
|
static FString PropertyNameToJsonKey(const FString& PropName);
|
2026-03-08 03:58:06 -04:00
|
|
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue, MCPErrorCallback Error);
|
|
|
|
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json, MCPErrorCallback Error);
|
2026-03-06 17:14:25 -05:00
|
|
|
|
2026-03-09 03:44:35 -04:00
|
|
|
// ----- Handler discovery -----
|
|
|
|
|
// Collect all concrete IMCPHandler classes, sorted by tool name.
|
|
|
|
|
static TArray<UClass*> CollectHandlerClasses();
|
|
|
|
|
|
2026-03-08 21:28:47 -04:00
|
|
|
// ----- Command help -----
|
2026-03-08 22:00:29 -04:00
|
|
|
// Derive tool name from handler class name: "MCPHandler_FooBar" → "FooBar"
|
|
|
|
|
static FString GetToolName(UClass* HandlerClass);
|
2026-03-08 21:28:47 -04:00
|
|
|
static FString FormatPropertyType(FProperty* Prop);
|
|
|
|
|
static void FormatCommandHelp(UClass* HandlerClass, FStringBuilderBase& Result);
|
|
|
|
|
|
2026-03-06 17:14:25 -05:00
|
|
|
private:
|
2026-03-10 00:22:56 -04:00
|
|
|
static void SanitizeNameInPlace(FString& Name);
|
|
|
|
|
static void AppendNumericSuffix(FString &Name, int32 N);
|
2026-03-06 17:14:25 -05:00
|
|
|
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
|
2026-03-06 15:06:12 -05:00
|
|
|
};
|