#include "BlueprintMCPHandlers_Interfaces.h" #include "BlueprintMCPServer.h" #include "MCPUtils.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "Kismet2/BlueprintEditorUtils.h" #include "UObject/UObjectIterator.h" // ============================================================ // ListInterfaces // ============================================================ void UMCPHandler_ListBlueprintInterfaces::Handle(const FJsonObject* Json, FJsonObject* Result) { MCPHelper* Helper = MCPHelper::Get(); FString LoadError; UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError); if (!BP) { return MCPUtils::MakeErrorJson(Result, LoadError); } TArray> InterfacesArr; for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) { if (!IfaceDesc.Interface) { continue; } TSharedRef IfaceObj = MakeShared(); IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName()); IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName()); TArray> FuncArr; for (const UEdGraph* Graph : IfaceDesc.Graphs) { if (Graph) { FuncArr.Add(MakeShared(Graph->GetName())); } } IfaceObj->SetArrayField(TEXT("functions"), FuncArr); InterfacesArr.Add(MakeShared(IfaceObj)); } Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetNumberField(TEXT("count"), InterfacesArr.Num()); Result->SetArrayField(TEXT("interfaces"), InterfacesArr); } // ============================================================ // AddInterface // ============================================================ void UMCPHandler_AddBlueprintInterface::Handle(const FJsonObject* Json, FJsonObject* Result) { MCPHelper* Helper = MCPHelper::Get(); FString LoadError; UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError); if (!BP) { return MCPUtils::MakeErrorJson(Result, LoadError); } // Resolve the interface class UClass* InterfaceClass = nullptr; // Strategy 1: Search loaded UInterface classes by name for (TObjectIterator 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) { FString IfaceLoadError; UBlueprint* IfaceBP = Helper->LoadBlueprintByName(InterfaceName, IfaceLoadError); if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass())) { InterfaceClass = IfaceBP->GeneratedClass; } } 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 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->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName()); TArray> FuncArr; for (const FString& FuncName : AddedFunctions) { FuncArr.Add(MakeShared(FuncName)); } Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr); } // ============================================================ // RemoveInterface // ============================================================ void UMCPHandler_RemoveBlueprintInterface::Handle(const FJsonObject* Json, FJsonObject* Result) { MCPHelper* Helper = MCPHelper::Get(); FString LoadError; UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError); if (!BP) { return MCPUtils::MakeErrorJson(Result, LoadError); } // 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> IfaceList; for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) { if (IfaceDesc.Interface) { IfaceList.Add(MakeShared(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->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), Blueprint); Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions); }