Refactoring more MCP handlers.

This commit is contained in:
2026-03-10 01:42:43 -04:00
parent 2a5833fe04
commit 519933d532
47 changed files with 516 additions and 689 deletions

Binary file not shown.

View File

@@ -59,7 +59,8 @@ public:
const TArray<USCS_Node*>& ExistingNodes = SCS->GetAllNodes(); const TArray<USCS_Node*>& ExistingNodes = SCS->GetAllNodes();
for (USCS_Node* Existing : ExistingNodes) 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( return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("A component named '%s' already exists in Blueprint '%s'"), TEXT("A component named '%s' already exists in Blueprint '%s'"),
@@ -68,42 +69,13 @@ public:
} }
// Resolve the component class by name // Resolve the component class by name
// Try multiple name variants: exact name, with U prefix, without U prefix
UClass* ComponentClassObj = nullptr; UClass* ComponentClassObj = nullptr;
TArray<FString> 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<UClass> It; It; ++It) for (TObjectIterator<UClass> It; It; ++It)
{ {
if (!It->IsChildOf(UActorComponent::StaticClass())) if (!It->IsChildOf(UActorComponent::StaticClass())) continue;
{ if (!MCPUtils::Identifies(ComponentClass, *It)) continue;
continue; ComponentClassObj = *It;
} break;
FString ClassName = It->GetName();
for (const FString& NameToTry : NamesToTry)
{
if (ClassName.Equals(NameToTry, ESearchCase::IgnoreCase))
{
ComponentClassObj = *It;
break;
}
}
if (ComponentClassObj)
{
break;
}
} }
if (!ComponentClassObj) if (!ComponentClassObj)
@@ -123,7 +95,8 @@ public:
{ {
for (USCS_Node* Node : ExistingNodes) 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; ParentSCSNode = Node;
break; break;
@@ -139,7 +112,7 @@ public:
} }
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding component '%s' (%s) to Blueprint '%s'"), 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 // Create the SCS node
USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component)); USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component));
@@ -147,7 +120,7 @@ public:
{ {
return MCPUtils::MakeErrorJson(Result, FString::Printf( return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Failed to create SCS node for component '%s' with class '%s'"), TEXT("Failed to create SCS node for component '%s' with class '%s'"),
*Component, *ComponentClassObj->GetName())); *Component, *MCPUtils::FormatName(ComponentClassObj)));
} }
// Add to the hierarchy // Add to the hierarchy
@@ -164,15 +137,15 @@ public:
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added component '%s' (%s) to '%s' (parent: %s, saved: %s)"), 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)"), ParentSCSNode ? *ParentComponent : TEXT("(root)"),
bSaved ? TEXT("true") : TEXT("false")); bSaved ? TEXT("true") : TEXT("false"));
Result->SetStringField(TEXT("component"), NewNode->GetVariableName().ToString()); Result->SetStringField(TEXT("component"), MCPUtils::FormatName(NewNode->ComponentTemplate));
Result->SetStringField(TEXT("componentClass"), ComponentClassObj->GetName()); Result->SetStringField(TEXT("componentClass"), MCPUtils::FormatName(ComponentClassObj));
if (ParentSCSNode) if (ParentSCSNode)
{ {
Result->SetStringField(TEXT("parentComponent"), ParentSCSNode->GetVariableName().ToString()); Result->SetStringField(TEXT("parentComponent"), MCPUtils::FormatName(ParentSCSNode->ComponentTemplate));
} }
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -106,7 +106,7 @@ public:
FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName(); FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"), 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); bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded) if (!bAdded)
@@ -126,7 +126,7 @@ public:
{ {
if (Graph) if (Graph)
{ {
AddedFunctions.Add(Graph->GetName()); AddedFunctions.Add(MCPUtils::FormatName(Graph));
} }
} }
break; break;
@@ -137,9 +137,9 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"), 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()); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
TArray<TSharedPtr<FJsonValue>> FuncArr; TArray<TSharedPtr<FJsonValue>> FuncArr;

View File

@@ -62,7 +62,7 @@ public:
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
UEdGraph* FEGraph = FE->GetGraph(); 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) // Skip delegate signature graphs (handled in Strategy 3)
if (BP->DelegateSignatureGraphs.Contains(FEGraph)) continue; if (BP->DelegateSignatureGraphs.Contains(FEGraph)) continue;
@@ -91,7 +91,7 @@ public:
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
UEdGraph* FEGraph = FE->GetGraph(); UEdGraph* FEGraph = FE->GetGraph();
if (!FEGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) continue; if (!MCPUtils::Identifies(FunctionName, FEGraph)) continue;
if (!BP->DelegateSignatureGraphs.Contains(FEGraph)) continue; if (!BP->DelegateSignatureGraphs.Contains(FEGraph)) continue;
EntryNode = FE; EntryNode = FE;
@@ -107,7 +107,7 @@ public:
for (UEdGraph* Graph : BP->FunctionGraphs) for (UEdGraph* Graph : BP->FunctionGraphs)
{ {
if (Graph) AvailFuncs.Add(MakeShared<FJsonValueString>(Graph->GetName())); if (Graph) AvailFuncs.Add(MakeShared<FJsonValueString>(MCPUtils::FormatName(Graph)));
} }
// Custom events // Custom events

View File

@@ -114,7 +114,7 @@ public:
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString()); AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString());
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet")); AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet"));
AffNode->SetStringField(TEXT("graph"), VG->GetGraph()->GetName()); AffNode->SetStringField(TEXT("graph"), MCPUtils::FormatName(VG->GetGraph()));
TArray<TSharedPtr<FJsonValue>> AffPins; TArray<TSharedPtr<FJsonValue>> AffPins;
for (UEdGraphPin* Pin : VG->Pins) for (UEdGraphPin* Pin : VG->Pins)
{ {
@@ -122,7 +122,7 @@ public:
{ {
AffPins.Add(MakeShared<FJsonValueString>( AffPins.Add(MakeShared<FJsonValueString>(
FString::Printf(TEXT("%s (connected to %d pin(s))"), 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); AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
@@ -134,7 +134,7 @@ public:
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString()); AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString());
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet")); AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet"));
AffNode->SetStringField(TEXT("graph"), VS->GetGraph()->GetName()); AffNode->SetStringField(TEXT("graph"), MCPUtils::FormatName(VS->GetGraph()));
TArray<TSharedPtr<FJsonValue>> AffPins; TArray<TSharedPtr<FJsonValue>> AffPins;
for (UEdGraphPin* Pin : VS->Pins) for (UEdGraphPin* Pin : VS->Pins)
{ {
@@ -142,7 +142,7 @@ public:
{ {
AffPins.Add(MakeShared<FJsonValueString>( AffPins.Add(MakeShared<FJsonValueString>(
FString::Printf(TEXT("%s (connected to %d pin(s))"), 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); AffNode->SetArrayField(TEXT("affectedPins"), AffPins);

View File

@@ -63,7 +63,7 @@ public:
// Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name // Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name
for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) if (MCPUtils::Identifies(FunctionName, FuncEntry->GetGraph()))
{ {
EntryNode = FuncEntry; EntryNode = FuncEntry;
FoundNodeType = TEXT("FunctionEntry"); FoundNodeType = TEXT("FunctionEntry");
@@ -92,7 +92,7 @@ public:
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
Available.Add(MakeShared<FJsonValueString>( Available.Add(MakeShared<FJsonValueString>(
FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName()))); FString::Printf(TEXT("function:%s"), *MCPUtils::FormatName(FE->GetGraph()))));
} }
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP)) for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
{ {
@@ -147,16 +147,16 @@ public:
TArray<TSharedPtr<FJsonValue>> AffectedPins; TArray<TSharedPtr<FJsonValue>> AffectedPins;
for (UEdGraphPin* Pin : EntryNode->Pins) 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) for (UEdGraphPin* Linked : Pin->LinkedTo)
{ {
if (Linked && Linked->GetOwningNode()) if (Linked && Linked->GetOwningNode())
{ {
TSharedRef<FJsonObject> AffPin = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> AffPin = MakeShared<FJsonObject>();
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("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()); AffPin->SetStringField(TEXT("currentType"), Pin->PinType.PinCategory.ToString());
if (Pin->PinType.PinSubCategoryObject.IsValid()) if (Pin->PinType.PinSubCategoryObject.IsValid())
AffPin->SetStringField(TEXT("currentSubtype"), Pin->PinType.PinSubCategoryObject->GetName()); AffPin->SetStringField(TEXT("currentSubtype"), Pin->PinType.PinSubCategoryObject->GetName());

View File

@@ -99,7 +99,7 @@ public:
if (!BreakNode && !MakeNode) if (!BreakNode && !MakeNode)
{ {
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' is not a BreakStruct or MakeStruct node (class: %s)"), 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 // Find the new struct type
@@ -267,7 +267,7 @@ public:
TSharedPtr<FJsonObject> UpdatedNodeState = MCPUtils::SerializeNode(FoundNode); TSharedPtr<FJsonObject> UpdatedNodeState = MCPUtils::SerializeNode(FoundNode);
Result->SetStringField(TEXT("newStructType"), NewStruct->GetName()); 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("reconnected"), Reconnected);
Result->SetNumberField(TEXT("failed"), Failed); Result->SetNumberField(TEXT("failed"), Failed);
Result->SetArrayField(TEXT("reconnectDetails"), ReconnectDetails); Result->SetArrayField(TEXT("reconnectDetails"), ReconnectDetails);

View File

@@ -67,10 +67,10 @@ public:
if (Node->bHasCompilerMessage) if (Node->bHasCompilerMessage)
{ {
TSharedRef<FJsonObject> Msg = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> Msg = MakeShared<FJsonObject>();
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("nodeId"), Node->NodeGuid.ToString());
Msg->SetStringField(TEXT("nodeTitle"), Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); Msg->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Node));
Msg->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName()); Msg->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(Node->GetClass()));
Msg->SetStringField(TEXT("message"), Node->ErrorMsg); Msg->SetStringField(TEXT("message"), Node->ErrorMsg);
if (Node->ErrorType == EMessageSeverity::Error) if (Node->ErrorType == EMessageSeverity::Error)

View File

@@ -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<UBlueprint> Assets;
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = Assets.Object();
TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0;
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
{
TSharedRef<FJsonObject> EntryResult = MakeShared<FJsonObject>();
Results.Add(MakeShared<FJsonValueObject>(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);
}
};

View File

@@ -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<UBlueprint>();
if (!BP) return;
TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0;
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
{
TSharedRef<FJsonObject> EntryResult = MakeShared<FJsonObject>();
Results.Add(MakeShared<FJsonValueObject>(EntryResult));
FConnectPinsEntry Entry;
if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, &*EntryResult)) continue;
MCPFetcher FS(EntryResult, BP);
UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>();
if (!SourcePin) continue;
MCPFetcher FT(EntryResult, BP);
UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
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);
}
};

View File

@@ -127,7 +127,7 @@ public:
Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName()); 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->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames); Result->SetArrayField(TEXT("graphs"), GraphNames);
} }

View File

@@ -100,7 +100,7 @@ public:
} }
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blueprint '%s' in '%s' with parent '%s' (type=%s)"), 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 // Create the package
FString FullPackagePath = PackagePath / Blueprint; FString FullPackagePath = PackagePath / Blueprint;
@@ -139,7 +139,7 @@ public:
*Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); *Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
Result->SetStringField(TEXT("assetPath"), FullAssetPath); Result->SetStringField(TEXT("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); Result->SetStringField(TEXT("parentClass"), MCPUtils::FormatName(ParentClassObj));
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames); Result->SetArrayField(TEXT("graphs"), GraphNames);
} }

View File

@@ -48,7 +48,7 @@ public:
for (UEdGraph* CandidateGraph : BP->FunctionGraphs) for (UEdGraph* CandidateGraph : BP->FunctionGraphs)
{ {
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph))
{ {
TargetGraph = CandidateGraph; TargetGraph = CandidateGraph;
GraphType = TEXT("function"); GraphType = TEXT("function");
@@ -59,7 +59,7 @@ public:
{ {
for (UEdGraph* CandidateGraph : BP->MacroGraphs) for (UEdGraph* CandidateGraph : BP->MacroGraphs)
{ {
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph))
{ {
TargetGraph = CandidateGraph; TargetGraph = CandidateGraph;
GraphType = TEXT("macro"); GraphType = TEXT("macro");
@@ -73,7 +73,7 @@ public:
{ {
for (UEdGraph* CandidateGraph : BP->UbergraphPages) 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( return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."), TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."),

View File

@@ -131,8 +131,8 @@ public:
} }
// Capture info before deletion // Capture info before deletion
FString DeletedNodeTitle = TargetMatNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString DeletedNodeTitle = MCPUtils::FormatName(TargetMatNode);
FString DeletedExprClass = TargetMatNode->MaterialExpression->GetClass()->GetName(); FString DeletedExprClass = MCPUtils::FormatName(TargetMatNode->MaterialExpression->GetClass());
if (DryRun) if (DryRun)
{ {

View File

@@ -2,52 +2,14 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.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/EdGraph.h"
#include "EdGraph/EdGraphNode.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_Event.h"
#include "K2Node_CustomEvent.h" #include "K2Node_CustomEvent.h"
#include "K2Node_FunctionEntry.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/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" #include "UMCPHandler_DeleteNodeFromGraph.generated.h"
@@ -61,10 +23,7 @@ class UMCPHandler_DeleteNodeFromGraph : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode"))
FString Blueprint;
UPROPERTY(meta=(Description="Node GUID"))
FString Node; FString Node;
virtual FString GetDescription() const override virtual FString GetDescription() const override
@@ -76,24 +35,16 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
UBlueprint* BP = Assets.Object(); if (!FoundNode) return;
UEdGraph* Graph = nullptr; UEdGraph* Graph = FoundNode->GetGraph();
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph); UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode);
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));
}
FString NodeClass = FoundNode->GetClass()->GetName(); FString NodeClass = MCPUtils::FormatName(FoundNode->GetClass());
FString NodeTitle = FoundNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString NodeTitle = MCPUtils::FormatName(FoundNode);
FString GraphName = Graph->GetName(); FString GraphName = MCPUtils::FormatName(Graph);
// Protect root/entry nodes — deleting these leaves the graph in an invalid // 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 // state with no root node, causing compiler errors that can't be fixed
@@ -121,8 +72,8 @@ public:
*NodeTitle, *GraphName)); *NodeTitle, *GraphName));
} }
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting node '%s' (%s) from graph '%s' in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting node '%s' (%s) from graph '%s'"),
*Node, *NodeTitle, *GraphName, *Blueprint); *MCPUtils::FormatName(FoundNode), *NodeTitle, *GraphName);
FoundNode->BreakAllNodeLinks(); FoundNode->BreakAllNodeLinks();
Graph->RemoveNode(FoundNode); Graph->RemoveNode(FoundNode);

View File

@@ -150,7 +150,7 @@ public:
} }
else else
{ {
NodeDesc = Expr->GetClass()->GetName(); NodeDesc = MCPUtils::FormatName(Expr->GetClass());
} }
// If the source node has input pins with connections, recurse // If the source node has input pins with connections, recurse
@@ -170,7 +170,7 @@ public:
else else
{ {
// Non-material node (e.g., root, comment), just use title // Non-material node (e.g., root, comment), just use title
NodeDesc = SourceNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); NodeDesc = MCPUtils::FormatName(SourceNode);
} }
Sources.Add(NodeDesc); Sources.Add(NodeDesc);
@@ -201,7 +201,7 @@ public:
{ {
if (!Pin || Pin->Direction != EGPD_Input) continue; if (!Pin || Pin->Direction != EGPD_Input) continue;
FString PinName = Pin->PinName.ToString(); FString PinName = MCPUtils::FormatName(Pin);
FString Description; FString Description;
if (Pin->LinkedTo.Num() == 0) if (Pin->LinkedTo.Num() == 0)

View File

@@ -117,13 +117,13 @@ public:
for (UEdGraphNode* N : GA->Nodes) for (UEdGraphNode* N : GA->Nodes)
{ {
if (!N) continue; if (!N) continue;
FString Title = N->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString Title = MCPUtils::FormatName(N);
NodesA.FindOrAdd(Title).Add(N); NodesA.FindOrAdd(Title).Add(N);
} }
for (UEdGraphNode* N : GB->Nodes) for (UEdGraphNode* N : GB->Nodes)
{ {
if (!N) continue; if (!N) continue;
FString Title = N->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString Title = MCPUtils::FormatName(N);
NodesB.FindOrAdd(Title).Add(N); NodesB.FindOrAdd(Title).Add(N);
} }
@@ -141,7 +141,7 @@ public:
{ {
TSharedRef<FJsonObject> NObj = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> NObj = MakeShared<FJsonObject>();
NObj->SetStringField(TEXT("title"), Pair.Key); 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); NObj->SetNumberField(TEXT("extraCount"), CountA - CountB);
OnlyInA.Add(MakeShared<FJsonValueObject>(NObj)); OnlyInA.Add(MakeShared<FJsonValueObject>(NObj));
} }
@@ -161,7 +161,7 @@ public:
{ {
TSharedRef<FJsonObject> NObj = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> NObj = MakeShared<FJsonObject>();
NObj->SetStringField(TEXT("title"), Pair.Key); 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); NObj->SetNumberField(TEXT("extraCount"), CountB - CountA);
OnlyInB.Add(MakeShared<FJsonValueObject>(NObj)); OnlyInB.Add(MakeShared<FJsonValueObject>(NObj));
} }
@@ -170,9 +170,9 @@ public:
// Connection diff: use connection key approach // Connection diff: use connection key approach
auto MakeConnKey = [](UEdGraphPin* SrcPin, UEdGraphPin* TgtPin) -> FString auto MakeConnKey = [](UEdGraphPin* SrcPin, UEdGraphPin* TgtPin) -> FString
{ {
FString SrcTitle = SrcPin->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString SrcTitle = MCPUtils::FormatName(SrcPin->GetOwningNode());
FString TgtTitle = TgtPin->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString TgtTitle = MCPUtils::FormatName(TgtPin->GetOwningNode());
return FString::Printf(TEXT("%s|%s|%s|%s"), *SrcTitle, *SrcPin->PinName.ToString(), *TgtTitle, *TgtPin->PinName.ToString()); return FString::Printf(TEXT("%s|%s|%s|%s"), *SrcTitle, *MCPUtils::FormatName(SrcPin), *TgtTitle, *MCPUtils::FormatName(TgtPin));
}; };
TSet<FString> ConnectionsA, ConnectionsB; TSet<FString> ConnectionsA, ConnectionsB;

View File

@@ -2,15 +2,13 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "UMCPHandler_DisconnectBlueprintPins.generated.h" #include "UMCPHandler_DisconnectPins.generated.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -23,21 +21,15 @@ struct FDisconnectPinEntry
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() UPROPERTY()
FString Node; FString Pin;
UPROPERTY()
FString PinName;
UPROPERTY(meta=(Optional)) UPROPERTY(meta=(Optional))
FString TargetNode; FString TargetPin;
UPROPERTY(meta=(Optional))
FString TargetPinName;
}; };
UCLASS() UCLASS()
class UMCPHandler_DisconnectBlueprintPins : public UObject, public IMCPHandler class UMCPHandler_DisconnectPins : public UObject, public IMCPHandler
{ {
GENERATED_BODY() GENERATED_BODY()
@@ -45,7 +37,7 @@ public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint; 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; FMCPJsonArray Disconnections;
virtual FString GetDescription() const override virtual FString GetDescription() const override
@@ -57,9 +49,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
UBlueprint* BP = Assets.Object(); if (!BP) return;
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0; int32 SuccessCount = 0;
@@ -73,45 +65,28 @@ public:
FDisconnectPinEntry Entry; FDisconnectPinEntry Entry;
if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, &*EntryResult)) continue; if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, &*EntryResult)) continue;
UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); MCPFetcher FP(EntryResult, BP);
if (!Node) UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
{ if (!Pin) continue;
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;
}
int32 DisconnectedCount = 0; int32 DisconnectedCount = 0;
if (!Entry.TargetNode.IsEmpty() && !Entry.TargetPinName.IsEmpty()) if (!Entry.TargetPin.IsEmpty())
{ {
UEdGraphNode* TargetNode = MCPUtils::FindNodeByGuid(BP, Entry.TargetNode); MCPFetcher FT(EntryResult, BP);
if (!TargetNode) UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
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; continue;
} }
UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*Entry.TargetPinName)); Pin->BreakLinkTo(Target);
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);
DisconnectedCount = 1; DisconnectedCount = 1;
} }
else else

View File

@@ -2,24 +2,13 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/World.h" #include "Animation/AnimBlueprint.h"
#include "Engine/Level.h" #include "Animation/Skeleton.h"
#include "Engine/LevelScriptBlueprint.h" #include "Engine/SimpleConstructionScript.h"
#include "EdGraph/EdGraph.h" #include "Engine/SCS_Node.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_DumpBlueprint.generated.h" #include "UMCPHandler_DumpBlueprint.generated.h"
@@ -38,15 +27,77 @@ public:
virtual FString GetDescription() const override 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 virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(Assets.Object()); Result->SetStringField(TEXT("name"), MCPUtils::FormatName(BP));
MCPUtils::CopyJsonFields(&*Tmp, Result); Result->SetStringField(TEXT("parentClass"), BP->ParentClass ? MCPUtils::FormatName(BP->ParentClass) : TEXT("None"));
Result->SetStringField(TEXT("blueprintType"),
StaticEnum<EBlueprintType>()->GetNameStringByValue((int64)BP->BlueprintType));
// Animation Blueprint
if (UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(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<TSharedPtr<FJsonValue>> Vars;
for (const FBPVariableDescription& V : BP->NewVariables)
{
TSharedRef<FJsonObject> VJ = MakeShared<FJsonObject>();
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<FJsonValueObject>(VJ));
}
Result->SetArrayField(TEXT("variables"), Vars);
// Interfaces
TArray<TSharedPtr<FJsonValue>> Ifaces;
for (const FBPInterfaceDescription& I : BP->ImplementedInterfaces)
{
if (I.Interface)
Ifaces.Add(MakeShared<FJsonValueString>(MCPUtils::FormatName(I.Interface)));
}
Result->SetArrayField(TEXT("interfaces"), Ifaces);
// Components
if (USimpleConstructionScript* SCS = BP->SimpleConstructionScript)
{
TArray<TSharedPtr<FJsonValue>> Comps;
for (USCS_Node* Node : SCS->GetAllNodes())
{
if (!Node || !Node->ComponentTemplate) continue;
TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>();
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<FJsonValueObject>(CJ));
}
Result->SetArrayField(TEXT("components"), Comps);
}
// Graph names (without contents)
Result->SetArrayField(TEXT("graphs"), MCPUtils::AllGraphNamesJson(BP));
} }
}; };

View File

@@ -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<UBlueprint> Assets;
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = Assets.Object();
TArray<UEdGraph*> AllGraphs = MCPUtils::AllGraphs(BP);
for (UEdGraph* GraphObj : AllGraphs)
{
if (GraphObj->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
{
TSharedPtr<FJsonObject> GraphJson = MCPUtils::SerializeGraph(GraphObj);
if (GraphJson.IsValid())
{
MCPUtils::CopyJsonFields(GraphJson.Get(), Result);
return;
}
}
}
// Not found — list available graphs
TArray<TSharedPtr<FJsonValue>> GraphNames;
for (UEdGraph* GraphObj : AllGraphs)
{
GraphNames.Add(MakeShared<FJsonValueString>(GraphObj->GetName()));
}
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
Result->SetArrayField(TEXT("availableGraphs"), GraphNames);
}
};

View File

@@ -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<UEdGraph>(F.Obj))
{
EmitGraph(Graph, Result);
return;
}
if (UBlueprint* BP = Cast<UBlueprint>(F.Obj))
{
TArray<UEdGraph*> 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);
}
}
};

View File

@@ -174,8 +174,8 @@ public:
Entry->SetStringField(TEXT("newNodeId"), NewNode->NodeGuid.ToString()); Entry->SetStringField(TEXT("newNodeId"), NewNode->NodeGuid.ToString());
Entry->SetNumberField(TEXT("posX"), NewNode->NodePosX); Entry->SetNumberField(TEXT("posX"), NewNode->NodePosX);
Entry->SetNumberField(TEXT("posY"), NewNode->NodePosY); Entry->SetNumberField(TEXT("posY"), NewNode->NodePosY);
Entry->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); Entry->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(NewNode->GetClass()));
Entry->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); Entry->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(NewNode));
DuplicatedNodes.Add(MakeShared<FJsonValueObject>(Entry)); DuplicatedNodes.Add(MakeShared<FJsonValueObject>(Entry));
} }

View File

@@ -2,52 +2,9 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.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/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" #include "UMCPHandler_GetNodeComment.generated.h"
@@ -61,10 +18,7 @@ class UMCPHandler_GetNodeComment : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode"))
FString Blueprint;
UPROPERTY(meta=(Description="Node GUID"))
FString Node; FString Node;
virtual FString GetDescription() const override virtual FString GetDescription() const override
@@ -75,15 +29,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
UBlueprint* BP = Assets.Object(); if (!FoundNode) return;
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node);
if (!FoundNode)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node));
}
Result->SetStringField(TEXT("comment"), FoundNode->NodeComment); Result->SetStringField(TEXT("comment"), FoundNode->NodeComment);
Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible); Result->SetBoolField(TEXT("commentBubbleVisible"), FoundNode->bCommentBubbleVisible);

View File

@@ -38,7 +38,7 @@ public:
UEdGraphPin* P = F.Walk(Pin).Cast<UEdGraphPin>(); UEdGraphPin* P = F.Walk(Pin).Cast<UEdGraphPin>();
if (!P) return; 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("direction"), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"));
Result->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString()); Result->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString());
@@ -79,8 +79,8 @@ public:
if (!Linked || !Linked->GetOwningNode()) continue; if (!Linked || !Linked->GetOwningNode()) continue;
TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>();
CJ->SetStringField(TEXT("nodeId"), Linked->GetOwningNode()->NodeGuid.ToString()); CJ->SetStringField(TEXT("nodeId"), Linked->GetOwningNode()->NodeGuid.ToString());
CJ->SetStringField(TEXT("pinName"), Linked->PinName.ToString()); CJ->SetStringField(TEXT("pinName"), MCPUtils::FormatName(Linked));
CJ->SetStringField(TEXT("nodeTitle"), Linked->GetOwningNode()->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); CJ->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Linked->GetOwningNode()));
Conns.Add(MakeShared<FJsonValueObject>(CJ)); Conns.Add(MakeShared<FJsonValueObject>(CJ));
} }
Result->SetArrayField(TEXT("connectedTo"), Conns); Result->SetArrayField(TEXT("connectedTo"), Conns);

View File

@@ -61,7 +61,7 @@ public:
if (Node->ComponentClass) if (Node->ComponentClass)
{ {
CompObj->SetStringField(TEXT("componentClass"), Node->ComponentClass->GetName()); CompObj->SetStringField(TEXT("componentClass"), MCPUtils::FormatName(Node->ComponentClass));
} }
else else
{ {

View File

@@ -47,7 +47,7 @@ public:
} }
TSharedRef<FJsonObject> IfaceObj = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> IfaceObj = MakeShared<FJsonObject>();
IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName()); IfaceObj->SetStringField(TEXT("name"), MCPUtils::FormatName(IfaceDesc.Interface));
IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName()); IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName());
TArray<TSharedPtr<FJsonValue>> FuncArr; TArray<TSharedPtr<FJsonValue>> FuncArr;
@@ -55,7 +55,7 @@ public:
{ {
if (Graph) if (Graph)
{ {
FuncArr.Add(MakeShared<FJsonValueString>(Graph->GetName())); FuncArr.Add(MakeShared<FJsonValueString>(MCPUtils::FormatName(Graph)));
} }
} }
IfaceObj->SetArrayField(TEXT("functions"), FuncArr); IfaceObj->SetArrayField(TEXT("functions"), FuncArr);

View File

@@ -80,7 +80,7 @@ public:
UClass* OwnerClass = Func->GetOwnerClass(); UClass* OwnerClass = Func->GetOwnerClass();
if (OwnerClass) if (OwnerClass)
{ {
FuncObj->SetStringField(TEXT("definedIn"), OwnerClass->GetName()); FuncObj->SetStringField(TEXT("definedIn"), MCPUtils::FormatName(OwnerClass));
} }
// Function flags // Function flags
@@ -124,7 +124,7 @@ public:
FuncList.Add(MakeShared<FJsonValueObject>(FuncObj)); FuncList.Add(MakeShared<FJsonValueObject>(FuncObj));
} }
Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetStringField(TEXT("className"), MCPUtils::FormatName(FoundClass));
Result->SetNumberField(TEXT("count"), FuncList.Num()); Result->SetNumberField(TEXT("count"), FuncList.Num());
Result->SetArrayField(TEXT("functions"), FuncList); Result->SetArrayField(TEXT("functions"), FuncList);
} }

View File

@@ -78,7 +78,7 @@ public:
UClass* OwnerClass = Prop->GetOwnerClass(); UClass* OwnerClass = Prop->GetOwnerClass();
if (OwnerClass) if (OwnerClass)
{ {
PropObj->SetStringField(TEXT("definedIn"), OwnerClass->GetName()); PropObj->SetStringField(TEXT("definedIn"), MCPUtils::FormatName(OwnerClass));
} }
// Property flags // Property flags
@@ -99,7 +99,7 @@ public:
PropList.Add(MakeShared<FJsonValueObject>(PropObj)); PropList.Add(MakeShared<FJsonValueObject>(PropObj));
} }
Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetStringField(TEXT("className"), MCPUtils::FormatName(FoundClass));
Result->SetNumberField(TEXT("count"), PropList.Num()); Result->SetNumberField(TEXT("count"), PropList.Num());
Result->SetArrayField(TEXT("properties"), PropList); Result->SetArrayField(TEXT("properties"), PropList);
} }

View File

@@ -128,9 +128,9 @@ public:
{ {
if (Node->bHasCompilerMessage) if (Node->bHasCompilerMessage)
{ {
FString NodeTitle = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString NodeTitle = MCPUtils::FormatName(Node);
FString NodeMsg = FString::Printf(TEXT("[%s] %s: %s"), 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) if (Node->ErrorType == EMessageSeverity::Error)
{ {
ErrorsArr.Add(MakeShared<FJsonValueString>(NodeMsg)); ErrorsArr.Add(MakeShared<FJsonValueString>(NodeMsg));

View File

@@ -80,7 +80,7 @@ public:
{ {
if (IfaceDesc.Interface) if (IfaceDesc.Interface)
{ {
IfaceList.Add(MakeShared<FJsonValueString>(IfaceDesc.Interface->GetName())); IfaceList.Add(MakeShared<FJsonValueString>(MCPUtils::FormatName(IfaceDesc.Interface)));
} }
} }
@@ -94,7 +94,7 @@ public:
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"), 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); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
@@ -102,8 +102,8 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"), 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));
} }
}; };

View File

@@ -51,7 +51,7 @@ public:
// Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name // Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name
for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) if (MCPUtils::Identifies(FunctionName, FuncEntry->GetGraph()))
{ {
EntryNode = FuncEntry; EntryNode = FuncEntry;
FoundNodeType = TEXT("FunctionEntry"); FoundNodeType = TEXT("FunctionEntry");
@@ -80,7 +80,7 @@ public:
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP)) for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
{ {
Available.Add(MakeShared<FJsonValueString>( Available.Add(MakeShared<FJsonValueString>(
FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName()))); FString::Printf(TEXT("function:%s"), *MCPUtils::FormatName(FE->GetGraph()))));
} }
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP)) for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
{ {

View File

@@ -48,7 +48,7 @@ public:
// Check if it's an UbergraphPage — disallow rename // Check if it's an UbergraphPage — disallow rename
for (UEdGraph* CandidateGraph : BP->UbergraphPages) 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( return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."), TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."),
@@ -62,7 +62,7 @@ public:
for (UEdGraph* CandidateGraph : BP->FunctionGraphs) for (UEdGraph* CandidateGraph : BP->FunctionGraphs)
{ {
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph))
{ {
TargetGraph = CandidateGraph; TargetGraph = CandidateGraph;
GraphType = TEXT("function"); GraphType = TEXT("function");
@@ -73,7 +73,7 @@ public:
{ {
for (UEdGraph* CandidateGraph : BP->MacroGraphs) for (UEdGraph* CandidateGraph : BP->MacroGraphs)
{ {
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase)) if (CandidateGraph && MCPUtils::Identifies(Graph, CandidateGraph))
{ {
TargetGraph = CandidateGraph; TargetGraph = CandidateGraph;
GraphType = TEXT("macro"); GraphType = TEXT("macro");
@@ -108,7 +108,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"),
*Graph, *NewName, bSaved ? TEXT("true") : TEXT("false")); *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->SetStringField(TEXT("graphType"), GraphType);
Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -43,7 +43,7 @@ public:
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
UBlueprint* BP = Assets.Object(); 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 // Find the new parent class
// Try C++ class first (e.g. "WebUIHUD" finds /Script/ModuleName.WebUIHUD) // 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 // Just warn, don't block — the user may intentionally reparent to a sibling
UE_LOG(LogTemp, Warning, UE_LOG(LogTemp, Warning,
TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s' — classes are not in a direct hierarchy"), 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'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s'"),
*Blueprint, *OldParentName, *NewParentClassObj->GetName()); *Blueprint, *OldParentName, *MCPUtils::FormatName(NewParentClassObj));
// Perform reparent // Perform reparent
BP->PreEditChange(nullptr); BP->PreEditChange(nullptr);
@@ -105,7 +105,7 @@ public:
// Save // Save
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
FString NewParentActualName = NewParentClassObj->GetName(); FString NewParentActualName = MCPUtils::FormatName(NewParentClassObj);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"),
bSaved ? TEXT("succeeded") : TEXT("failed")); bSaved ? TEXT("succeeded") : TEXT("failed"));

View File

@@ -202,9 +202,9 @@ public:
AtRisk->SetStringField(TEXT("type"), TEXT("connectionAtRisk")); AtRisk->SetStringField(TEXT("type"), TEXT("connectionAtRisk"));
AtRisk->SetStringField(TEXT("functionName"), FuncName.ToString()); AtRisk->SetStringField(TEXT("functionName"), FuncName.ToString());
AtRisk->SetStringField(TEXT("nodeId"), CallNode->NodeGuid.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("connectedToNode"), Linked->GetOwningNode()->NodeGuid.ToString());
AtRisk->SetStringField(TEXT("connectedToPin"), Linked->PinName.ToString()); AtRisk->SetStringField(TEXT("connectedToPin"), MCPUtils::FormatName(Linked));
BrokenConnections.Add(MakeShared<FJsonValueObject>(AtRisk)); BrokenConnections.Add(MakeShared<FJsonValueObject>(AtRisk));
} }
} }

View File

@@ -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<UObject> 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<FAssetData>& 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);
}
}
};

View File

@@ -125,7 +125,7 @@ public:
R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("functionParameter")); R->SetStringField(TEXT("usage"), TEXT("functionParameter"));
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"), 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("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString()); R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
if (!ParamSubtype.IsEmpty()) if (!ParamSubtype.IsEmpty())
@@ -172,7 +172,7 @@ public:
R->SetStringField(TEXT("blueprint"), BPName); R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("breakStruct")); 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("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName()); R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName());
if (bIsLevel) if (bIsLevel)
@@ -188,7 +188,7 @@ public:
R->SetStringField(TEXT("blueprint"), BPName); R->SetStringField(TEXT("blueprint"), BPName);
R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("makeStruct")); 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("nodeId"), Node->NodeGuid.ToString());
R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName()); R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName());
if (bIsLevel) if (bIsLevel)
@@ -214,10 +214,10 @@ public:
R->SetStringField(TEXT("blueprintPath"), BPPath); R->SetStringField(TEXT("blueprintPath"), BPPath);
R->SetStringField(TEXT("usage"), TEXT("pinConnection")); R->SetStringField(TEXT("usage"), TEXT("pinConnection"));
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"), R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
*Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *MCPUtils::FormatName(Node),
*Pin->PinName.ToString())); *MCPUtils::FormatName(Pin)));
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString()); 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()); R->SetStringField(TEXT("pinType"), Pin->PinType.PinCategory.ToString());
if (!PinSubtype.IsEmpty()) if (!PinSubtype.IsEmpty())
R->SetStringField(TEXT("pinSubtype"), PinSubtype); R->SetStringField(TEXT("pinSubtype"), PinSubtype);

View File

@@ -92,7 +92,7 @@ public:
if (ClassList.Num() >= Limit) continue; // Count but don't add beyond limit if (ClassList.Num() >= Limit) continue; // Count but don't add beyond limit
TSharedRef<FJsonObject> ClassObj = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> ClassObj = MakeShared<FJsonObject>();
ClassObj->SetStringField(TEXT("name"), ClassName); ClassObj->SetStringField(TEXT("name"), MCPUtils::FormatName(Class));
ClassObj->SetStringField(TEXT("fullPath"), Class->GetPathName()); ClassObj->SetStringField(TEXT("fullPath"), Class->GetPathName());
// Determine if it's a Blueprint-generated class // Determine if it's a Blueprint-generated class
@@ -102,7 +102,7 @@ public:
// Parent class // Parent class
if (Class->GetSuperClass()) if (Class->GetSuperClass())
{ {
ClassObj->SetStringField(TEXT("parentClass"), Class->GetSuperClass()->GetName()); ClassObj->SetStringField(TEXT("parentClass"), MCPUtils::FormatName(Class->GetSuperClass()));
} }
// Module/package info // Module/package info

View File

@@ -92,9 +92,9 @@ public:
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>(); TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
R->SetStringField(TEXT("blueprint"), AssetName); R->SetStringField(TEXT("blueprint"), AssetName);
R->SetStringField(TEXT("blueprintPath"), AssetPath); R->SetStringField(TEXT("blueprintPath"), AssetPath);
R->SetStringField(TEXT("graph"), Node->GetGraph()->GetName()); R->SetStringField(TEXT("graph"), MCPUtils::FormatName(Node->GetGraph()));
R->SetStringField(TEXT("nodeTitle"), Title); R->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(Node));
R->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName()); R->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(Node->GetClass()));
if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName); if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName);
if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName); if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName);
if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName); if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName);

View File

@@ -151,7 +151,7 @@ public:
{ {
return MCPUtils::MakeErrorJson(Result, FString::Printf( return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("'%s' is not a subclass of '%s' (required by property '%s')"), 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); ClassProp->SetPropertyValue_InContainer(CDO, ResolvedClass);
} }
@@ -160,7 +160,7 @@ public:
FSoftObjectPtr SoftPtr(ResolvedClass); FSoftObjectPtr SoftPtr(ResolvedClass);
SoftClassProp->SetPropertyValue_InContainer(CDO, SoftPtr); SoftClassProp->SetPropertyValue_InContainer(CDO, SoftPtr);
} }
ActualNewValue = ResolvedClass->GetName(); ActualNewValue = MCPUtils::FormatName(ResolvedClass);
bSuccess = true; bSuccess = true;
} }
// Handle object properties (TObjectPtr, UObject*) // Handle object properties (TObjectPtr, UObject*)

View File

@@ -2,52 +2,10 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.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/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/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" #include "UMCPHandler_SetNodeComment.generated.h"
@@ -61,10 +19,7 @@ class UMCPHandler_SetNodeComment : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Path to the node, e.g. /Game/Foo,node:MyNode"))
FString Blueprint;
UPROPERTY(meta=(Description="Node GUID"))
FString Node; FString Node;
UPROPERTY(meta=(Description="Comment text to set")) UPROPERTY(meta=(Description="Comment text to set"))
@@ -78,15 +33,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
UBlueprint* BP = Assets.Object(); if (!FoundNode) return;
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node);
if (!FoundNode)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node));
}
FString OldComment = FoundNode->NodeComment; FString OldComment = FoundNode->NodeComment;
FoundNode->NodeComment = Comment; FoundNode->NodeComment = Comment;
@@ -98,10 +47,11 @@ public:
FoundNode->bCommentBubblePinned = true; FoundNode->bCommentBubblePinned = true;
} }
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode);
FBlueprintEditorUtils::MarkBlueprintAsModified(BP); FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s'"),
*Node, *Blueprint); *MCPUtils::FormatName(FoundNode));
Result->SetStringField(TEXT("oldComment"), OldComment); Result->SetStringField(TEXT("oldComment"), OldComment);
} }

View File

@@ -2,52 +2,11 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.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/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/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" #include "UMCPHandler_SetNodePositions.generated.h"
@@ -91,9 +50,9 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{ {
MCPAssets<UBlueprint> Assets; MCPFetcher F(Result);
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return; UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
UBlueprint* BP = Assets.Object(); if (!BP) return;
TArray<TSharedPtr<FJsonValue>> Results; TArray<TSharedPtr<FJsonValue>> Results;
int32 SuccessCount = 0; int32 SuccessCount = 0;
@@ -106,12 +65,9 @@ public:
FMoveNodeEntry Entry; FMoveNodeEntry Entry;
if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal, &*EntryResult)) continue; if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal, &*EntryResult)) continue;
UEdGraphNode* Node = MCPUtils::FindNodeByGuid(BP, Entry.Node); MCPFetcher FN(EntryResult, BP);
if (!Node) UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
{ if (!Node) continue;
EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Node '%s' not found"), *Entry.Node));
continue;
}
int32 OldX = Node->NodePosX; int32 OldX = Node->NodePosX;
int32 OldY = Node->NodePosY; int32 OldY = Node->NodePosY;

View File

@@ -2,10 +2,9 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssetFinder.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/World.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
@@ -66,25 +65,12 @@ public:
FSetPinDefaultEntry Entry; FSetPinDefaultEntry Entry;
if (!MCPUtils::PopulateFromJson(FSetPinDefaultEntry::StaticStruct(), &Entry, PinVal, &*EntryResult)) continue; if (!MCPUtils::PopulateFromJson(FSetPinDefaultEntry::StaticStruct(), &Entry, PinVal, &*EntryResult)) continue;
MCPAssets<UBlueprint> Assets; MCPFetcher FE(EntryResult);
if (!Assets.Scan<UBlueprint>().Scan<UWorld>().Exact(Entry.Blueprint).Errors(&*EntryResult).ENone().ETwo().Load()) FE.Walk(Entry.Blueprint).Node(Entry.Node).Pin(Entry.PinName);
continue; UEdGraphPin* Pin = FE.Cast<UEdGraphPin>();
UBlueprint* BP = Assets.Object(); if (!Pin) continue;
UEdGraph* Graph = nullptr; UEdGraphNode* Node = Pin->GetOwningNode();
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;
}
if (Pin->Direction != EGPD_Input) if (Pin->Direction != EGPD_Input)
{ {
@@ -92,7 +78,7 @@ public:
continue; continue;
} }
const UEdGraphSchema* Schema = Graph->GetSchema(); const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema();
if (Schema) if (Schema)
{ {
FString ValidationError = Schema->IsPinDefaultValid(Pin, Entry.Value, nullptr, FText::GetEmpty()); FString ValidationError = Schema->IsPinDefaultValid(Pin, Entry.Value, nullptr, FText::GetEmpty());
@@ -110,7 +96,7 @@ public:
EntryResult->SetStringField(TEXT("oldValue"), OldValue); EntryResult->SetStringField(TEXT("oldValue"), OldValue);
SuccessCount++; SuccessCount++;
ModifiedNodes.Add(Node); ModifiedNodes.Add(Node);
ModifiedBlueprints.Add(BP); ModifiedBlueprints.Add(FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node));
} }
for (UEdGraphNode* Node : ModifiedNodes) for (UEdGraphNode* Node : ModifiedNodes)

View File

@@ -109,7 +109,7 @@ public:
TArray<TSharedPtr<FJsonValue>> GraphNames; TArray<TSharedPtr<FJsonValue>> GraphNames;
for (UEdGraph* G : MCPUtils::AllGraphs(BP)) for (UEdGraph* G : MCPUtils::AllGraphs(BP))
{ {
GraphNames.Add(MakeShared<FJsonValueString>(G->GetName())); GraphNames.Add(MakeShared<FJsonValueString>(MCPUtils::FormatName(G)));
} }
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
Result->SetArrayField(TEXT("availableGraphs"), GraphNames); 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'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Spawned node '%s' (class %s) via action '%s' in graph '%s' of '%s'"),
*NewNode->NodeGuid.ToString(), *NewNode->NodeGuid.ToString(),
*NewNode->GetClass()->GetName(), *MCPUtils::FormatName(NewNode->GetClass()),
*Entry.ActionName, *Entry.ActionName,
*DecodedGraphName, *DecodedGraphName,
*Blueprint); *Blueprint);
@@ -174,8 +174,8 @@ public:
TSharedPtr<FJsonObject> NodeState = MCPUtils::SerializeNode(NewNode); TSharedPtr<FJsonObject> NodeState = MCPUtils::SerializeNode(NewNode);
EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString()); EntryResult->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString());
EntryResult->SetStringField(TEXT("nodeClass"), NewNode->GetClass()->GetName()); EntryResult->SetStringField(TEXT("nodeClass"), MCPUtils::FormatName(NewNode->GetClass()));
EntryResult->SetStringField(TEXT("nodeTitle"), NewNode->GetNodeTitle(ENodeTitleType::ListView).ToString()); EntryResult->SetStringField(TEXT("nodeTitle"), MCPUtils::FormatName(NewNode));
if (NodeState.IsValid()) if (NodeState.IsValid())
{ {
EntryResult->SetObjectField(TEXT("node"), NodeState); EntryResult->SetObjectField(TEXT("node"), NodeState);

View File

@@ -55,7 +55,7 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"),
*BP->GetName(), *BP->GetName(),
BP->GeneratedClass ? *BP->GeneratedClass->GetName() : TEXT("null")); BP->GeneratedClass ? *MCPUtils::FormatName(BP->GeneratedClass) : TEXT("null"));
// Attempt save with NO modifications // Attempt save with NO modifications
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);

View File

@@ -14,7 +14,7 @@
#include "Handlers/UMCPHandler_CheckPinConnectionCompatibility.h" #include "Handlers/UMCPHandler_CheckPinConnectionCompatibility.h"
#include "Handlers/UMCPHandler_CompileBlueprint.h" #include "Handlers/UMCPHandler_CompileBlueprint.h"
#include "Handlers/UMCPHandler_CompileMaterial.h" #include "Handlers/UMCPHandler_CompileMaterial.h"
#include "Handlers/UMCPHandler_ConnectBlueprintPins.h" #include "Handlers/UMCPHandler_ConnectPins.h"
#include "Handlers/UMCPHandler_ConnectMaterialExpressionPins.h" #include "Handlers/UMCPHandler_ConnectMaterialExpressionPins.h"
#include "Handlers/UMCPHandler_CreateAnimBlueprintAsset.h" #include "Handlers/UMCPHandler_CreateAnimBlueprintAsset.h"
#include "Handlers/UMCPHandler_CreateBlendSpaceAsset.h" #include "Handlers/UMCPHandler_CreateBlendSpaceAsset.h"
@@ -31,10 +31,10 @@
#include "Handlers/UMCPHandler_DeleteNodeFromGraph.h" #include "Handlers/UMCPHandler_DeleteNodeFromGraph.h"
#include "Handlers/UMCPHandler_DescribeMaterialInEnglish.h" #include "Handlers/UMCPHandler_DescribeMaterialInEnglish.h"
#include "Handlers/UMCPHandler_DiffTwoBlueprints.h" #include "Handlers/UMCPHandler_DiffTwoBlueprints.h"
#include "Handlers/UMCPHandler_DisconnectBlueprintPins.h" #include "Handlers/UMCPHandler_DisconnectPins.h"
#include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h" #include "Handlers/UMCPHandler_DisconnectMaterialExpressionPin.h"
#include "Handlers/UMCPHandler_DumpBlueprint.h" #include "Handlers/UMCPHandler_DumpBlueprint.h"
#include "Handlers/UMCPHandler_DumpBlueprintGraph.h" #include "Handlers/UMCPHandler_DumpGraphs.h"
#include "Handlers/UMCPHandler_DumpMaterial.h" #include "Handlers/UMCPHandler_DumpMaterial.h"
#include "Handlers/UMCPHandler_DumpMaterialExpressionGraph.h" #include "Handlers/UMCPHandler_DumpMaterialExpressionGraph.h"
#include "Handlers/UMCPHandler_DumpMaterialFunction.h" #include "Handlers/UMCPHandler_DumpMaterialFunction.h"
@@ -67,6 +67,7 @@
#include "Handlers/UMCPHandler_ReparentMaterialInstance.h" #include "Handlers/UMCPHandler_ReparentMaterialInstance.h"
#include "Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h" #include "Handlers/UMCPHandler_ReplaceFunctionCallsInBlueprint.h"
#include "Handlers/UMCPHandler_RestoreAsset.h" #include "Handlers/UMCPHandler_RestoreAsset.h"
#include "Handlers/UMCPHandler_SearchAssets.h"
#include "Handlers/UMCPHandler_SearchSpawnableNodeTypes.h" #include "Handlers/UMCPHandler_SearchSpawnableNodeTypes.h"
#include "Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h" #include "Handlers/UMCPHandler_SearchTypeUsageInBlueprints.h"
#include "Handlers/UMCPHandler_SearchUnrealClasses.h" #include "Handlers/UMCPHandler_SearchUnrealClasses.h"

View File

@@ -127,29 +127,29 @@ void MCPUtils::AppendNumericSuffix(FString &Name, int32 N)
Name += FString::Printf(TEXT("_%d"), N - 1); Name += FString::Printf(TEXT("_%d"), N - 1);
} }
FString MCPUtils::FormatName(UWorld *World) FString MCPUtils::FormatName(const UWorld *World)
{ {
return World->GetPathName(); return World->GetPathName();
} }
FString MCPUtils::FormatName(UBlueprint *BP) FString MCPUtils::FormatName(const UBlueprint *BP)
{ {
return BP->GetPathName(); return BP->GetPathName();
} }
FString MCPUtils::FormatName(UActorComponent *C) FString MCPUtils::FormatName(const UActorComponent *C)
{ {
return C->GetName(); return C->GetName();
} }
FString MCPUtils::FormatName(UEdGraph *Graph) FString MCPUtils::FormatName(const UEdGraph *Graph)
{ {
FString Name = Graph->GetName(); FString Name = Graph->GetName();
SanitizeNameInPlace(Name); SanitizeNameInPlace(Name);
return Name; return Name;
} }
FString MCPUtils::FormatName(UEdGraphNode* Node) FString MCPUtils::FormatName(const UEdGraphNode* Node)
{ {
// Sanitized first line of the node title. // Sanitized first line of the node title.
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(); FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
@@ -161,7 +161,7 @@ FString MCPUtils::FormatName(UEdGraphNode* Node)
return Title; return Title;
} }
FString MCPUtils::FormatName(UEdGraphPin *Pin) FString MCPUtils::FormatName(const UEdGraphPin *Pin)
{ {
FString Name = Pin->PinName.ToString(); FString Name = Pin->PinName.ToString();
SanitizeNameInPlace(Name); SanitizeNameInPlace(Name);
@@ -198,34 +198,34 @@ bool MCPUtils::Identifies(const FString &Name, const UClass *Class)
// Identifies // 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); 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); 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); 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); 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)) if (Node->NodeGuid.ToString().Equals(Name, ESearchCase::IgnoreCase))
return true; return true;
return FormatName(Node).Equals(Name, ESearchCase::IgnoreCase); 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); return FormatName(Pin).Equals(Name, ESearchCase::IgnoreCase);
} }

View File

@@ -81,6 +81,7 @@ struct MCPErrorCallback
MCPErrorCallback(std::nullptr_t); MCPErrorCallback(std::nullptr_t);
MCPErrorCallback(FString& OutError); MCPErrorCallback(FString& OutError);
MCPErrorCallback(FJsonObject* Result); MCPErrorCallback(FJsonObject* Result);
MCPErrorCallback(const TSharedRef<FJsonObject>& Result) : MCPErrorCallback(&*Result) {}
MCPErrorCallback(FStringBuilderBase& OutResult); MCPErrorCallback(FStringBuilderBase& OutResult);
void SetError(const FString& Msg) const { Func(Msg); } void SetError(const FString& Msg) const { Func(Msg); }
@@ -104,12 +105,12 @@ public:
// //
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
static FString FormatName(UWorld *World); static FString FormatName(const UWorld *World);
static FString FormatName(UBlueprint *BP); static FString FormatName(const UBlueprint *BP);
static FString FormatName(UActorComponent *C); static FString FormatName(const UActorComponent *C);
static FString FormatName(UEdGraph *Graph); static FString FormatName(const UEdGraph *Graph);
static FString FormatName(UEdGraphNode* Node); static FString FormatName(const UEdGraphNode* Node);
static FString FormatName(UEdGraphPin *Pin); static FString FormatName(const UEdGraphPin *Pin);
static FString FormatName(const FMemberReference &Ref); static FString FormatName(const FMemberReference &Ref);
static FString FormatName(const FBPVariableDescription &Var); static FString FormatName(const FBPVariableDescription &Var);
static FString FormatName(const UClass *Class); 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, const UWorld *World);
static bool Identifies(const FString &Name, UBlueprint *BP); static bool Identifies(const FString &Name, const UBlueprint *BP);
static bool Identifies(const FString &Name, UActorComponent *C); static bool Identifies(const FString &Name, const UActorComponent *C);
static bool Identifies(const FString &Name, UEdGraph *Graph); static bool Identifies(const FString &Name, const UEdGraph *Graph);
static bool Identifies(const FString &Name, UEdGraphNode* Node); static bool Identifies(const FString &Name, const UEdGraphNode* Node);
static bool Identifies(const FString &Name, UEdGraphPin *Pin); 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 FMemberReference &Ref);
static bool Identifies(const FString &Name, const UClass *Class); static bool Identifies(const FString &Name, const UClass *Class);