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

244 lines
8.2 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.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 "MCPHandlers_Dispatchers.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="add_event_dispatcher"))
class UMCPHandler_AddEventDispatcher : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name for the new event dispatcher"))
FString DispatcherName;
UPROPERTY(meta=(Optional, Description="Array of parameter objects, each with 'name' and 'type' fields"))
FMCPJsonArray Parameters;
virtual FString GetDescription() const override
{
return TEXT("Create a new multicast event dispatcher on a Blueprint, optionally with parameters.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
// Load Blueprint
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, 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, *Blueprint));
}
}
// 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, *Blueprint));
}
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding event dispatcher '%s' to Blueprint '%s'"),
*DispatcherName, *Blueprint);
// 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>> AddedParamsJson;
if (Parameters.Array.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 : Parameters.Array)
{
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;
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, Result))
return;
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, *Blueprint, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false"));
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("dispatcherName"), DispatcherName);
Result->SetArrayField(TEXT("parameters"), AddedParamsJson);
Result->SetBoolField(TEXT("saved"), bSaved);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(ToolName="list_event_dispatchers"))
class UMCPHandler_ListEventDispatchers : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
virtual FString GetDescription() const override
{
return TEXT("List all event dispatchers on a Blueprint, including their parameter signatures.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
FString LoadError;
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, 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"), Blueprint);
Result->SetNumberField(TEXT("count"), DispatchersArr.Num());
Result->SetArrayField(TEXT("dispatchers"), DispatchersArr);
}
};