|
|
|
|
@@ -459,219 +459,212 @@ void FBlueprintMCPServer::HandleDeleteAsset(const FJsonObject* Json, FJsonObject
|
|
|
|
|
// HandleConnectPins — wire two pins together
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void FBlueprintMCPServer::HandleConnectPins(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
// connect_pins is now handled by UMCPHandler_ConnectPins (new-style registry)
|
|
|
|
|
|
|
|
|
|
void UMCPHandler_ConnectPins::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
|
|
|
FString SourceNodeId = Json->GetStringField(TEXT("sourceNodeId"));
|
|
|
|
|
FString SourcePinName = Json->GetStringField(TEXT("sourcePinName"));
|
|
|
|
|
FString TargetNodeId = Json->GetStringField(TEXT("targetNodeId"));
|
|
|
|
|
FString TargetPinName = Json->GetStringField(TEXT("targetPinName"));
|
|
|
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
|
|
|
|
|
|
if (BlueprintName.IsEmpty() || SourceNodeId.IsEmpty() || SourcePinName.IsEmpty() ||
|
|
|
|
|
TargetNodeId.IsEmpty() || TargetPinName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load Blueprint
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
|
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, LoadError);
|
|
|
|
|
return Helper->MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find source node
|
|
|
|
|
UEdGraph* SourceGraph = nullptr;
|
|
|
|
|
UEdGraphNode* SourceNode = FindNodeByGuid(BP, SourceNodeId, &SourceGraph);
|
|
|
|
|
if (!SourceNode)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId));
|
|
|
|
|
}
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> Results;
|
|
|
|
|
int32 SuccessCount = 0;
|
|
|
|
|
|
|
|
|
|
// Find target node
|
|
|
|
|
UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId);
|
|
|
|
|
if (!TargetNode)
|
|
|
|
|
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId));
|
|
|
|
|
}
|
|
|
|
|
TSharedRef<FJsonObject> EntryResult = MakeShared<FJsonObject>();
|
|
|
|
|
Results.Add(MakeShared<FJsonValueObject>(EntryResult));
|
|
|
|
|
|
|
|
|
|
// Find source pin
|
|
|
|
|
UEdGraphPin* SourcePin = SourceNode->FindPin(FName(*SourcePinName));
|
|
|
|
|
if (!SourcePin)
|
|
|
|
|
{
|
|
|
|
|
// List available pins for debugging
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> PinNames;
|
|
|
|
|
for (UEdGraphPin* P : SourceNode->Pins)
|
|
|
|
|
FConnectPinsEntry Entry;
|
|
|
|
|
FString PopulateError = Helper->PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal);
|
|
|
|
|
if (!PopulateError.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
if (P) PinNames.Add(MakeShared<FJsonValueString>(
|
|
|
|
|
FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(),
|
|
|
|
|
P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"))));
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), PopulateError);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
MakeErrorJson(Result, FString::Printf(TEXT("Source pin '%s' not found on node '%s'"),
|
|
|
|
|
*SourcePinName, *SourceNodeId));
|
|
|
|
|
Result->SetArrayField(TEXT("availablePins"), PinNames);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find target pin
|
|
|
|
|
UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName));
|
|
|
|
|
if (!TargetPin)
|
|
|
|
|
{
|
|
|
|
|
// List available pins for debugging
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> PinNames;
|
|
|
|
|
for (UEdGraphPin* P : TargetNode->Pins)
|
|
|
|
|
EntryResult->SetStringField(TEXT("sourceNodeId"), Entry.SourceNodeId);
|
|
|
|
|
EntryResult->SetStringField(TEXT("sourcePinName"), Entry.SourcePinName);
|
|
|
|
|
EntryResult->SetStringField(TEXT("targetNodeId"), Entry.TargetNodeId);
|
|
|
|
|
EntryResult->SetStringField(TEXT("targetPinName"), Entry.TargetPinName);
|
|
|
|
|
|
|
|
|
|
UEdGraph* SourceGraph = nullptr;
|
|
|
|
|
UEdGraphNode* SourceNode = Helper->FindNodeByGuid(BP, Entry.SourceNodeId, &SourceGraph);
|
|
|
|
|
if (!SourceNode)
|
|
|
|
|
{
|
|
|
|
|
if (P) PinNames.Add(MakeShared<FJsonValueString>(
|
|
|
|
|
FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(),
|
|
|
|
|
P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"))));
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Source node '%s' not found"), *Entry.SourceNodeId));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"),
|
|
|
|
|
*TargetPinName, *TargetNodeId));
|
|
|
|
|
Result->SetArrayField(TEXT("availablePins"), PinNames);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
UEdGraphNode* TargetNode = Helper->FindNodeByGuid(BP, Entry.TargetNodeId);
|
|
|
|
|
if (!TargetNode)
|
|
|
|
|
{
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target node '%s' not found"), *Entry.TargetNodeId));
|
|
|
|
|
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.SourceNodeId));
|
|
|
|
|
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.TargetNodeId));
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntryResult->SetBoolField(TEXT("success"), true);
|
|
|
|
|
SuccessCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Connecting %s.%s -> %s.%s"),
|
|
|
|
|
*SourceNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *SourcePinName,
|
|
|
|
|
*TargetNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *TargetPinName);
|
|
|
|
|
|
|
|
|
|
// Try type-validated connection via the schema
|
|
|
|
|
const UEdGraphSchema* Schema = SourceGraph->GetSchema();
|
|
|
|
|
if (!Schema)
|
|
|
|
|
if (SuccessCount > 0)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Graph schema not found"));
|
|
|
|
|
}
|
|
|
|
|
bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin);
|
|
|
|
|
|
|
|
|
|
Result->SetBoolField(TEXT("success"), bConnected);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
|
|
|
Result->SetStringField(TEXT("sourcePinType"), SourcePin->PinType.PinCategory.ToString());
|
|
|
|
|
if (SourcePin->PinType.PinSubCategoryObject.IsValid())
|
|
|
|
|
Result->SetStringField(TEXT("sourcePinSubtype"), SourcePin->PinType.PinSubCategoryObject->GetName());
|
|
|
|
|
Result->SetStringField(TEXT("targetPinType"), TargetPin->PinType.PinCategory.ToString());
|
|
|
|
|
if (TargetPin->PinType.PinSubCategoryObject.IsValid())
|
|
|
|
|
Result->SetStringField(TEXT("targetPinSubtype"), TargetPin->PinType.PinSubCategoryObject->GetName());
|
|
|
|
|
|
|
|
|
|
if (!bConnected)
|
|
|
|
|
{
|
|
|
|
|
// Provide type mismatch details
|
|
|
|
|
FString Reason = FString::Printf(TEXT("Cannot connect %s (%s) to %s (%s) — types are incompatible"),
|
|
|
|
|
*SourcePinName, *SourcePin->PinType.PinCategory.ToString(),
|
|
|
|
|
*TargetPinName, *TargetPin->PinType.PinCategory.ToString());
|
|
|
|
|
return MakeErrorJson(Result, Reason);
|
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save
|
|
|
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: ConnectPins — %d/%d succeeded in '%s'"),
|
|
|
|
|
SuccessCount, Connections.Array.Num(), *Blueprint);
|
|
|
|
|
|
|
|
|
|
// Return updated node state for both source and target
|
|
|
|
|
TSharedPtr<FJsonObject> SourceNodeState = SerializeNode(SourceNode);
|
|
|
|
|
TSharedPtr<FJsonObject> TargetNodeState = SerializeNode(TargetNode);
|
|
|
|
|
if (SourceNodeState.IsValid())
|
|
|
|
|
Result->SetObjectField(TEXT("updatedSourceNode"), SourceNodeState);
|
|
|
|
|
if (TargetNodeState.IsValid())
|
|
|
|
|
Result->SetObjectField(TEXT("updatedTargetNode"), TargetNodeState);
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Connection %s, save %s"),
|
|
|
|
|
bConnected ? TEXT("succeeded") : TEXT("failed"),
|
|
|
|
|
bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
|
|
|
Result->SetNumberField(TEXT("successCount"), SuccessCount);
|
|
|
|
|
Result->SetNumberField(TEXT("totalCount"), Connections.Array.Num());
|
|
|
|
|
Result->SetArrayField(TEXT("results"), Results);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// HandleDisconnectPin — break connections on a pin
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void FBlueprintMCPServer::HandleDisconnectPin(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
// disconnect_pin is now handled by UMCPHandler_DisconnectPin (new-style registry)
|
|
|
|
|
|
|
|
|
|
void UMCPHandler_DisconnectPin::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
|
|
|
FString NodeId = Json->GetStringField(TEXT("nodeId"));
|
|
|
|
|
FString PinName = Json->GetStringField(TEXT("pinName"));
|
|
|
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
|
|
|
|
|
|
if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || PinName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, pinName"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optional: specific target to disconnect from
|
|
|
|
|
FString TargetNodeId = Json->GetStringField(TEXT("targetNodeId"));
|
|
|
|
|
FString TargetPinName = Json->GetStringField(TEXT("targetPinName"));
|
|
|
|
|
|
|
|
|
|
// Load Blueprint
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
|
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, LoadError);
|
|
|
|
|
return Helper->MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find source node
|
|
|
|
|
UEdGraphNode* Node = FindNodeByGuid(BP, NodeId);
|
|
|
|
|
if (!Node)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
}
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> Results;
|
|
|
|
|
int32 SuccessCount = 0;
|
|
|
|
|
int32 TotalDisconnected = 0;
|
|
|
|
|
|
|
|
|
|
// Find pin
|
|
|
|
|
UEdGraphPin* Pin = Node->FindPin(FName(*PinName));
|
|
|
|
|
if (!Pin)
|
|
|
|
|
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId));
|
|
|
|
|
}
|
|
|
|
|
TSharedRef<FJsonObject> EntryResult = MakeShared<FJsonObject>();
|
|
|
|
|
Results.Add(MakeShared<FJsonValueObject>(EntryResult));
|
|
|
|
|
|
|
|
|
|
int32 DisconnectedCount = 0;
|
|
|
|
|
|
|
|
|
|
if (!TargetNodeId.IsEmpty() && !TargetPinName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
// Disconnect a single specific link
|
|
|
|
|
UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId);
|
|
|
|
|
if (!TargetNode)
|
|
|
|
|
FDisconnectPinEntry Entry;
|
|
|
|
|
FString PopulateError = Helper->PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal);
|
|
|
|
|
if (!PopulateError.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId));
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), PopulateError);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName));
|
|
|
|
|
if (!TargetPin)
|
|
|
|
|
EntryResult->SetStringField(TEXT("nodeId"), Entry.NodeId);
|
|
|
|
|
EntryResult->SetStringField(TEXT("pinName"), Entry.PinName);
|
|
|
|
|
|
|
|
|
|
UEdGraphNode* Node = Helper->FindNodeByGuid(BP, Entry.NodeId);
|
|
|
|
|
if (!Node)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"),
|
|
|
|
|
*TargetPinName, *TargetNodeId));
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Node '%s' not found"), *Entry.NodeId));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Pin->LinkedTo.Contains(TargetPin))
|
|
|
|
|
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.NodeId));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 DisconnectedCount = 0;
|
|
|
|
|
|
|
|
|
|
if (!Entry.TargetNodeId.IsEmpty() && !Entry.TargetPinName.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
UEdGraphNode* TargetNode = Helper->FindNodeByGuid(BP, Entry.TargetNodeId);
|
|
|
|
|
if (!TargetNode)
|
|
|
|
|
{
|
|
|
|
|
EntryResult->SetStringField(TEXT("error"), FString::Printf(TEXT("Target node '%s' not found"), *Entry.TargetNodeId));
|
|
|
|
|
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.TargetNodeId));
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("The specified pins are not connected to each other"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Disconnect all links on this pin
|
|
|
|
|
DisconnectedCount = Pin->LinkedTo.Num();
|
|
|
|
|
if (DisconnectedCount > 0)
|
|
|
|
|
{
|
|
|
|
|
Pin->BreakAllPinLinks(true);
|
|
|
|
|
DisconnectedCount = Pin->LinkedTo.Num();
|
|
|
|
|
if (DisconnectedCount > 0)
|
|
|
|
|
{
|
|
|
|
|
Pin->BreakAllPinLinks(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EntryResult->SetBoolField(TEXT("success"), true);
|
|
|
|
|
EntryResult->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount);
|
|
|
|
|
SuccessCount++;
|
|
|
|
|
TotalDisconnected += DisconnectedCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Disconnected %d link(s) from %s.%s"),
|
|
|
|
|
DisconnectedCount, *Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *PinName);
|
|
|
|
|
|
|
|
|
|
// Save
|
|
|
|
|
bool bSaved = false;
|
|
|
|
|
if (DisconnectedCount > 0)
|
|
|
|
|
if (TotalDisconnected > 0)
|
|
|
|
|
{
|
|
|
|
|
bSaved = SaveBlueprintPackage(BP);
|
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DisconnectPin — %d/%d succeeded, %d links broken in '%s'"),
|
|
|
|
|
SuccessCount, Disconnections.Array.Num(), TotalDisconnected, *Blueprint);
|
|
|
|
|
|
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
|
|
|
Result->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount);
|
|
|
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
|
|
|
Result->SetNumberField(TEXT("successCount"), SuccessCount);
|
|
|
|
|
Result->SetNumberField(TEXT("totalCount"), Disconnections.Array.Num());
|
|
|
|
|
Result->SetNumberField(TEXT("totalDisconnected"), TotalDisconnected);
|
|
|
|
|
Result->SetArrayField(TEXT("results"), Results);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
@@ -1128,34 +1121,28 @@ void FBlueprintMCPServer::HandleChangeStructNodeType(const FJsonObject* Json, FJ
|
|
|
|
|
// HandleDeleteNode — remove a node from a blueprint graph
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void FBlueprintMCPServer::HandleDeleteNode(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
// delete_node is now handled by UMCPHandler_DeleteNode (new-style registry)
|
|
|
|
|
|
|
|
|
|
void UMCPHandler_DeleteNode::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
|
|
|
FString NodeId = Json->GetStringField(TEXT("nodeId"));
|
|
|
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
|
|
|
|
|
|
if (BlueprintName.IsEmpty() || NodeId.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load Blueprint
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
|
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, LoadError);
|
|
|
|
|
return Helper->MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find node
|
|
|
|
|
UEdGraph* Graph = nullptr;
|
|
|
|
|
UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph);
|
|
|
|
|
UEdGraphNode* Node = Helper->FindNodeByGuid(BP, NodeId, &Graph);
|
|
|
|
|
if (!Node)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
}
|
|
|
|
|
if (!Graph)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Graph not found for node '%s'"), *NodeId));
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(TEXT("Graph not found for node '%s'"), *NodeId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString NodeClass = Node->GetClass()->GetName();
|
|
|
|
|
@@ -1167,7 +1154,7 @@ void FBlueprintMCPServer::HandleDeleteNode(const FJsonObject* Json, FJsonObject*
|
|
|
|
|
// without recreating the entire function/event.
|
|
|
|
|
if (Cast<UK2Node_FunctionEntry>(Node))
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Cannot delete FunctionEntry node '%s' in graph '%s'. ")
|
|
|
|
|
TEXT("This is the root node of the function — removing it would leave an empty, uncompilable graph. ")
|
|
|
|
|
TEXT("To remove the entire function, delete it from the Blueprint editor."),
|
|
|
|
|
@@ -1175,41 +1162,35 @@ void FBlueprintMCPServer::HandleDeleteNode(const FJsonObject* Json, FJsonObject*
|
|
|
|
|
}
|
|
|
|
|
if (Cast<UK2Node_Event>(Node))
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Cannot delete event entry node '%s' in graph '%s'. ")
|
|
|
|
|
TEXT("This is the root node of the event handler — removing it would leave an empty, uncompilable graph."),
|
|
|
|
|
*NodeTitle, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
if (Cast<UK2Node_CustomEvent>(Node))
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(
|
|
|
|
|
TEXT("Cannot delete CustomEvent entry node '%s' in graph '%s'. ")
|
|
|
|
|
TEXT("This is the root node of the custom event — removing it would leave an empty, uncompilable graph."),
|
|
|
|
|
*NodeTitle, *GraphName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting node '%s' (%s) from graph '%s' in '%s'"),
|
|
|
|
|
*NodeId, *NodeTitle, *GraphName, *BlueprintName);
|
|
|
|
|
*NodeId, *NodeTitle, *GraphName, *Blueprint);
|
|
|
|
|
|
|
|
|
|
// Disconnect all pins
|
|
|
|
|
Node->BreakAllNodeLinks();
|
|
|
|
|
|
|
|
|
|
// Remove the node from the graph
|
|
|
|
|
Graph->RemoveNode(Node);
|
|
|
|
|
|
|
|
|
|
// Save (which also compiles)
|
|
|
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Node deleted, save %s"),
|
|
|
|
|
bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Node deleted"));
|
|
|
|
|
|
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
|
|
|
Result->SetStringField(TEXT("nodeId"), NodeId);
|
|
|
|
|
Result->SetStringField(TEXT("nodeClass"), NodeClass);
|
|
|
|
|
Result->SetStringField(TEXT("nodeTitle"), NodeTitle);
|
|
|
|
|
Result->SetStringField(TEXT("graph"), GraphName);
|
|
|
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
@@ -2221,31 +2202,27 @@ void UMCPHandler_DuplicateNodes::Handle(const FJsonObject* Json, FJsonObject* Re
|
|
|
|
|
// HandleGetNodeComment — read a node's comment text
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void FBlueprintMCPServer::HandleGetNodeComment(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
|
|
|
FString NodeId = Json->GetStringField(TEXT("nodeId"));
|
|
|
|
|
// get_node_comment is now handled by UMCPHandler_GetNodeComment (new-style registry)
|
|
|
|
|
|
|
|
|
|
if (BlueprintName.IsEmpty() || NodeId.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId"));
|
|
|
|
|
}
|
|
|
|
|
void UMCPHandler_GetNodeComment::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
|
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, LoadError);
|
|
|
|
|
return Helper->MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UEdGraphNode* Node = FindNodeByGuid(BP, NodeId);
|
|
|
|
|
UEdGraphNode* Node = Helper->FindNodeByGuid(BP, NodeId);
|
|
|
|
|
if (!Node)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
|
|
|
Result->SetStringField(TEXT("nodeId"), NodeId);
|
|
|
|
|
Result->SetStringField(TEXT("comment"), Node->NodeComment);
|
|
|
|
|
Result->SetBoolField(TEXT("commentBubbleVisible"), Node->bCommentBubbleVisible);
|
|
|
|
|
@@ -2255,34 +2232,23 @@ void FBlueprintMCPServer::HandleGetNodeComment(const FJsonObject* Json, FJsonObj
|
|
|
|
|
// HandleSetNodeComment — set a node's comment text
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void FBlueprintMCPServer::HandleSetNodeComment(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
// set_node_comment is now handled by UMCPHandler_SetNodeComment (new-style registry)
|
|
|
|
|
|
|
|
|
|
void UMCPHandler_SetNodeComment::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
|
|
|
{
|
|
|
|
|
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
|
|
|
|
|
FString NodeId = Json->GetStringField(TEXT("nodeId"));
|
|
|
|
|
|
|
|
|
|
if (BlueprintName.IsEmpty() || NodeId.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Json->HasField(TEXT("comment")))
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, TEXT("Missing required field: comment"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString Comment = Json->GetStringField(TEXT("comment"));
|
|
|
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
|
|
|
|
|
|
FString LoadError;
|
|
|
|
|
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
|
|
|
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
|
|
|
if (!BP)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, LoadError);
|
|
|
|
|
return Helper->MakeErrorJson(Result, LoadError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UEdGraphNode* Node = FindNodeByGuid(BP, NodeId);
|
|
|
|
|
UEdGraphNode* Node = Helper->FindNodeByGuid(BP, NodeId);
|
|
|
|
|
if (!Node)
|
|
|
|
|
{
|
|
|
|
|
return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
return Helper->MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FString OldComment = Node->NodeComment;
|
|
|
|
|
@@ -2296,17 +2262,15 @@ void FBlueprintMCPServer::HandleSetNodeComment(const FJsonObject* Json, FJsonObj
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
|
|
|
|
|
bool bSaved = SaveBlueprintPackage(BP);
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s', save %s"),
|
|
|
|
|
*NodeId, *BlueprintName, bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s'"),
|
|
|
|
|
*NodeId, *Blueprint);
|
|
|
|
|
|
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
|
|
|
Result->SetStringField(TEXT("nodeId"), NodeId);
|
|
|
|
|
Result->SetStringField(TEXT("oldComment"), OldComment);
|
|
|
|
|
Result->SetStringField(TEXT("newComment"), Comment);
|
|
|
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
|