diff --git a/Content/Widgets/WB_Hotkeys.uasset b/Content/Widgets/WB_Hotkeys.uasset index a39c86bf..03e72a2f 100644 --- a/Content/Widgets/WB_Hotkeys.uasset +++ b/Content/Widgets/WB_Hotkeys.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1f49b0aee588e2675b41d866fd454b2e03b17d92e220ecd3502228c952594a5 -size 254912 +oid sha256:54dbf0e29b1c24d490bca37ef178a85a1d9d8ac970f9409ffbdf0fa38ea5b711 +size 253564 diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintComponent.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintComponent.h index 58488a88..8c4d2aef 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintComponent.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintComponent.h @@ -59,7 +59,8 @@ public: const TArray& ExistingNodes = SCS->GetAllNodes(); for (USCS_Node* Existing : ExistingNodes) { - if (Existing && Existing->GetVariableName().ToString().Equals(Component, ESearchCase::IgnoreCase)) + if (Existing && Existing->ComponentTemplate && + MCPUtils::Identifies(Component, Existing->ComponentTemplate)) { return MCPUtils::MakeErrorJson(Result, FString::Printf( TEXT("A component named '%s' already exists in Blueprint '%s'"), @@ -68,42 +69,13 @@ public: } // Resolve the component class by name - // Try multiple name variants: exact name, with U prefix, without U prefix UClass* ComponentClassObj = nullptr; - - TArray NamesToTry; - NamesToTry.Add(ComponentClass); - if (!ComponentClass.StartsWith(TEXT("U"))) - { - NamesToTry.Add(FString::Printf(TEXT("U%s"), *ComponentClass)); - } - else - { - // Also try without U prefix - NamesToTry.Add(ComponentClass.Mid(1)); - } - for (TObjectIterator It; It; ++It) { - if (!It->IsChildOf(UActorComponent::StaticClass())) - { - continue; - } - - FString ClassName = It->GetName(); - for (const FString& NameToTry : NamesToTry) - { - if (ClassName.Equals(NameToTry, ESearchCase::IgnoreCase)) - { - ComponentClassObj = *It; - break; - } - } - - if (ComponentClassObj) - { - break; - } + if (!It->IsChildOf(UActorComponent::StaticClass())) continue; + if (!MCPUtils::Identifies(ComponentClass, *It)) continue; + ComponentClassObj = *It; + break; } if (!ComponentClassObj) @@ -123,7 +95,8 @@ public: { for (USCS_Node* Node : ExistingNodes) { - if (Node && Node->GetVariableName().ToString().Equals(ParentComponent, ESearchCase::IgnoreCase)) + if (Node && Node->ComponentTemplate && + MCPUtils::Identifies(ParentComponent, Node->ComponentTemplate)) { ParentSCSNode = Node; break; @@ -139,7 +112,7 @@ public: } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding component '%s' (%s) to Blueprint '%s'"), - *Component, *ComponentClassObj->GetName(), *Blueprint); + *Component, *MCPUtils::FormatName(ComponentClassObj), *Blueprint); // Create the SCS node USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component)); @@ -147,7 +120,7 @@ public: { return MCPUtils::MakeErrorJson(Result, FString::Printf( TEXT("Failed to create SCS node for component '%s' with class '%s'"), - *Component, *ComponentClassObj->GetName())); + *Component, *MCPUtils::FormatName(ComponentClassObj))); } // Add to the hierarchy @@ -164,15 +137,15 @@ public: bool bSaved = MCPUtils::SaveBlueprintPackage(BP); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added component '%s' (%s) to '%s' (parent: %s, saved: %s)"), - *Component, *ComponentClassObj->GetName(), *Blueprint, + *Component, *MCPUtils::FormatName(ComponentClassObj), *Blueprint, ParentSCSNode ? *ParentComponent : TEXT("(root)"), bSaved ? TEXT("true") : TEXT("false")); - Result->SetStringField(TEXT("component"), NewNode->GetVariableName().ToString()); - Result->SetStringField(TEXT("componentClass"), ComponentClassObj->GetName()); + Result->SetStringField(TEXT("component"), MCPUtils::FormatName(NewNode->ComponentTemplate)); + Result->SetStringField(TEXT("componentClass"), MCPUtils::FormatName(ComponentClassObj)); if (ParentSCSNode) { - Result->SetStringField(TEXT("parentComponent"), ParentSCSNode->GetVariableName().ToString()); + Result->SetStringField(TEXT("parentComponent"), MCPUtils::FormatName(ParentSCSNode->ComponentTemplate)); } Result->SetBoolField(TEXT("saved"), bSaved); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintInterface.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintInterface.h index 01253a1b..02e1d833 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintInterface.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddBlueprintInterface.h @@ -106,7 +106,7 @@ public: FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"), - *InterfaceClass->GetName(), *Blueprint); + *MCPUtils::FormatName(InterfaceClass), *Blueprint); bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath); if (!bAdded) @@ -126,7 +126,7 @@ public: { if (Graph) { - AddedFunctions.Add(Graph->GetName()); + AddedFunctions.Add(MCPUtils::FormatName(Graph)); } } break; @@ -137,9 +137,9 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"), - *InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num()); + *MCPUtils::FormatName(InterfaceClass), *Blueprint, AddedFunctions.Num()); - Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); + Result->SetStringField(TEXT("interfaceName"), MCPUtils::FormatName(InterfaceClass)); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName()); TArray> FuncArr; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddFunctionParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddFunctionParameter.h index 56845e2b..1f5754c1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddFunctionParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_AddFunctionParameter.h @@ -62,7 +62,7 @@ public: for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes(BP)) { UEdGraph* FEGraph = FE->GetGraph(); - if (!FEGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) continue; + if (!MCPUtils::Identifies(FunctionName, FEGraph)) continue; // Skip delegate signature graphs (handled in Strategy 3) if (BP->DelegateSignatureGraphs.Contains(FEGraph)) continue; @@ -91,7 +91,7 @@ public: for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes(BP)) { UEdGraph* FEGraph = FE->GetGraph(); - if (!FEGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) continue; + if (!MCPUtils::Identifies(FunctionName, FEGraph)) continue; if (!BP->DelegateSignatureGraphs.Contains(FEGraph)) continue; EntryNode = FE; @@ -107,7 +107,7 @@ public: for (UEdGraph* Graph : BP->FunctionGraphs) { - if (Graph) AvailFuncs.Add(MakeShared(Graph->GetName())); + if (Graph) AvailFuncs.Add(MakeShared(MCPUtils::FormatName(Graph))); } // Custom events diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeBlueprintVariableType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeBlueprintVariableType.h index a9f23daa..eda5dc8d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeBlueprintVariableType.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeBlueprintVariableType.h @@ -114,7 +114,7 @@ public: TSharedRef AffNode = MakeShared(); AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString()); AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet")); - AffNode->SetStringField(TEXT("graph"), VG->GetGraph()->GetName()); + AffNode->SetStringField(TEXT("graph"), MCPUtils::FormatName(VG->GetGraph())); TArray> AffPins; for (UEdGraphPin* Pin : VG->Pins) { @@ -122,7 +122,7 @@ public: { AffPins.Add(MakeShared( FString::Printf(TEXT("%s (connected to %d pin(s))"), - *Pin->PinName.ToString(), Pin->LinkedTo.Num()))); + *MCPUtils::FormatName(Pin), Pin->LinkedTo.Num()))); } } AffNode->SetArrayField(TEXT("affectedPins"), AffPins); @@ -134,7 +134,7 @@ public: TSharedRef AffNode = MakeShared(); AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString()); AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet")); - AffNode->SetStringField(TEXT("graph"), VS->GetGraph()->GetName()); + AffNode->SetStringField(TEXT("graph"), MCPUtils::FormatName(VS->GetGraph())); TArray> AffPins; for (UEdGraphPin* Pin : VS->Pins) { @@ -142,7 +142,7 @@ public: { AffPins.Add(MakeShared( FString::Printf(TEXT("%s (connected to %d pin(s))"), - *Pin->PinName.ToString(), Pin->LinkedTo.Num()))); + *MCPUtils::FormatName(Pin), Pin->LinkedTo.Num()))); } } AffNode->SetArrayField(TEXT("affectedPins"), AffPins); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeFunctionParameterType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeFunctionParameterType.h index 7203791d..0660b173 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeFunctionParameterType.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeFunctionParameterType.h @@ -63,7 +63,7 @@ public: // Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes(BP)) { - if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) + if (MCPUtils::Identifies(FunctionName, FuncEntry->GetGraph())) { EntryNode = FuncEntry; FoundNodeType = TEXT("FunctionEntry"); @@ -92,7 +92,7 @@ public: for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes(BP)) { Available.Add(MakeShared( - FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName()))); + FString::Printf(TEXT("function:%s"), *MCPUtils::FormatName(FE->GetGraph())))); } for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes(BP)) { @@ -147,16 +147,16 @@ public: TArray> AffectedPins; for (UEdGraphPin* Pin : EntryNode->Pins) { - if (Pin && Pin->PinName.ToString().Equals(ParamName, ESearchCase::IgnoreCase) && Pin->LinkedTo.Num() > 0) + if (Pin && MCPUtils::Identifies(ParamName, Pin) && Pin->LinkedTo.Num() > 0) { for (UEdGraphPin* Linked : Pin->LinkedTo) { if (Linked && Linked->GetOwningNode()) { TSharedRef AffPin = MakeShared(); - AffPin->SetStringField(TEXT("pinName"), Pin->PinName.ToString()); + AffPin->SetStringField(TEXT("pinName"), MCPUtils::FormatName(Pin)); AffPin->SetStringField(TEXT("connectedToNode"), Linked->GetOwningNode()->NodeGuid.ToString()); - AffPin->SetStringField(TEXT("connectedToPin"), Linked->PinName.ToString()); + AffPin->SetStringField(TEXT("connectedToPin"), MCPUtils::FormatName(Linked)); AffPin->SetStringField(TEXT("currentType"), Pin->PinType.PinCategory.ToString()); if (Pin->PinType.PinSubCategoryObject.IsValid()) AffPin->SetStringField(TEXT("currentSubtype"), Pin->PinType.PinSubCategoryObject->GetName()); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeStructNodeType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeStructNodeType.h index fc9a5323..3b1ed914 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeStructNodeType.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ChangeStructNodeType.h @@ -99,7 +99,7 @@ public: if (!BreakNode && !MakeNode) { return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' is not a BreakStruct or MakeStruct node (class: %s)"), - *Node, *FoundNode->GetClass()->GetName())); + *Node, *MCPUtils::FormatName(FoundNode->GetClass()))); } // Find the new struct type @@ -267,7 +267,7 @@ public: TSharedPtr UpdatedNodeState = MCPUtils::SerializeNode(FoundNode); Result->SetStringField(TEXT("newStructType"), NewStruct->GetName()); - Result->SetStringField(TEXT("nodeClass"), FoundNode->GetClass()->GetName()); + Result->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(FoundNode->GetClass())); Result->SetNumberField(TEXT("reconnected"), Reconnected); Result->SetNumberField(TEXT("failed"), Failed); Result->SetArrayField(TEXT("reconnectDetails"), ReconnectDetails); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CompileBlueprint.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CompileBlueprint.h index df8642db..e819f4d8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CompileBlueprint.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CompileBlueprint.h @@ -67,10 +67,10 @@ public: if (Node->bHasCompilerMessage) { TSharedRef Msg = MakeShared(); - Msg->SetStringField(TEXT("graph"), Node->GetGraph()->GetName()); + Msg->SetStringField(TEXT("graph"), MCPUtils::FormatName(Node->GetGraph())); Msg->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); - Msg->SetStringField(TEXT("nodeTitle"), Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); - Msg->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName()); + Msg->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Node)); + Msg->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(Node->GetClass())); Msg->SetStringField(TEXT("message"), Node->ErrorMsg); if (Node->ErrorType == EMessageSeverity::Error) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectBlueprintPins.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectBlueprintPins.h deleted file mode 100644 index e614f47b..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectBlueprintPins.h +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MCPHandler.h" -#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" -#include "Kismet2/BlueprintEditorUtils.h" -#include "UMCPHandler_ConnectBlueprintPins.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -USTRUCT() -struct FConnectPinsEntry -{ - GENERATED_BODY() - - UPROPERTY() - FString SourceNode; - - UPROPERTY() - FString SourcePinName; - - UPROPERTY() - FString TargetNode; - - UPROPERTY() - FString TargetPinName; -}; - - -UCLASS() -class UMCPHandler_ConnectBlueprintPins : public UObject, public IMCPHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; - - UPROPERTY(meta=(Description="Array of {sourceNode, sourcePinName, targetNode, targetPinName} objects")) - FMCPJsonArray Connections; - - virtual FString GetDescription() const override - { - return TEXT("Connect pins between nodes in a Blueprint graph."); - } - - virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override - { - - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); - - TArray> Results; - int32 SuccessCount = 0; - - for (const TSharedPtr& ConnVal : Connections.Array) - { - TSharedRef EntryResult = MakeShared(); - Results.Add(MakeShared(EntryResult)); - - FConnectPinsEntry Entry; - if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, &*EntryResult)) continue; - - UEdGraph* SourceGraph = nullptr; - UEdGraphNode* SourceNode = MCPUtils::FindNodeByGuid(BP, Entry.SourceNode, &SourceGraph); - if (!SourceNode) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Source node '%s' not found"), *Entry.SourceNode)); - continue; - } - - UEdGraphNode* TargetNode = MCPUtils::FindNodeByGuid(BP, Entry.TargetNode); - if (!TargetNode) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target node '%s' not found"), *Entry.TargetNode)); - continue; - } - - UEdGraphPin* SourcePin = SourceNode->FindPin(FName(*Entry.SourcePinName)); - if (!SourcePin) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *Entry.SourcePinName, *Entry.SourceNode)); - continue; - } - - UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*Entry.TargetPinName)); - if (!TargetPin) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *Entry.TargetPinName, *Entry.TargetNode)); - continue; - } - - const UEdGraphSchema* Schema = SourceGraph->GetSchema(); - if (!Schema) - { - EntryResult->SetStringField(TEXT("error"), TEXT("Graph schema not found")); - continue; - } - - bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin); - if (!bConnected) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf( - TEXT("Cannot connect %s (%s) to %s (%s) — types are incompatible"), - *Entry.SourcePinName, *SourcePin->PinType.PinCategory.ToString(), - *Entry.TargetPinName, *TargetPin->PinType.PinCategory.ToString())); - continue; - } - - SuccessCount++; - } - - if (SuccessCount > 0) - { - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); - } - - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"), - SuccessCount, Connections.Array.Num(), *Blueprint); - Result->SetNumberField(TEXT("successCount"), SuccessCount); - Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num()); - Result->SetArrayField(TEXT("results"), Results); - } -}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectPins.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectPins.h new file mode 100644 index 00000000..7fba8f4d --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ConnectPins.h @@ -0,0 +1,102 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPFetcher.h" +#include "MCPUtils.h" +#include "Engine/Blueprint.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphNode.h" +#include "EdGraph/EdGraphPin.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "UMCPHandler_ConnectPins.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +USTRUCT() +struct FConnectPinsEntry +{ + GENERATED_BODY() + + UPROPERTY() + FString SourcePin; + + UPROPERTY() + FString TargetPin; +}; + + +UCLASS() +class UMCPHandler_ConnectPins : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Blueprint name or package path")) + FString Blueprint; + + UPROPERTY(meta=(Description="Array of {sourcePin, targetPin} objects. Each pin is a path like node:MyNode,pin:Output")) + FMCPJsonArray Connections; + + virtual FString GetDescription() const override + { + return TEXT("Connect pins between nodes in a Blueprint graph."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + + MCPFetcher F(Result); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; + + TArray> Results; + int32 SuccessCount = 0; + + for (const TSharedPtr& ConnVal : Connections.Array) + { + TSharedRef EntryResult = MakeShared(); + Results.Add(MakeShared(EntryResult)); + + FConnectPinsEntry Entry; + if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, &*EntryResult)) continue; + + MCPFetcher FS(EntryResult, BP); + UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast(); + if (!SourcePin) continue; + + MCPFetcher FT(EntryResult, BP); + UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast(); + if (!TargetPin) continue; + + const UEdGraphSchema* Schema = SourcePin->GetOwningNode()->GetGraph()->GetSchema(); + const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); + if (Response.Response == CONNECT_RESPONSE_DISALLOW) + { + EntryResult->SetStringField(TEXT("error"), FString::Printf( + TEXT("Cannot connect %s.%s to %s.%s: %s"), + *MCPUtils::FormatName(SourcePin->GetOwningNode()), *MCPUtils::FormatName(SourcePin), + *MCPUtils::FormatName(TargetPin->GetOwningNode()), *MCPUtils::FormatName(TargetPin), + *Response.Message.ToString())); + continue; + } + + Schema->TryCreateConnection(SourcePin, TargetPin); + SuccessCount++; + } + + if (SuccessCount > 0) + { + FBlueprintEditorUtils::MarkBlueprintAsModified(BP); + } + + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"), + SuccessCount, Connections.Array.Num(), *Blueprint); + Result->SetNumberField(TEXT("successCount"), SuccessCount); + Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num()); + Result->SetArrayField(TEXT("results"), Results); + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateAnimBlueprintAsset.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateAnimBlueprintAsset.h index 535ef579..f80261f6 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateAnimBlueprintAsset.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateAnimBlueprintAsset.h @@ -127,7 +127,7 @@ public: Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName()); - Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); + Result->SetStringField(TEXT("parentClass"), MCPUtils::FormatName(ParentClassObj)); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetArrayField(TEXT("graphs"), GraphNames); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateBlueprintAsset.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateBlueprintAsset.h index 5a0b42e4..05b2616f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateBlueprintAsset.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateBlueprintAsset.h @@ -100,7 +100,7 @@ public: } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blueprint '%s' in '%s' with parent '%s' (type=%s)"), - *Blueprint, *PackagePath, *ParentClassObj->GetName(), *BlueprintType); + *Blueprint, *PackagePath, *MCPUtils::FormatName(ParentClassObj), *BlueprintType); // Create the package FString FullPackagePath = PackagePath / Blueprint; @@ -139,7 +139,7 @@ public: *Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); Result->SetStringField(TEXT("assetPath"), FullAssetPath); - Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); + Result->SetStringField(TEXT("parentClass"), MCPUtils::FormatName(ParentClassObj)); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetArrayField(TEXT("graphs"), GraphNames); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteBlueprintGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteBlueprintGraph.h index 1b959b73..baafd65f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteBlueprintGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteBlueprintGraph.h @@ -48,7 +48,7 @@ public: for (UEdGraph* CandidateGraph : BP->FunctionGraphs) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { TargetGraph = CandidateGraph; GraphType = TEXT("function"); @@ -59,7 +59,7 @@ public: { for (UEdGraph* CandidateGraph : BP->MacroGraphs) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { TargetGraph = CandidateGraph; GraphType = TEXT("macro"); @@ -73,7 +73,7 @@ public: { for (UEdGraph* CandidateGraph : BP->UbergraphPages) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { return MCPUtils::MakeErrorJson(Result, FString::Printf( TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteMaterialExpression.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteMaterialExpression.h index d8285c96..be0f38d0 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteMaterialExpression.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteMaterialExpression.h @@ -131,8 +131,8 @@ public: } // Capture info before deletion - FString DeletedNodeTitle = TargetMatNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); - FString DeletedExprClass = TargetMatNode->MaterialExpression->GetClass()->GetName(); + FString DeletedNodeTitle = MCPUtils::FormatName(TargetMatNode); + FString DeletedExprClass = MCPUtils::FormatName(TargetMatNode->MaterialExpression->GetClass()); if (DryRun) { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteNodeFromGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteNodeFromGraph.h index 8ba83248..bbb47fb6 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteNodeFromGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DeleteNodeFromGraph.h @@ -2,52 +2,14 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPServer.h" +#include "MCPFetcher.h" #include "MCPUtils.h" -#include "Engine/Blueprint.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialFunction.h" -#include "Engine/World.h" -#include "Engine/LevelScriptBlueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "EdGraphSchema_K2.h" -#include "K2Node.h" -#include "K2Node_CallFunction.h" #include "K2Node_Event.h" #include "K2Node_CustomEvent.h" #include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "K2Node_VariableGet.h" -#include "K2Node_VariableSet.h" -#include "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_DynamicCast.h" -#include "K2Node_CallParentFunction.h" -#include "K2Node_IfThenElse.h" -#include "K2Node_ExecutionSequence.h" -#include "K2Node_MacroInstance.h" -#include "K2Node_SpawnActorFromClass.h" -#include "K2Node_Select.h" -#include "K2Node_Knot.h" -#include "EdGraphNode_Comment.h" -#include "GameFramework/Actor.h" #include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Serialization/JsonSerializer.h" -#include "UObject/SavePackage.h" -#include "UObject/UObjectIterator.h" -#include "Misc/PackageName.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetRegistry/IAssetRegistry.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" -#include "BlueprintNodeSpawner.h" #include "UMCPHandler_DeleteNodeFromGraph.generated.h" @@ -61,10 +23,7 @@ class UMCPHandler_DeleteNodeFromGraph : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; - - UPROPERTY(meta=(Description="Node GUID")) + UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode")) FString Node; virtual FString GetDescription() const override @@ -76,24 +35,16 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); + MCPFetcher F(Result); + UEdGraphNode* FoundNode = F.Walk(Node).Cast(); + if (!FoundNode) return; - UEdGraph* Graph = nullptr; - UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); - if (!FoundNode) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node)); - } - if (!Graph) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph not found for node '%s'"), *Node)); - } + UEdGraph* Graph = FoundNode->GetGraph(); + UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode); - FString NodeClass = FoundNode->GetClass()->GetName(); - FString NodeTitle = FoundNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); - FString GraphName = Graph->GetName(); + FString NodeClass = MCPUtils::FormatName(FoundNode->GetClass()); + FString NodeTitle = MCPUtils::FormatName(FoundNode); + FString GraphName = MCPUtils::FormatName(Graph); // Protect root/entry nodes — deleting these leaves the graph in an invalid // state with no root node, causing compiler errors that can't be fixed @@ -121,8 +72,8 @@ public: *NodeTitle, *GraphName)); } - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting node '%s' (%s) from graph '%s' in '%s'"), - *Node, *NodeTitle, *GraphName, *Blueprint); + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting node '%s' (%s) from graph '%s'"), + *MCPUtils::FormatName(FoundNode), *NodeTitle, *GraphName); FoundNode->BreakAllNodeLinks(); Graph->RemoveNode(FoundNode); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DescribeMaterialInEnglish.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DescribeMaterialInEnglish.h index 48554c69..1a41e9ec 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DescribeMaterialInEnglish.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DescribeMaterialInEnglish.h @@ -150,7 +150,7 @@ public: } else { - NodeDesc = Expr->GetClass()->GetName(); + NodeDesc = MCPUtils::FormatName(Expr->GetClass()); } // If the source node has input pins with connections, recurse @@ -170,7 +170,7 @@ public: else { // Non-material node (e.g., root, comment), just use title - NodeDesc = SourceNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); + NodeDesc = MCPUtils::FormatName(SourceNode); } Sources.Add(NodeDesc); @@ -201,7 +201,7 @@ public: { if (!Pin || Pin->Direction != EGPD_Input) continue; - FString PinName = Pin->PinName.ToString(); + FString PinName = MCPUtils::FormatName(Pin); FString Description; if (Pin->LinkedTo.Num() == 0) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DiffTwoBlueprints.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DiffTwoBlueprints.h index 59031f66..b9e27b48 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DiffTwoBlueprints.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DiffTwoBlueprints.h @@ -117,13 +117,13 @@ public: for (UEdGraphNode* N : GA->Nodes) { if (!N) continue; - FString Title = N->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); + FString Title = MCPUtils::FormatName(N); NodesA.FindOrAdd(Title).Add(N); } for (UEdGraphNode* N : GB->Nodes) { if (!N) continue; - FString Title = N->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); + FString Title = MCPUtils::FormatName(N); NodesB.FindOrAdd(Title).Add(N); } @@ -141,7 +141,7 @@ public: { TSharedRef NObj = MakeShared(); NObj->SetStringField(TEXT("title"), Pair.Key); - NObj->SetStringField(TEXT("class"), Pair.Value[0]->GetClass()->GetName()); + NObj->SetStringField(TEXT("class"), MCPUtils::FormatName(Pair.Value[0]->GetClass())); NObj->SetNumberField(TEXT("extraCount"), CountA - CountB); OnlyInA.Add(MakeShared(NObj)); } @@ -161,7 +161,7 @@ public: { TSharedRef NObj = MakeShared(); NObj->SetStringField(TEXT("title"), Pair.Key); - NObj->SetStringField(TEXT("class"), Pair.Value[0]->GetClass()->GetName()); + NObj->SetStringField(TEXT("class"), MCPUtils::FormatName(Pair.Value[0]->GetClass())); NObj->SetNumberField(TEXT("extraCount"), CountB - CountA); OnlyInB.Add(MakeShared(NObj)); } @@ -170,9 +170,9 @@ public: // Connection diff: use connection key approach auto MakeConnKey = [](UEdGraphPin* SrcPin, UEdGraphPin* TgtPin) -> FString { - FString SrcTitle = SrcPin->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); - FString TgtTitle = TgtPin->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); - return FString::Printf(TEXT("%s|%s|%s|%s"), *SrcTitle, *SrcPin->PinName.ToString(), *TgtTitle, *TgtPin->PinName.ToString()); + FString SrcTitle = MCPUtils::FormatName(SrcPin->GetOwningNode()); + FString TgtTitle = MCPUtils::FormatName(TgtPin->GetOwningNode()); + return FString::Printf(TEXT("%s|%s|%s|%s"), *SrcTitle, *MCPUtils::FormatName(SrcPin), *TgtTitle, *MCPUtils::FormatName(TgtPin)); }; TSet ConnectionsA, ConnectionsB; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectBlueprintPins.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectPins.h similarity index 57% rename from Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectBlueprintPins.h rename to Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectPins.h index c71ca83e..a989fd9e 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectBlueprintPins.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DisconnectPins.h @@ -2,15 +2,13 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" +#include "MCPFetcher.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" -#include "Engine/World.h" -#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "Kismet2/BlueprintEditorUtils.h" -#include "UMCPHandler_DisconnectBlueprintPins.generated.h" +#include "UMCPHandler_DisconnectPins.generated.h" // --------------------------------------------------------------------------- @@ -23,21 +21,15 @@ struct FDisconnectPinEntry GENERATED_BODY() UPROPERTY() - FString Node; - - UPROPERTY() - FString PinName; + FString Pin; UPROPERTY(meta=(Optional)) - FString TargetNode; - - UPROPERTY(meta=(Optional)) - FString TargetPinName; + FString TargetPin; }; UCLASS() -class UMCPHandler_DisconnectBlueprintPins : public UObject, public IMCPHandler +class UMCPHandler_DisconnectPins : public UObject, public IMCPHandler { GENERATED_BODY() @@ -45,7 +37,7 @@ public: UPROPERTY(meta=(Description="Blueprint name or package path")) FString Blueprint; - UPROPERTY(meta=(Description="Array of {node, pinName, targetNode?, targetPinName?} objects. If target is omitted, all connections on the pin are broken.")) + UPROPERTY(meta=(Description="Array of {pin, targetPin?} objects. Each pin is a path like node:MyNode,pin:Output. If targetPin is omitted, all connections on the pin are broken.")) FMCPJsonArray Disconnections; virtual FString GetDescription() const override @@ -57,9 +49,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); + MCPFetcher F(Result); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; TArray> Results; int32 SuccessCount = 0; @@ -73,45 +65,28 @@ public: FDisconnectPinEntry Entry; if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, &*EntryResult)) continue; - UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); - if (!Node) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Node '%s' not found"), *Entry.Node)); - continue; - } - - UEdGraphPin* Pin = Node->FindPin(FName(*Entry.PinName)); - if (!Pin) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *Entry.PinName, *Entry.Node)); - continue; - } + MCPFetcher FP(EntryResult, BP); + UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast(); + if (!Pin) continue; int32 DisconnectedCount = 0; - if (!Entry.TargetNode.IsEmpty() && !Entry.TargetPinName.IsEmpty()) + if (!Entry.TargetPin.IsEmpty()) { - UEdGraphNode* TargetNode = MCPUtils::FindNodeByGuid(BP, Entry.TargetNode); - if (!TargetNode) + MCPFetcher FT(EntryResult, BP); + UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast(); + if (!Target) continue; + + if (!Pin->LinkedTo.Contains(Target)) { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target node '%s' not found"), *Entry.TargetNode)); + EntryResult->SetStringField(TEXT("error"), FString::Printf( + TEXT("%s.%s is not connected to %s.%s"), + *MCPUtils::FormatName(Pin->GetOwningNode()), *MCPUtils::FormatName(Pin), + *MCPUtils::FormatName(Target->GetOwningNode()), *MCPUtils::FormatName(Target))); continue; } - UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*Entry.TargetPinName)); - if (!TargetPin) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *Entry.TargetPinName, *Entry.TargetNode)); - continue; - } - - if (!Pin->LinkedTo.Contains(TargetPin)) - { - EntryResult->SetStringField(TEXT("error"), TEXT("The specified pins are not connected to each other")); - continue; - } - - Pin->BreakLinkTo(TargetPin); + Pin->BreakLinkTo(Target); DisconnectedCount = 1; } else diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprint.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprint.h index 3dcf6698..4225831d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprint.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprint.h @@ -2,24 +2,13 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" +#include "MCPFetcher.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" -#include "Engine/World.h" -#include "Engine/Level.h" -#include "Engine/LevelScriptBlueprint.h" -#include "EdGraph/EdGraph.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 "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "AssetRegistry/IAssetRegistry.h" +#include "Animation/AnimBlueprint.h" +#include "Animation/Skeleton.h" +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" #include "UMCPHandler_DumpBlueprint.generated.h" @@ -38,15 +27,77 @@ public: virtual FString GetDescription() const override { - return TEXT("Load and serialize a Blueprint, returning its full structure including graphs, variables, and components."); + return TEXT("Dump a Blueprint's structure: variables, interfaces, components, " + "and graph names. Does not include graph contents (use DumpGraphs for that)."); } virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; + MCPFetcher F(Result); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; - TSharedRef Tmp = MCPUtils::SerializeBlueprint(Assets.Object()); - MCPUtils::CopyJsonFields(&*Tmp, Result); + Result->SetStringField(TEXT("name"), MCPUtils::FormatName(BP)); + Result->SetStringField(TEXT("parentClass"), BP->ParentClass ? MCPUtils::FormatName(BP->ParentClass) : TEXT("None")); + Result->SetStringField(TEXT("blueprintType"), + StaticEnum()->GetNameStringByValue((int64)BP->BlueprintType)); + + // Animation Blueprint + if (UAnimBlueprint* AnimBP = Cast(BP)) + { + Result->SetBoolField(TEXT("isAnimBlueprint"), true); + if (AnimBP->TargetSkeleton) + { + Result->SetStringField(TEXT("targetSkeleton"), AnimBP->TargetSkeleton->GetName()); + Result->SetStringField(TEXT("targetSkeletonPath"), AnimBP->TargetSkeleton->GetPathName()); + } + } + + // Variables + TArray> Vars; + for (const FBPVariableDescription& V : BP->NewVariables) + { + TSharedRef VJ = MakeShared(); + VJ->SetStringField(TEXT("name"), MCPUtils::FormatName(V)); + VJ->SetStringField(TEXT("type"), V.VarType.PinCategory.ToString()); + if (V.VarType.PinSubCategoryObject.IsValid()) + VJ->SetStringField(TEXT("subtype"), V.VarType.PinSubCategoryObject->GetName()); + VJ->SetBoolField(TEXT("isArray"), V.VarType.IsArray()); + VJ->SetBoolField(TEXT("isSet"), V.VarType.IsSet()); + VJ->SetBoolField(TEXT("isMap"), V.VarType.IsMap()); + VJ->SetStringField(TEXT("category"), V.Category.ToString()); + VJ->SetStringField(TEXT("defaultValue"), V.DefaultValue); + Vars.Add(MakeShared(VJ)); + } + Result->SetArrayField(TEXT("variables"), Vars); + + // Interfaces + TArray> Ifaces; + for (const FBPInterfaceDescription& I : BP->ImplementedInterfaces) + { + if (I.Interface) + Ifaces.Add(MakeShared(MCPUtils::FormatName(I.Interface))); + } + Result->SetArrayField(TEXT("interfaces"), Ifaces); + + // Components + if (USimpleConstructionScript* SCS = BP->SimpleConstructionScript) + { + TArray> Comps; + for (USCS_Node* Node : SCS->GetAllNodes()) + { + if (!Node || !Node->ComponentTemplate) continue; + TSharedRef CJ = MakeShared(); + CJ->SetStringField(TEXT("name"), MCPUtils::FormatName(Node->ComponentTemplate)); + CJ->SetStringField(TEXT("class"), MCPUtils::FormatName(Node->ComponentClass)); + if (Node->ParentComponentOrVariableName != NAME_None) + CJ->SetStringField(TEXT("parent"), Node->ParentComponentOrVariableName.ToString()); + Comps.Add(MakeShared(CJ)); + } + Result->SetArrayField(TEXT("components"), Comps); + } + + // Graph names (without contents) + Result->SetArrayField(TEXT("graphs"), MCPUtils::AllGraphNamesJson(BP)); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprintGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprintGraph.h deleted file mode 100644 index 570fe382..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpBlueprintGraph.h +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPUtils.h" -#include "Engine/Blueprint.h" -#include "Engine/World.h" -#include "Engine/Level.h" -#include "Engine/LevelScriptBlueprint.h" -#include "EdGraph/EdGraph.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 "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "AssetRegistry/IAssetRegistry.h" -#include "UMCPHandler_DumpBlueprintGraph.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UMCPHandler_DumpBlueprintGraph : public UObject, public IMCPHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; - - UPROPERTY(meta=(Description="Graph name to dump")) - FString Graph; - - virtual FString GetDescription() const override - { - return TEXT("Dump the detailed node/pin structure of a specific graph within a Blueprint."); - } - - virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override - { - // URL-decode graph name to handle spaces encoded as '+' or '%20' - FString DecodedGraphName = MCPUtils::UrlDecode(Graph); - - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); - - TArray AllGraphs = MCPUtils::AllGraphs(BP); - - for (UEdGraph* GraphObj : AllGraphs) - { - if (GraphObj->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase)) - { - TSharedPtr GraphJson = MCPUtils::SerializeGraph(GraphObj); - if (GraphJson.IsValid()) - { - MCPUtils::CopyJsonFields(GraphJson.Get(), Result); - return; - } - } - } - - // Not found — list available graphs - TArray> GraphNames; - for (UEdGraph* GraphObj : AllGraphs) - { - GraphNames.Add(MakeShared(GraphObj->GetName())); - } - MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); - Result->SetArrayField(TEXT("availableGraphs"), GraphNames); - } -}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpGraphs.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpGraphs.h new file mode 100644 index 00000000..dd9ba0eb --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpGraphs.h @@ -0,0 +1,71 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPFetcher.h" +#include "MCPUtils.h" +#include "BlueprintExporter.h" +#include "Engine/Blueprint.h" +#include "EdGraph/EdGraph.h" +#include "UMCPHandler_DumpGraphs.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCPHandler_DumpGraphs : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Path to a blueprint or graph, e.g. /Game/Foo or /Game/Foo,graph:EventGraph")) + FString Path; + + virtual FString GetDescription() const override + { + return TEXT("Dump blueprint graphs as readable text. " + "If given a blueprint, dumps all graphs. If given a specific graph, dumps only that one."); + } + + virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override + { + MCPFetcher F(Result); + F.Walk(Path); + if (!F.Ok()) return; + + if (UEdGraph* Graph = Cast(F.Obj)) + { + EmitGraph(Graph, Result); + return; + } + + if (UBlueprint* BP = Cast(F.Obj)) + { + TArray Graphs = MCPUtils::AllGraphs(BP); + for (UEdGraph* Graph : Graphs) + { + Result.Appendf(TEXT("\n======== %s ========\n"), *MCPUtils::FormatName(Graph)); + EmitGraph(Graph, Result); + } + return; + } + + Result.Appendf(TEXT("ERROR: Expected a blueprint or graph, got %s\n"), + *F.Obj->GetClass()->GetName()); + } + +private: + void EmitGraph(UEdGraph* Graph, FStringBuilderBase& Result) + { + FlxBlueprintExporter Exporter(Graph); + Result.Append(Exporter.GetOutput()); + FString Details = Exporter.GetDetails(); + if (!Details.IsEmpty()) + { + Result.Append(TEXT("\n")); + Result.Append(Details); + } + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DuplicateNodesInGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DuplicateNodesInGraph.h index 13369de0..e0561145 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DuplicateNodesInGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DuplicateNodesInGraph.h @@ -174,8 +174,8 @@ public: Entry->SetStringField(TEXT("newNodeId"), NewNode->NodeGuid.ToString()); Entry->SetNumberField(TEXT("posX"), NewNode->NodePosX); Entry->SetNumberField(TEXT("posY"), NewNode->NodePosY); - Entry->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); - Entry->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); + Entry->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(NewNode->GetClass())); + Entry->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(NewNode)); DuplicatedNodes.Add(MakeShared(Entry)); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetNodeComment.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetNodeComment.h index 0aa3580d..dee0f6d5 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetNodeComment.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetNodeComment.h @@ -2,52 +2,9 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPServer.h" +#include "MCPFetcher.h" #include "MCPUtils.h" -#include "Engine/Blueprint.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialFunction.h" -#include "Engine/World.h" -#include "Engine/LevelScriptBlueprint.h" -#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "EdGraphSchema_K2.h" -#include "K2Node.h" -#include "K2Node_CallFunction.h" -#include "K2Node_Event.h" -#include "K2Node_CustomEvent.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "K2Node_VariableGet.h" -#include "K2Node_VariableSet.h" -#include "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_DynamicCast.h" -#include "K2Node_CallParentFunction.h" -#include "K2Node_IfThenElse.h" -#include "K2Node_ExecutionSequence.h" -#include "K2Node_MacroInstance.h" -#include "K2Node_SpawnActorFromClass.h" -#include "K2Node_Select.h" -#include "K2Node_Knot.h" -#include "EdGraphNode_Comment.h" -#include "GameFramework/Actor.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Serialization/JsonSerializer.h" -#include "UObject/SavePackage.h" -#include "UObject/UObjectIterator.h" -#include "Misc/PackageName.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetRegistry/IAssetRegistry.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" -#include "BlueprintNodeSpawner.h" #include "UMCPHandler_GetNodeComment.generated.h" @@ -61,10 +18,7 @@ class UMCPHandler_GetNodeComment : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; - - UPROPERTY(meta=(Description="Node GUID")) + UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode")) FString Node; virtual FString GetDescription() const override @@ -75,15 +29,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); - - UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); - if (!FoundNode) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node)); - } + MCPFetcher F(Result); + UEdGraphNode* FoundNode = F.Walk(Node).Cast(); + if (!FoundNode) return; Result->SetStringField(TEXT("comment"), FoundNode->NodeComment); Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetPinDetails.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetPinDetails.h index 8b994b41..55d2e378 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetPinDetails.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_GetPinDetails.h @@ -38,7 +38,7 @@ public: UEdGraphPin* P = F.Walk(Pin).Cast(); if (!P) return; - Result->SetStringField(TEXT("pinName"), P->PinName.ToString()); + Result->SetStringField(TEXT("pinName"), MCPUtils::FormatName(P)); Result->SetStringField(TEXT("direction"), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")); Result->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString()); @@ -79,8 +79,8 @@ public: if (!Linked || !Linked->GetOwningNode()) continue; TSharedRef CJ = MakeShared(); CJ->SetStringField(TEXT("nodeId"), Linked->GetOwningNode()->NodeGuid.ToString()); - CJ->SetStringField(TEXT("pinName"), Linked->PinName.ToString()); - CJ->SetStringField(TEXT("nodeTitle"), Linked->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); + CJ->SetStringField(TEXT("pinName"), MCPUtils::FormatName(Linked)); + CJ->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Linked->GetOwningNode())); Conns.Add(MakeShared(CJ)); } Result->SetArrayField(TEXT("connectedTo"), Conns); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintComponents.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintComponents.h index 20b6f71c..94f0b4e1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintComponents.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintComponents.h @@ -61,7 +61,7 @@ public: if (Node->ComponentClass) { - CompObj->SetStringField(TEXT("componentClass"), Node->ComponentClass->GetName()); + CompObj->SetStringField(TEXT("componentClass"), MCPUtils::FormatName(Node->ComponentClass)); } else { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintInterfaces.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintInterfaces.h index 714ba287..507c5932 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintInterfaces.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListBlueprintInterfaces.h @@ -47,7 +47,7 @@ public: } TSharedRef IfaceObj = MakeShared(); - IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName()); + IfaceObj->SetStringField(TEXT("name"), MCPUtils::FormatName(IfaceDesc.Interface)); IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName()); TArray> FuncArr; @@ -55,7 +55,7 @@ public: { if (Graph) { - FuncArr.Add(MakeShared(Graph->GetName())); + FuncArr.Add(MakeShared(MCPUtils::FormatName(Graph))); } } IfaceObj->SetArrayField(TEXT("functions"), FuncArr); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassFunctions.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassFunctions.h index b63b3666..a2987c09 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassFunctions.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassFunctions.h @@ -80,7 +80,7 @@ public: UClass* OwnerClass = Func->GetOwnerClass(); if (OwnerClass) { - FuncObj->SetStringField(TEXT("definedIn"), OwnerClass->GetName()); + FuncObj->SetStringField(TEXT("definedIn"), MCPUtils::FormatName(OwnerClass)); } // Function flags @@ -124,7 +124,7 @@ public: FuncList.Add(MakeShared(FuncObj)); } - Result->SetStringField(TEXT("className"), FoundClass->GetName()); + Result->SetStringField(TEXT("className"), MCPUtils::FormatName(FoundClass)); Result->SetNumberField(TEXT("count"), FuncList.Num()); Result->SetArrayField(TEXT("functions"), FuncList); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassProperties.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassProperties.h index f5437f13..ba5b3ff1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassProperties.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ListClassProperties.h @@ -78,7 +78,7 @@ public: UClass* OwnerClass = Prop->GetOwnerClass(); if (OwnerClass) { - PropObj->SetStringField(TEXT("definedIn"), OwnerClass->GetName()); + PropObj->SetStringField(TEXT("definedIn"), MCPUtils::FormatName(OwnerClass)); } // Property flags @@ -99,7 +99,7 @@ public: PropList.Add(MakeShared(PropObj)); } - Result->SetStringField(TEXT("className"), FoundClass->GetName()); + Result->SetStringField(TEXT("className"), MCPUtils::FormatName(FoundClass)); Result->SetNumberField(TEXT("count"), PropList.Num()); Result->SetArrayField(TEXT("properties"), PropList); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RefreshAllNodesInGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RefreshAllNodesInGraph.h index 99139534..61efedde 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RefreshAllNodesInGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RefreshAllNodesInGraph.h @@ -128,9 +128,9 @@ public: { if (Node->bHasCompilerMessage) { - FString NodeTitle = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); + FString NodeTitle = MCPUtils::FormatName(Node); FString NodeMsg = FString::Printf(TEXT("[%s] %s: %s"), - *Node->GetGraph()->GetName(), *NodeTitle, *Node->ErrorMsg); + *MCPUtils::FormatName(Node->GetGraph()), *NodeTitle, *Node->ErrorMsg); if (Node->ErrorType == EMessageSeverity::Error) { ErrorsArr.Add(MakeShared(NodeMsg)); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveBlueprintInterface.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveBlueprintInterface.h index 50bb91bf..5d89523c 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveBlueprintInterface.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveBlueprintInterface.h @@ -80,7 +80,7 @@ public: { if (IfaceDesc.Interface) { - IfaceList.Add(MakeShared(IfaceDesc.Interface->GetName())); + IfaceList.Add(MakeShared(MCPUtils::FormatName(IfaceDesc.Interface))); } } @@ -94,7 +94,7 @@ public: FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"), - *FoundInterface->GetName(), *Blueprint, PreserveFunctions ? TEXT("true") : TEXT("false")); + *MCPUtils::FormatName(FoundInterface), *Blueprint, PreserveFunctions ? TEXT("true") : TEXT("false")); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions); @@ -102,8 +102,8 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"), - *FoundInterface->GetName(), *Blueprint); + *MCPUtils::FormatName(FoundInterface), *Blueprint); - Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); + Result->SetStringField(TEXT("interfaceName"), MCPUtils::FormatName(FoundInterface)); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveFunctionParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveFunctionParameter.h index 386670dc..11580592 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveFunctionParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RemoveFunctionParameter.h @@ -51,7 +51,7 @@ public: // Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes(BP)) { - if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) + if (MCPUtils::Identifies(FunctionName, FuncEntry->GetGraph())) { EntryNode = FuncEntry; FoundNodeType = TEXT("FunctionEntry"); @@ -80,7 +80,7 @@ public: for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes(BP)) { Available.Add(MakeShared( - FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName()))); + FString::Printf(TEXT("function:%s"), *MCPUtils::FormatName(FE->GetGraph())))); } for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes(BP)) { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RenameBlueprintGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RenameBlueprintGraph.h index 14072db5..b06e9300 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RenameBlueprintGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_RenameBlueprintGraph.h @@ -48,7 +48,7 @@ public: // Check if it's an UbergraphPage — disallow rename for (UEdGraph* CandidateGraph : BP->UbergraphPages) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { return MCPUtils::MakeErrorJson(Result, FString::Printf( TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."), @@ -62,7 +62,7 @@ public: for (UEdGraph* CandidateGraph : BP->FunctionGraphs) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { TargetGraph = CandidateGraph; GraphType = TEXT("function"); @@ -73,7 +73,7 @@ public: { for (UEdGraph* CandidateGraph : BP->MacroGraphs) { - if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) + if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph)) { TargetGraph = CandidateGraph; GraphType = TEXT("macro"); @@ -108,7 +108,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"), *Graph, *NewName, bSaved ? TEXT("true") : TEXT("false")); - Result->SetStringField(TEXT("newName"), TargetGraph->GetName()); + Result->SetStringField(TEXT("newName"), MCPUtils::FormatName(TargetGraph)); Result->SetStringField(TEXT("graphType"), GraphType); Result->SetBoolField(TEXT("saved"), bSaved); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReparentBlueprint.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReparentBlueprint.h index aecea5e4..87143cba 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReparentBlueprint.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReparentBlueprint.h @@ -43,7 +43,7 @@ public: if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UBlueprint* BP = Assets.Object(); - FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None"); + FString OldParentName = BP->ParentClass ? MCPUtils::FormatName(BP->ParentClass) : TEXT("None"); // Find the new parent class // Try C++ class first (e.g. "WebUIHUD" finds /Script/ModuleName.WebUIHUD) @@ -85,11 +85,11 @@ public: // Just warn, don't block — the user may intentionally reparent to a sibling UE_LOG(LogTemp, Warning, TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s' — classes are not in a direct hierarchy"), - *Blueprint, *OldParentName, *NewParentClassObj->GetName()); + *Blueprint, *OldParentName, *MCPUtils::FormatName(NewParentClassObj)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s'"), - *Blueprint, *OldParentName, *NewParentClassObj->GetName()); + *Blueprint, *OldParentName, *MCPUtils::FormatName(NewParentClassObj)); // Perform reparent BP->PreEditChange(nullptr); @@ -105,7 +105,7 @@ public: // Save bool bSaved = MCPUtils::SaveBlueprintPackage(BP); - FString NewParentActualName = NewParentClassObj->GetName(); + FString NewParentActualName = MCPUtils::FormatName(NewParentClassObj); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h index 7c39d1e6..19612a06 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h @@ -202,9 +202,9 @@ public: AtRisk->SetStringField(TEXT("type"), TEXT("connectionAtRisk")); AtRisk->SetStringField(TEXT("functionName"), FuncName.ToString()); AtRisk->SetStringField(TEXT("nodeId"), CallNode->NodeGuid.ToString()); - AtRisk->SetStringField(TEXT("pinName"), Pin->PinName.ToString()); + AtRisk->SetStringField(TEXT("pinName"), MCPUtils::FormatName(Pin)); AtRisk->SetStringField(TEXT("connectedToNode"), Linked->GetOwningNode()->NodeGuid.ToString()); - AtRisk->SetStringField(TEXT("connectedToPin"), Linked->PinName.ToString()); + AtRisk->SetStringField(TEXT("connectedToPin"), MCPUtils::FormatName(Linked)); BrokenConnections.Add(MakeShared(AtRisk)); } } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchAssets.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchAssets.h new file mode 100644 index 00000000..1b9a62a9 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchAssets.h @@ -0,0 +1,76 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPAssetFinder.h" +#include "MCPUtils.h" +#include "UMCPHandler_SearchAssets.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCPHandler_SearchAssets : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Optional, Description="Substring to match against asset package paths")) + FString Search; + + UPROPERTY(meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh")) + FString Type; + + UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)")) + int32 Limit = 50; + + virtual FString GetDescription() const override + { + return TEXT("Search for assets by name and/or type. At least one of Search or Type must be specified."); + } + + virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override + { + if (Search.IsEmpty() && Type.IsEmpty()) + { + Result.Append(TEXT("ERROR: At least one of Search or Type must be specified\n")); + return; + } + + MCPAssets Assets; + + // If a type is specified, find the UClass and filter by it + if (!Type.IsEmpty()) + { + UClass* TypeClass = MCPUtils::FindClassByName(Type); + if (!TypeClass) + { + Result.Appendf(TEXT("ERROR: Unknown asset type '%s'\n"), *Type); + return; + } + Assets.NoScans().Scan(TypeClass); + } + + if (!Search.IsEmpty()) + { + Assets.Substring(Search); + } + + Assets.AllContent().Limit(Limit).Errors(Result).Info(); + + const TArray& AllData = Assets.AllData(); + for (const FAssetData& Data : AllData) + { + Result.Appendf(TEXT("%s %s\n"), + *Data.AssetClassPath.GetAssetName().ToString(), + *Data.PackageName.ToString()); + } + + if (AllData.Num() >= Limit) + { + Result.Appendf(TEXT("WARNING: You reached the limit of %d, to raise it, specify the Limit parameter.\n"), Limit); + } + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h index 75630f4b..3d30023d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h @@ -125,7 +125,7 @@ public: R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("usage"), TEXT("functionParameter")); R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"), - *Node->GetGraph()->GetName(), *PinInfo->PinName.ToString())); + *MCPUtils::FormatName(Node->GetGraph()), *PinInfo->PinName.ToString())); R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString()); if (!ParamSubtype.IsEmpty()) @@ -172,7 +172,7 @@ public: R->SetStringField(TEXT("blueprint"), BPName); R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("usage"), TEXT("breakStruct")); - R->SetStringField(TEXT("location"), Node->GetGraph()->GetName()); + R->SetStringField(TEXT("location"), MCPUtils::FormatName(Node->GetGraph())); R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName()); if (bIsLevel) @@ -188,7 +188,7 @@ public: R->SetStringField(TEXT("blueprint"), BPName); R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("usage"), TEXT("makeStruct")); - R->SetStringField(TEXT("location"), Node->GetGraph()->GetName()); + R->SetStringField(TEXT("location"), MCPUtils::FormatName(Node->GetGraph())); R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName()); if (bIsLevel) @@ -214,10 +214,10 @@ public: R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("usage"), TEXT("pinConnection")); R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"), - *Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), - *Pin->PinName.ToString())); + *MCPUtils::FormatName(Node), + *MCPUtils::FormatName(Pin))); R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); - R->SetStringField(TEXT("graph"), Node->GetGraph()->GetName()); + R->SetStringField(TEXT("graph"), MCPUtils::FormatName(Node->GetGraph())); R->SetStringField(TEXT("pinType"), Pin->PinType.PinCategory.ToString()); if (!PinSubtype.IsEmpty()) R->SetStringField(TEXT("pinSubtype"), PinSubtype); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchUnrealClasses.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchUnrealClasses.h index 5a8d5d90..4786d78f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchUnrealClasses.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchUnrealClasses.h @@ -92,7 +92,7 @@ public: if (ClassList.Num() >= Limit) continue; // Count but don't add beyond limit TSharedRef ClassObj = MakeShared(); - ClassObj->SetStringField(TEXT("name"), ClassName); + ClassObj->SetStringField(TEXT("name"), MCPUtils::FormatName(Class)); ClassObj->SetStringField(TEXT("fullPath"), Class->GetPathName()); // Determine if it's a Blueprint-generated class @@ -102,7 +102,7 @@ public: // Parent class if (Class->GetSuperClass()) { - ClassObj->SetStringField(TEXT("parentClass"), Class->GetSuperClass()->GetName()); + ClassObj->SetStringField(TEXT("parentClass"), MCPUtils::FormatName(Class->GetSuperClass())); } // Module/package info diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchWithinBlueprints.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchWithinBlueprints.h index b5cfb2f0..8d878325 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchWithinBlueprints.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SearchWithinBlueprints.h @@ -92,9 +92,9 @@ public: TSharedRef R = MakeShared(); R->SetStringField(TEXT("blueprint"), AssetName); R->SetStringField(TEXT("blueprintPath"), AssetPath); - R->SetStringField(TEXT("graph"), Node->GetGraph()->GetName()); - R->SetStringField(TEXT("nodeTitle"), Title); - R->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName()); + R->SetStringField(TEXT("graph"), MCPUtils::FormatName(Node->GetGraph())); + R->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Node)); + R->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(Node->GetClass())); if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName); if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName); if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetClassDefaultValue.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetClassDefaultValue.h index 69a4225d..67443f89 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetClassDefaultValue.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetClassDefaultValue.h @@ -151,7 +151,7 @@ public: { return MCPUtils::MakeErrorJson(Result, FString::Printf( TEXT("'%s' is not a subclass of '%s' (required by property '%s')"), - *ResolvedClass->GetName(), *MetaClass->GetName(), *Property)); + *MCPUtils::FormatName(ResolvedClass), *MCPUtils::FormatName(MetaClass), *Property)); } ClassProp->SetPropertyValue_InContainer(CDO, ResolvedClass); } @@ -160,7 +160,7 @@ public: FSoftObjectPtr SoftPtr(ResolvedClass); SoftClassProp->SetPropertyValue_InContainer(CDO, SoftPtr); } - ActualNewValue = ResolvedClass->GetName(); + ActualNewValue = MCPUtils::FormatName(ResolvedClass); bSuccess = true; } // Handle object properties (TObjectPtr, UObject*) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodeComment.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodeComment.h index d161ffa6..b881ca9b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodeComment.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodeComment.h @@ -2,52 +2,10 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPServer.h" +#include "MCPFetcher.h" #include "MCPUtils.h" -#include "Engine/Blueprint.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialFunction.h" -#include "Engine/World.h" -#include "Engine/LevelScriptBlueprint.h" -#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "EdGraphSchema_K2.h" -#include "K2Node.h" -#include "K2Node_CallFunction.h" -#include "K2Node_Event.h" -#include "K2Node_CustomEvent.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "K2Node_VariableGet.h" -#include "K2Node_VariableSet.h" -#include "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_DynamicCast.h" -#include "K2Node_CallParentFunction.h" -#include "K2Node_IfThenElse.h" -#include "K2Node_ExecutionSequence.h" -#include "K2Node_MacroInstance.h" -#include "K2Node_SpawnActorFromClass.h" -#include "K2Node_Select.h" -#include "K2Node_Knot.h" -#include "EdGraphNode_Comment.h" -#include "GameFramework/Actor.h" #include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Serialization/JsonSerializer.h" -#include "UObject/SavePackage.h" -#include "UObject/UObjectIterator.h" -#include "Misc/PackageName.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetRegistry/IAssetRegistry.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" -#include "BlueprintNodeSpawner.h" #include "UMCPHandler_SetNodeComment.generated.h" @@ -61,10 +19,7 @@ class UMCPHandler_SetNodeComment : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; - - UPROPERTY(meta=(Description="Node GUID")) + UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode")) FString Node; UPROPERTY(meta=(Description="Comment text to set")) @@ -78,15 +33,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); - - UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node); - if (!FoundNode) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node)); - } + MCPFetcher F(Result); + UEdGraphNode* FoundNode = F.Walk(Node).Cast(); + if (!FoundNode) return; FString OldComment = FoundNode->NodeComment; FoundNode->NodeComment = Comment; @@ -98,10 +47,11 @@ public: FoundNode->bCommentBubblePinned = true; } + UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode); FBlueprintEditorUtils::MarkBlueprintAsModified(BP); - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"), - *Node, *Blueprint); + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s'"), + *MCPUtils::FormatName(FoundNode)); Result->SetStringField(TEXT("oldComment"), OldComment); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodePositions.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodePositions.h index c5dd39cd..bbd2b493 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodePositions.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetNodePositions.h @@ -2,52 +2,11 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPServer.h" +#include "MCPFetcher.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialFunction.h" -#include "Engine/World.h" -#include "Engine/LevelScriptBlueprint.h" -#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "EdGraphSchema_K2.h" -#include "K2Node.h" -#include "K2Node_CallFunction.h" -#include "K2Node_Event.h" -#include "K2Node_CustomEvent.h" -#include "K2Node_FunctionEntry.h" -#include "K2Node_EditablePinBase.h" -#include "K2Node_VariableGet.h" -#include "K2Node_VariableSet.h" -#include "K2Node_BreakStruct.h" -#include "K2Node_MakeStruct.h" -#include "K2Node_DynamicCast.h" -#include "K2Node_CallParentFunction.h" -#include "K2Node_IfThenElse.h" -#include "K2Node_ExecutionSequence.h" -#include "K2Node_MacroInstance.h" -#include "K2Node_SpawnActorFromClass.h" -#include "K2Node_Select.h" -#include "K2Node_Knot.h" -#include "EdGraphNode_Comment.h" -#include "GameFramework/Actor.h" #include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Serialization/JsonSerializer.h" -#include "UObject/SavePackage.h" -#include "UObject/UObjectIterator.h" -#include "Misc/PackageName.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "AssetRegistry/IAssetRegistry.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" -#include "BlueprintNodeSpawner.h" #include "UMCPHandler_SetNodePositions.generated.h" @@ -91,9 +50,9 @@ public: virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { - MCPAssets Assets; - if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; - UBlueprint* BP = Assets.Object(); + MCPFetcher F(Result); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; TArray> Results; int32 SuccessCount = 0; @@ -106,12 +65,9 @@ public: FMoveNodeEntry Entry; if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal, &*EntryResult)) continue; - UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); - if (!Node) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Node '%s' not found"), *Entry.Node)); - continue; - } + MCPFetcher FN(EntryResult, BP); + UEdGraphNode* Node = FN.Node(Entry.Node).Cast(); + if (!Node) continue; int32 OldX = Node->NodePosX; int32 OldY = Node->NodePosY; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetPinDefaultValues.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetPinDefaultValues.h index ed7917c4..54686039 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetPinDefaultValues.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SetPinDefaultValues.h @@ -2,10 +2,9 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" +#include "MCPFetcher.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" -#include "Engine/World.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" @@ -66,25 +65,12 @@ public: FSetPinDefaultEntry Entry; if (!MCPUtils::PopulateFromJson(FSetPinDefaultEntry::StaticStruct(), &Entry, PinVal, &*EntryResult)) continue; - MCPAssets Assets; - if (!Assets.Scan().Scan().Exact(Entry.Blueprint).Errors(&*EntryResult).ENone().ETwo().Load()) - continue; - UBlueprint* BP = Assets.Object(); + MCPFetcher FE(EntryResult); + FE.Walk(Entry.Blueprint).Node(Entry.Node).Pin(Entry.PinName); + UEdGraphPin* Pin = FE.Cast(); + if (!Pin) continue; - UEdGraph* Graph = nullptr; - UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node, &Graph); - if (!Node) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Node '%s' not found"), *Entry.Node)); - continue; - } - - UEdGraphPin* Pin = Node->FindPin(FName(*Entry.PinName)); - if (!Pin) - { - EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *Entry.PinName, *Entry.Node)); - continue; - } + UEdGraphNode* Node = Pin->GetOwningNode(); if (Pin->Direction != EGPD_Input) { @@ -92,7 +78,7 @@ public: continue; } - const UEdGraphSchema* Schema = Graph->GetSchema(); + const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema(); if (Schema) { FString ValidationError = Schema->IsPinDefaultValid(Pin, Entry.Value, nullptr, FText::GetEmpty()); @@ -110,7 +96,7 @@ public: EntryResult->SetStringField(TEXT("oldValue"), OldValue); SuccessCount++; ModifiedNodes.Add(Node); - ModifiedBlueprints.Add(BP); + ModifiedBlueprints.Add(FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node)); } for (UEdGraphNode* Node : ModifiedNodes) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SpawnNodesInGraph.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SpawnNodesInGraph.h index b961d93e..7efcfcd9 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SpawnNodesInGraph.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_SpawnNodesInGraph.h @@ -109,7 +109,7 @@ public: TArray> GraphNames; for (UEdGraph* G : MCPUtils::AllGraphs(BP)) { - GraphNames.Add(MakeShared(G->GetName())); + GraphNames.Add(MakeShared(MCPUtils::FormatName(G))); } MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); Result->SetArrayField(TEXT("availableGraphs"), GraphNames); @@ -165,7 +165,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Spawned node '%s' (class %s) via action '%s' in graph '%s' of '%s'"), *NewNode->NodeGuid.ToString(), - *NewNode->GetClass()->GetName(), + *MCPUtils::FormatName(NewNode->GetClass()), *Entry.ActionName, *DecodedGraphName, *Blueprint); @@ -174,8 +174,8 @@ public: TSharedPtr NodeState = MCPUtils::SerializeNode(NewNode); EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString()); - EntryResult->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); - EntryResult->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView).ToString()); + EntryResult->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(NewNode->GetClass())); + EntryResult->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(NewNode)); if (NodeState.IsValid()) { EntryResult->SetObjectField(TEXT("node"), NodeState); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_TestSaveBlueprintPackage.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_TestSaveBlueprintPackage.h index 26ffe6ba..fab3fb3b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_TestSaveBlueprintPackage.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_TestSaveBlueprintPackage.h @@ -55,7 +55,7 @@ public: UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"), *BP->GetName(), - BP->GeneratedClass ? *BP->GeneratedClass->GetName() : TEXT("null")); + BP->GeneratedClass ? *MCPUtils::FormatName(BP->GeneratedClass) : TEXT("null")); // Attempt save with NO modifications bool bSaved = MCPUtils::SaveBlueprintPackage(BP); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp index af217d59..58606fee 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp @@ -14,7 +14,7 @@ #include "Handlers/UMCPHandler_CheckPinConnectionCompatibility.h" #include "Handlers/UMCPHandler_CompileBlueprint.h" #include "Handlers/UMCPHandler_CompileMaterial.h" -#include "Handlers/UMCPHandler_ConnectBlueprintPins.h" +#include "Handlers/UMCPHandler_ConnectPins.h" #include "Handlers/UMCPHandler_ConnectMaterialExpressionPins.h" #include "Handlers/UMCPHandler_CreateAnimBlueprintAsset.h" #include "Handlers/UMCPHandler_CreateBlendSpaceAsset.h" @@ -31,10 +31,10 @@ #include "Handlers/UMCPHandler_DeleteNodeFromGraph.h" #include "Handlers/UMCPHandler_DescribeMaterialInEnglish.h" #include "Handlers/UMCPHandler_DiffTwoBlueprints.h" -#include "Handlers/UMCPHandler_DisconnectBlueprintPins.h" +#include "Handlers/UMCPHandler_DisconnectPins.h" #include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h" #include "Handlers/UMCPHandler_DumpBlueprint.h" -#include "Handlers/UMCPHandler_DumpBlueprintGraph.h" +#include "Handlers/UMCPHandler_DumpGraphs.h" #include "Handlers/UMCPHandler_DumpMaterial.h" #include "Handlers/UMCPHandler_DumpMaterialExpressionGraph.h" #include "Handlers/UMCPHandler_DumpMaterialFunction.h" @@ -67,6 +67,7 @@ #include "Handlers/UMCPHandler_ReparentMaterialInstance.h" #include "Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h" #include "Handlers/UMCPHandler_RestoreAsset.h" +#include "Handlers/UMCPHandler_SearchAssets.h" #include "Handlers/UMCPHandler_SearchSpawnableNodeTypes.h" #include "Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h" #include "Handlers/UMCPHandler_SearchUnrealClasses.h" diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp index 3411388e..266bbcae 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp @@ -127,29 +127,29 @@ void MCPUtils::AppendNumericSuffix(FString &Name, int32 N) Name += FString::Printf(TEXT("_%d"), N - 1); } -FString MCPUtils::FormatName(UWorld *World) +FString MCPUtils::FormatName(const UWorld *World) { return World->GetPathName(); } -FString MCPUtils::FormatName(UBlueprint *BP) +FString MCPUtils::FormatName(const UBlueprint *BP) { return BP->GetPathName(); } -FString MCPUtils::FormatName(UActorComponent *C) +FString MCPUtils::FormatName(const UActorComponent *C) { return C->GetName(); } -FString MCPUtils::FormatName(UEdGraph *Graph) +FString MCPUtils::FormatName(const UEdGraph *Graph) { FString Name = Graph->GetName(); SanitizeNameInPlace(Name); return Name; } -FString MCPUtils::FormatName(UEdGraphNode* Node) +FString MCPUtils::FormatName(const UEdGraphNode* Node) { // Sanitized first line of the node title. FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); @@ -161,7 +161,7 @@ FString MCPUtils::FormatName(UEdGraphNode* Node) return Title; } -FString MCPUtils::FormatName(UEdGraphPin *Pin) +FString MCPUtils::FormatName(const UEdGraphPin *Pin) { FString Name = Pin->PinName.ToString(); SanitizeNameInPlace(Name); @@ -198,34 +198,34 @@ bool MCPUtils::Identifies(const FString &Name, const UClass *Class) // Identifies // ============================================================ -bool MCPUtils::Identifies(const FString &Name, UWorld *World) +bool MCPUtils::Identifies(const FString &Name, const UWorld *World) { return FormatName(World).Equals(Name, ESearchCase::IgnoreCase); } -bool MCPUtils::Identifies(const FString &Name, UBlueprint *BP) +bool MCPUtils::Identifies(const FString &Name, const UBlueprint *BP) { return FormatName(BP).Equals(Name, ESearchCase::IgnoreCase); } -bool MCPUtils::Identifies(const FString &Name, UActorComponent *C) +bool MCPUtils::Identifies(const FString &Name, const UActorComponent *C) { return FormatName(C).Equals(Name, ESearchCase::IgnoreCase); } -bool MCPUtils::Identifies(const FString &Name, UEdGraph *Graph) +bool MCPUtils::Identifies(const FString &Name, const UEdGraph *Graph) { return FormatName(Graph).Equals(Name, ESearchCase::IgnoreCase); } -bool MCPUtils::Identifies(const FString &Name, UEdGraphNode* Node) +bool MCPUtils::Identifies(const FString &Name, const UEdGraphNode* Node) { if (Node->NodeGuid.ToString().Equals(Name, ESearchCase::IgnoreCase)) return true; return FormatName(Node).Equals(Name, ESearchCase::IgnoreCase); } -bool MCPUtils::Identifies(const FString &Name, UEdGraphPin *Pin) +bool MCPUtils::Identifies(const FString &Name, const UEdGraphPin *Pin) { return FormatName(Pin).Equals(Name, ESearchCase::IgnoreCase); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index bcef53e7..02e77278 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -81,6 +81,7 @@ struct MCPErrorCallback MCPErrorCallback(std::nullptr_t); MCPErrorCallback(FString& OutError); MCPErrorCallback(FJsonObject* Result); + MCPErrorCallback(const TSharedRef& Result) : MCPErrorCallback(&*Result) {} MCPErrorCallback(FStringBuilderBase& OutResult); void SetError(const FString& Msg) const { Func(Msg); } @@ -104,12 +105,12 @@ public: // //////////////////////////////////////////////////////// - static FString FormatName(UWorld *World); - static FString FormatName(UBlueprint *BP); - static FString FormatName(UActorComponent *C); - static FString FormatName(UEdGraph *Graph); - static FString FormatName(UEdGraphNode* Node); - static FString FormatName(UEdGraphPin *Pin); + 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); static FString FormatName(const FMemberReference &Ref); static FString FormatName(const FBPVariableDescription &Var); static FString FormatName(const UClass *Class); @@ -126,12 +127,12 @@ public: // //////////////////////////////////////////////////////// - static bool Identifies(const FString &Name, UWorld *World); - static bool Identifies(const FString &Name, UBlueprint *BP); - static bool Identifies(const FString &Name, UActorComponent *C); - static bool Identifies(const FString &Name, UEdGraph *Graph); - static bool Identifies(const FString &Name, UEdGraphNode* Node); - static bool Identifies(const FString &Name, UEdGraphPin *Pin); + 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); static bool Identifies(const FString &Name, const FMemberReference &Ref); static bool Identifies(const FString &Name, const UClass *Class);