Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/MCPHandlers_Interfaces.h

304 lines
9.2 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.h"
#include "MCPServer.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "UObject/UObjectIterator.h"
#include "MCPHandlers_Interfaces.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCPHandler_ListBlueprintInterfaces : 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 Blueprint Interfaces implemented by a Blueprint, "
"including their function graphs.");
}
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>> InterfacesArr;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (!IfaceDesc.Interface)
{
continue;
}
TSharedRef<FJsonObject> IfaceObj = MakeShared<FJsonObject>();
IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName());
IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName());
TArray<TSharedPtr<FJsonValue>> FuncArr;
for (const UEdGraph* Graph : IfaceDesc.Graphs)
{
if (Graph)
{
FuncArr.Add(MakeShared<FJsonValueString>(Graph->GetName()));
}
}
IfaceObj->SetArrayField(TEXT("functions"), FuncArr);
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
}
Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
Result->SetArrayField(TEXT("interfaces"), InterfacesArr);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCPHandler_AddBlueprintInterface : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Interface name (e.g. 'BPI_MyInterface') or native UInterface class name"))
FString InterfaceName;
virtual FString GetDescription() const override
{
return TEXT("Add a Blueprint Interface implementation to a Blueprint. "
"Creates stub function graphs for each interface function.");
}
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();
// Resolve the interface class
UClass* InterfaceClass = nullptr;
// Strategy 1: Search loaded UInterface classes by name
for (TObjectIterator<UClass> It; It; ++It)
{
if (!It->IsChildOf(UInterface::StaticClass()))
{
continue;
}
FString ClassName = It->GetName();
// Match by class name (e.g. "BPI_Foo_C") or by trimmed name (e.g. "BPI_Foo")
if (ClassName.Equals(InterfaceName, ESearchCase::IgnoreCase))
{
InterfaceClass = *It;
break;
}
// Strip the generated "_C" suffix for comparison
FString TrimmedName = ClassName;
if (TrimmedName.EndsWith(TEXT("_C")))
{
TrimmedName = TrimmedName.LeftChop(2);
}
if (TrimmedName.Equals(InterfaceName, ESearchCase::IgnoreCase))
{
InterfaceClass = *It;
break;
}
}
// Strategy 2: Try loading as a Blueprint Interface asset
if (!InterfaceClass)
{
MCPAssets<UBlueprint> IfaceAssets;
if (!IfaceAssets.Exact(InterfaceName).AllContent().Errors(Result).ETwo().Load()) return;
if (!IfaceAssets.Objects().IsEmpty())
{
UClass* GenClass = IfaceAssets.Object()->GeneratedClass;
if (GenClass && GenClass->IsChildOf(UInterface::StaticClass()))
InterfaceClass = GenClass;
}
}
if (!InterfaceClass)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' not found. Provide a Blueprint Interface asset name (e.g. 'BPI_MyInterface') or a native UInterface class name."),
*InterfaceName));
}
// Check for duplicates
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (IfaceDesc.Interface == InterfaceClass)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is already implemented by Blueprint '%s'"),
*InterfaceName, *Blueprint));
}
}
FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"),
*InterfaceClass->GetName(), *Blueprint);
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded)
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"),
*InterfaceName, *Blueprint));
}
// Collect stub function graph names from the newly added interface entry
TArray<FString> AddedFunctions;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (IfaceDesc.Interface == InterfaceClass)
{
for (const UEdGraph* Graph : IfaceDesc.Graphs)
{
if (Graph)
{
AddedFunctions.Add(Graph->GetName());
}
}
break;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"),
*InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num());
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
TArray<TSharedPtr<FJsonValue>> FuncArr;
for (const FString& FuncName : AddedFunctions)
{
FuncArr.Add(MakeShared<FJsonValueString>(FuncName));
}
Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr);
}
};
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCPHandler_RemoveBlueprintInterface : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Interface name to remove"))
FString InterfaceName;
UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions"))
bool PreserveFunctions = false;
virtual FString GetDescription() const override
{
return TEXT("Remove a Blueprint Interface implementation from a Blueprint. "
"Optionally preserve the function graphs as regular functions.");
}
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();
// Find the interface in ImplementedInterfaces by name (case-insensitive)
UClass* FoundInterface = nullptr;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (!IfaceDesc.Interface)
{
continue;
}
FString ClassName = IfaceDesc.Interface->GetName();
if (ClassName.Equals(InterfaceName, ESearchCase::IgnoreCase))
{
FoundInterface = IfaceDesc.Interface;
break;
}
// Strip "_C" suffix for comparison
FString TrimmedName = ClassName;
if (TrimmedName.EndsWith(TEXT("_C")))
{
TrimmedName = TrimmedName.LeftChop(2);
}
if (TrimmedName.Equals(InterfaceName, ESearchCase::IgnoreCase))
{
FoundInterface = IfaceDesc.Interface;
break;
}
}
if (!FoundInterface)
{
// Build helpful error with list of implemented interfaces
TArray<TSharedPtr<FJsonValue>> IfaceList;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (IfaceDesc.Interface)
{
IfaceList.Add(MakeShared<FJsonValueString>(IfaceDesc.Interface->GetName()));
}
}
MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is not implemented by Blueprint '%s'"),
*InterfaceName, *Blueprint));
Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
return;
}
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"),
*FoundInterface->GetName(), *Blueprint, PreserveFunctions ? TEXT("true") : TEXT("false"));
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"),
*FoundInterface->GetName(), *Blueprint);
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
}
};