From 57f72d4a0eaee6e43d83d66eb6aa52d80c0f9d9b Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 14 Mar 2026 05:03:51 -0400 Subject: [PATCH] Start thinking about types --- .../BlueprintMCP/Handlers/Blueprint_Compile.h | 115 ++------------ .../Handlers/GraphNode_PrintPinType.h | 58 ++++++++ .../Source/BlueprintMCP/Private/MCPTypes.cpp | 140 ++++++++++++++++++ .../Source/BlueprintMCP/Public/MCPTypes.h | 46 ++++++ 4 files changed, 257 insertions(+), 102 deletions(-) create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Compile.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Compile.h index b67ff73d..0112aef5 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Compile.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Compile.h @@ -3,7 +3,7 @@ #include "CoreMinimal.h" #include "MCPServer.h" #include "MCPHandler.h" -#include "MCPAssets.h" +#include "MCPFetcher.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" #include "Kismet2/KismetEditorUtilities.h" @@ -20,124 +20,35 @@ class UMCP_Blueprint_Compile : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Optional, Description="Blueprint name or package path. If specified, compile this single blueprint.")) - FString Blueprint; - - UPROPERTY(meta=(Optional, Description="Substring search query. If specified instead of blueprint, compile all matching blueprints.")) - FString Query; - - UPROPERTY(meta=(Optional, Description="If true, return the count of matching blueprints without compiling.")) - bool CountOnly = false; - - UPROPERTY(meta=(Optional, Description="Starting index for pagination (default 0).")) - int32 Offset = 0; - - UPROPERTY(meta=(Optional, Description="Maximum number of blueprints to compile (default 0 = no limit).")) - int32 Limit = 0; + UPROPERTY(meta=(Optional, Description="Path of the blueprint.")) + FString Path; virtual FString GetDescription() const override { - return TEXT("Compile one or more blueprints without saving. " - "Reports errors and warnings. " - "Use 'blueprint' for a single blueprint, or 'query' to bulk-compile matching blueprints."); + return TEXT("Compile a blueprint. "); } - // Compile a single blueprint and append results to Out. - // Returns true if the blueprint compiled cleanly (no errors). - static bool CompileAndReport(UBlueprint* BP) + virtual void Handle() override { + MCPFetcher F; + UBlueprint *BP = F.Walk(Path).Cast(); + EBlueprintCompileOptions CompileOpts = - EBlueprintCompileOptions::SkipSave | - EBlueprintCompileOptions::SkipGarbageCollection | - EBlueprintCompileOptions::SkipFiBSearchMetaUpdate; + EBlueprintCompileOptions::SkipSave | + EBlueprintCompileOptions::SkipGarbageCollection | + EBlueprintCompileOptions::SkipFiBSearchMetaUpdate; FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr); - int32 ErrorCount = 0; - int32 WarningCount = 0; - // Collect compiler messages from nodes for (UEdGraphNode* Node : MCPUtils::AllNodes(BP)) { if (!Node->bHasCompilerMessage) continue; - bool bIsError = (Node->ErrorType == EMessageSeverity::Error); - if (bIsError) ErrorCount++; else WarningCount++; - UMCPServer::Printf(TEXT(" %s: [%s] %s > %s: %s\n"), - bIsError ? TEXT("ERROR") : TEXT("WARNING"), + UMCPServer::Printf(TEXT("%s %s: %s\n"), *MCPUtils::FormatName(Node->GetGraph()), *MCPUtils::FormatName(Node), - *MCPUtils::FormatName(Node->GetClass()), *Node->ErrorMsg); } - - FString StatusStr = MCPUtils::EnumToString((EBlueprintStatus)BP->Status, TEXT("BS_")); - bool bIsValid = (BP->Status == BS_UpToDate) && (ErrorCount == 0); - - if (bIsValid && WarningCount == 0) - { - UMCPServer::Printf(TEXT(" OK (status: %s)\n"), *StatusStr); - } - else - { - UMCPServer::Printf(TEXT(" status: %s, errors: %d, warnings: %d\n"), - *StatusStr, ErrorCount, WarningCount); - } - - return bIsValid; - } - - virtual void Handle() override - { - MCPAssets Finder; - Finder.Scan().Scan(); - if (!Blueprint.IsEmpty()) - { - if (!Finder.Exact(Blueprint).ENone().ETwo().Info()) return; - } - else - { - if (!Finder.Substring(Query).Info()) return; - } - - const TArray& MatchingAssets = Finder.AllData(); - int32 TotalMatching = MatchingAssets.Num(); - - // countOnly: return count without compiling anything - if (CountOnly) - { - UMCPServer::Printf(TEXT("Matching blueprints: %d\n"), TotalMatching); - return; - } - - // Compute range - int32 StartIdx = FMath::Clamp(Offset, 0, TotalMatching); - int32 EndIdx = (Limit > 0) ? FMath::Min(StartIdx + Limit, TotalMatching) : TotalMatching; - - int32 TotalChecked = 0; - int32 TotalPassed = 0; - int32 TotalFailed = 0; - - for (int32 Idx = StartIdx; Idx < EndIdx; Idx++) - { - const FAssetData& Asset = MatchingAssets[Idx]; - FString PackagePath = Asset.PackageName.ToString(); - - // Load the Blueprint (handles both regular and level blueprints) - MCPAssets Loader; - Loader.Scan().Scan(); - if (!Loader.Exact(PackagePath).ENone().ETwo().Load()) - { - continue; - } - UBlueprint* BP = Loader.Object(); - - TotalChecked++; - UMCPServer::Printf(TEXT("%s:\n"), *MCPUtils::FormatName(BP)); - bool bValid = CompileAndReport(BP); - if (bValid) TotalPassed++; else TotalFailed++; - } - - UMCPServer::Printf(TEXT("\nSummary: %d checked, %d passed, %d failed (of %d matching)\n"), - TotalChecked, TotalPassed, TotalFailed, TotalMatching); + UMCPServer::Printf(TEXT("Compilation Done.\n")); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h new file mode 100644 index 00000000..73ce197f --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h @@ -0,0 +1,58 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPServer.h" +#include "MCPHandler.h" +#include "MCPFetcher.h" +#include "MCPUtils.h" +#include "EdGraph/EdGraphNode.h" +#include "EdGraphSchema_K2.h" +#include "GraphNode_PrintPinType.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCP_GraphNode_ListPinTypes : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + virtual FString GetDescription() const override + { + return TEXT("List every type available in the variable type selector menu, as Category|Item."); + } + + virtual void Handle() override + { + const UEdGraphSchema_K2* Schema = GetDefault(); + TArray> TypeTree; + Schema->GetVariableTypeTree(TypeTree, ETypeTreeFilter::None); + + for (const TSharedPtr& Root : TypeTree) + { + PrintTree(Root, FString()); + } + } + +private: + void PrintTree(const TSharedPtr& Node, const FString& Category) + { + FString Desc = Node->GetDescription().ToString(); + + if (!Node->bReadOnly) + { + UObject *SCO = Node->GetPinTypeNoResolve().PinSubCategoryObject.Get(); + FString Path = SCO ? SCO->GetPathName() : *Node->GetCachedAssetData().GetObjectPathString(); + UMCPServer::Printf(TEXT("%s%s --- %s\n"), *Category, *Desc, *Path); + } + + FString ChildCategory = FString::Printf(TEXT("%s%s|"), *Category, *Desc); + for (const TSharedPtr& Child : Node->Children) + { + PrintTree(Child, ChildCategory); + } + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp new file mode 100644 index 00000000..c1c15d14 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp @@ -0,0 +1,140 @@ +#include "MCPTypes.h" +#include "EdGraphSchema_K2.h" + +// --------------------------------------------------------------------------- +// Helper: format a UField name with module prefix if not in /Script/Engine. +// --------------------------------------------------------------------------- + +static FString FormatFieldName(UField* Field, const FString& TypePrefix) +{ + // Package name is /Script/ModuleName + FString ModuleName = Field->GetOutermost()->GetName().Mid(8); + + FString Name = TypePrefix + Field->GetName(); + if (ModuleName != TEXT("Engine")) + return ModuleName + TEXT("::") + Name; + return Name; +} + +static FString FormatClassName(UClass* Class) +{ + FString Prefix = Class->IsChildOf(AActor::StaticClass()) ? TEXT("A") : TEXT("U"); + return FormatFieldName(Class, Prefix); +} + +static FString FormatStructName(UScriptStruct* Struct) +{ + return FormatFieldName(Struct, TEXT("F")); +} + +static FString FormatEnumName(UEnum* Enum) +{ + return FormatFieldName(Enum, TEXT("E")); +} + +// --------------------------------------------------------------------------- +// TypeToText +// --------------------------------------------------------------------------- + +static FString TerminalToText(const FEdGraphPinType& PinType) +{ + const FName& Cat = PinType.PinCategory; + UObject* SubObj = PinType.PinSubCategoryObject.Get(); + + if (Cat == UEdGraphSchema_K2::PC_Boolean) return TEXT("bool"); + if (Cat == UEdGraphSchema_K2::PC_Int) return TEXT("int32"); + if (Cat == UEdGraphSchema_K2::PC_Int64) return TEXT("int64"); + if (Cat == UEdGraphSchema_K2::PC_Name) return TEXT("FName"); + if (Cat == UEdGraphSchema_K2::PC_String) return TEXT("FString"); + if (Cat == UEdGraphSchema_K2::PC_Text) return TEXT("FText"); + + if (Cat == UEdGraphSchema_K2::PC_Real) + { + if (PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float) + return TEXT("float"); + return TEXT("double"); + } + + if (Cat == UEdGraphSchema_K2::PC_Byte) + { + if (UEnum* Enum = Cast(SubObj)) + return FormatEnumName(Enum); + return TEXT("uint8"); + } + + if (Cat == UEdGraphSchema_K2::PC_Enum) + { + if (UEnum* Enum = Cast(SubObj)) + return FormatEnumName(Enum); + return TEXT("uint8"); + } + + if (Cat == UEdGraphSchema_K2::PC_Struct) + { + if (UScriptStruct* Struct = Cast(SubObj)) + return FormatStructName(Struct); + return FString(); + } + + if (Cat == UEdGraphSchema_K2::PC_Object) + { + if (UClass* Class = Cast(SubObj)) + return FormatClassName(Class) + TEXT("*"); + return FString(); + } + + if (Cat == UEdGraphSchema_K2::PC_Class) + { + if (UClass* Class = Cast(SubObj)) + return FString::Printf(TEXT("TSubclassOf<%s>"), *FormatClassName(Class)); + return FString(); + } + + if (Cat == UEdGraphSchema_K2::PC_SoftObject) + { + if (UClass* Class = Cast(SubObj)) + return FString::Printf(TEXT("TSoftObjectPtr<%s>"), *FormatClassName(Class)); + return FString(); + } + + if (Cat == UEdGraphSchema_K2::PC_SoftClass) + { + if (UClass* Class = Cast(SubObj)) + return FString::Printf(TEXT("TSoftClassPtr<%s>"), *FormatClassName(Class)); + return FString(); + } + + if (Cat == UEdGraphSchema_K2::PC_Interface) + { + if (UClass* Class = Cast(SubObj)) + return FString::Printf(TEXT("TScriptInterface<%s>"), *FormatClassName(Class)); + return FString(); + } + + return FString(); +} + +FString MCPTypes::TypeToText(const FEdGraphPinType& PinType) +{ + FString Inner = TerminalToText(PinType); + if (Inner.IsEmpty()) + return FString(); + + if (PinType.IsArray()) + return FString::Printf(TEXT("TArray<%s>"), *Inner); + if (PinType.IsSet()) + return FString::Printf(TEXT("TSet<%s>"), *Inner); + if (PinType.IsMap()) + { + FEdGraphPinType ValueType; + ValueType.PinCategory = PinType.PinValueType.TerminalCategory; + ValueType.PinSubCategory = PinType.PinValueType.TerminalSubCategory; + ValueType.PinSubCategoryObject = PinType.PinValueType.TerminalSubCategoryObject; + FString ValueInner = TerminalToText(ValueType); + if (ValueInner.IsEmpty()) + return FString(); + return FString::Printf(TEXT("TMap<%s, %s>"), *Inner, *ValueInner); + } + + return Inner; +} diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h new file mode 100644 index 00000000..9513df51 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "EdGraph/EdGraphPin.h" + +// --------------------------------------------------------------------------- +// MCPTypes — converts between FEdGraphPinType and a concise C++-like string. +// +// The text format mirrors GetCPPType where possible: +// bool, uint8, int32, int64, float, double, FName, FString, FText +// FVector, FRotator, FTransform, ... (structs) +// EBlendMode, ... (enums) +// AActor*, UStaticMesh*, ... (object references) +// TSubclassOf (class references) +// TSoftObjectPtr (soft object references) +// TSoftClassPtr (soft class references) +// TScriptInterface (interfaces) +// TArray, TSet, TMap (containers) +// +// TypeToText and TextToType are inverses of each other. +// --------------------------------------------------------------------------- + + + + + + +class MCPTypes +{ +public: + // Convert an FEdGraphPinType to a concise string. + // Returns empty string on failure. + FString TypeToText(const FEdGraphPinType& PinType); + + // Convert a concise string back to an FEdGraphPinType. + // Returns true on success. Prints error via UMCPServer on failure. + bool TextToType(const FString& Text, FEdGraphPinType& OutPinType); + + // Same as TextToType, but does not print an error message on failure. + bool TryTextToType(const FString& Text, FEdGraphPinType& OutPinType); + +private: + TArray Tokens; + int32 Cursor = 0; + +};