Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Dispatchers.cpp

232 lines
7.9 KiB
C++

#include "MCPAssetFinder.h"
#include "MCPServer.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonWriter.h"
#include "Serialization/JsonSerializer.h"
// ============================================================
// HandleAddEventDispatcher — create a multicast delegate on a Blueprint
// ============================================================
void FBlueprintMCPServer::HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result)
{
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString DispatcherName = Json->GetStringField(TEXT("dispatcherName"));
if (BlueprintName.IsEmpty() || DispatcherName.IsEmpty())
{
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, dispatcherName"));
}
// Load Blueprint
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
FName DispatcherFName(*DispatcherName);
// Check for name uniqueness against existing variables
for (const FBPVariableDescription& Var : BP->NewVariables)
{
if (Var.VarName == DispatcherFName)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("A variable or dispatcher named '%s' already exists in Blueprint '%s'"),
*DispatcherName, *BlueprintName));
}
}
// Check against existing graphs (functions, macros, etc.)
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* Existing : AllGraphs)
{
if (Existing && Existing->GetName().Equals(DispatcherName, ESearchCase::IgnoreCase))
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("A graph named '%s' already exists in Blueprint '%s'"),
*DispatcherName, *BlueprintName));
}
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding event dispatcher '%s' to Blueprint '%s'"),
*DispatcherName, *BlueprintName);
// Step 1: Add a member variable with PC_MCDelegate pin type
FEdGraphPinType DelegateType;
DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate;
bool bVarAdded = FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType);
if (!bVarAdded)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Failed to add delegate variable for '%s'"), *DispatcherName));
}
// Step 2: Create the signature graph
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
UEdGraph* SigGraph = FBlueprintEditorUtils::CreateNewGraph(BP, DispatcherFName,
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!SigGraph)
{
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create delegate signature graph"));
}
K2Schema->CreateDefaultNodesForGraph(*SigGraph);
K2Schema->CreateFunctionGraphTerminators(*SigGraph, static_cast<UClass*>(nullptr));
K2Schema->AddExtraFunctionFlags(SigGraph, FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_Public);
K2Schema->MarkFunctionEntryAsEditable(SigGraph, true);
BP->DelegateSignatureGraphs.Add(SigGraph);
// Step 3: Add parameters if provided
TArray<TSharedPtr<FJsonValue>> ParamsArr;
if (Json->HasField(TEXT("parameters")))
{
ParamsArr = Json->GetArrayField(TEXT("parameters"));
}
TArray<TSharedPtr<FJsonValue>> AddedParamsJson;
if (ParamsArr.Num() > 0)
{
// Find the entry node in the signature graph
UK2Node_EditablePinBase* EntryNode = nullptr;
for (UEdGraphNode* Node : SigGraph->Nodes)
{
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
{
EntryNode = FE;
break;
}
}
if (!EntryNode)
{
// Still save what we have
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
MCPUtils::SaveBlueprintPackage(BP);
return MCPUtils::MakeErrorJson(Result, TEXT("Event dispatcher created but entry node not found — parameters could not be added"));
}
for (const TSharedPtr<FJsonValue>& ParamVal : ParamsArr)
{
if (!ParamVal.IsValid() || ParamVal->Type != EJson::Object) continue;
TSharedPtr<FJsonObject> ParamObj = ParamVal->AsObject();
FString ParamName = ParamObj->GetStringField(TEXT("name"));
FString ParamType = ParamObj->GetStringField(TEXT("type"));
if (ParamName.IsEmpty() || ParamType.IsEmpty()) continue;
FEdGraphPinType PinType;
FString TypeError;
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, TypeError))
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Parameter '%s': %s"), *ParamName, *TypeError));
}
EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output);
TSharedRef<FJsonObject> ParamJson = MakeShared<FJsonObject>();
ParamJson->SetStringField(TEXT("name"), ParamName);
ParamJson->SetStringField(TEXT("type"), ParamType);
AddedParamsJson.Add(MakeShared<FJsonValueObject>(ParamJson));
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added event dispatcher '%s' to '%s' with %d params (saved: %s)"),
*DispatcherName, *BlueprintName, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("dispatcherName"), DispatcherName);
Result->SetArrayField(TEXT("parameters"), AddedParamsJson);
Result->SetBoolField(TEXT("saved"), bSaved);
}
// ============================================================
// HandleListEventDispatchers — list all event dispatchers on a Blueprint
// ============================================================
void FBlueprintMCPServer::HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result)
{
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MCPUtils::MakeErrorJson(Result, LoadError);
}
TSet<FName> DelegateNameSet;
FBlueprintEditorUtils::GetDelegateNameList(BP, DelegateNameSet);
TArray<TSharedPtr<FJsonValue>> DispatchersArr;
for (const FName& DelegateName : DelegateNameSet)
{
TSharedRef<FJsonObject> DispObj = MakeShared<FJsonObject>();
DispObj->SetStringField(TEXT("name"), DelegateName.ToString());
// Get parameter info from the signature graph
TArray<TSharedPtr<FJsonValue>> ParamsArr;
UEdGraph* SigGraph = FBlueprintEditorUtils::GetDelegateSignatureGraphByName(BP, DelegateName);
if (SigGraph)
{
for (UEdGraphNode* Node : SigGraph->Nodes)
{
UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node);
if (!FE) continue;
for (const TSharedPtr<FUserPinInfo>& PinInfo : FE->UserDefinedPins)
{
if (!PinInfo.IsValid()) continue;
TSharedRef<FJsonObject> ParamObj = MakeShared<FJsonObject>();
ParamObj->SetStringField(TEXT("name"), PinInfo->PinName.ToString());
// Build a human-readable type name from the pin type
FString TypeStr = PinInfo->PinType.PinCategory.ToString();
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
{
TypeStr = PinInfo->PinType.PinSubCategoryObject->GetName();
}
ParamObj->SetStringField(TEXT("type"), TypeStr);
ParamsArr.Add(MakeShared<FJsonValueObject>(ParamObj));
}
break; // only need the first entry node
}
}
DispObj->SetArrayField(TEXT("parameters"), ParamsArr);
DispatchersArr.Add(MakeShared<FJsonValueObject>(DispObj));
}
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetNumberField(TEXT("count"), DispatchersArr.Num());
Result->SetArrayField(TEXT("dispatchers"), DispatchersArr);
}