Ported a few MCP handlers to the new registration system

This commit is contained in:
2026-03-06 00:33:07 -05:00
parent cf1c2bf8cf
commit d85b62027c
8 changed files with 166 additions and 114 deletions

View File

@@ -1,55 +1,36 @@
#include "BlueprintMCPHandlers_DiffBlueprints.h"
#include "BlueprintMCPServer.h" #include "BlueprintMCPServer.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "K2Node_BreakStruct.h"
#include "K2Node_MakeStruct.h"
#include "K2Node_CallFunction.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonWriter.h"
#include "Serialization/JsonSerializer.h"
// ============================================================ void UMCPHandler_DiffBlueprints::Handle(const FJsonObject* Json, FJsonObject* Result)
// HandleDiffBlueprints — structural diff between two Blueprints
// ============================================================
void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result)
{ {
FString BlueprintA = Json->GetStringField(TEXT("blueprintA")); MCPHelper* Helper = MCPHelper::Get();
FString BlueprintB = Json->GetStringField(TEXT("blueprintB"));
FString GraphFilter = Json->GetStringField(TEXT("graph"));
if (BlueprintA.IsEmpty() || BlueprintB.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprintA, blueprintB"));
}
// Load both blueprints // Load both blueprints
FString LoadErrorA, LoadErrorB; FString LoadErrorA, LoadErrorB;
UBlueprint* BPA = LoadBlueprintByName(BlueprintA, LoadErrorA); UBlueprint* BPA = Helper->LoadBlueprintByName(BlueprintA, LoadErrorA);
if (!BPA) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; } if (!BPA) { Helper->MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; }
UBlueprint* BPB = LoadBlueprintByName(BlueprintB, LoadErrorB); UBlueprint* BPB = Helper->LoadBlueprintByName(BlueprintB, LoadErrorB);
if (!BPB) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; } if (!BPB) { Helper->MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; }
// Helper to gather graphs from a Blueprint // Helper to gather graphs from a Blueprint
auto GatherGraphs = [&GraphFilter](UBlueprint* BP) -> TArray<UEdGraph*> auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*>
{ {
TArray<UEdGraph*> Graphs; TArray<UEdGraph*> Graphs;
for (UEdGraph* G : BP->UbergraphPages) for (UEdGraph* G : BP->UbergraphPages)
{ {
if (!G) continue; if (!G) continue;
if (!GraphFilter.IsEmpty() && G->GetName() != GraphFilter) continue; if (!Graph.IsEmpty() && (G->GetName() != Graph)) continue;
Graphs.Add(G); Graphs.Add(G);
} }
for (UEdGraph* G : BP->FunctionGraphs) for (UEdGraph* G : BP->FunctionGraphs)
{ {
if (!G) continue; if (!G) continue;
if (!GraphFilter.IsEmpty() && G->GetName() != GraphFilter) continue; if (!Graph.IsEmpty() && (G->GetName() != Graph)) continue;
Graphs.Add(G); Graphs.Add(G);
} }
return Graphs; return Graphs;
@@ -167,7 +148,7 @@ void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObj
if (!N) continue; if (!N) continue;
for (UEdGraphPin* Pin : N->Pins) for (UEdGraphPin* Pin : N->Pins)
{ {
if (!Pin || Pin->Direction != EGPD_Output) continue; if (!Pin || (Pin->Direction != EGPD_Output)) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo) for (UEdGraphPin* Linked : Pin->LinkedTo)
{ {
if (!Linked || !Linked->GetOwningNode()) continue; if (!Linked || !Linked->GetOwningNode()) continue;
@@ -180,7 +161,7 @@ void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObj
if (!N) continue; if (!N) continue;
for (UEdGraphPin* Pin : N->Pins) for (UEdGraphPin* Pin : N->Pins)
{ {
if (!Pin || Pin->Direction != EGPD_Output) continue; if (!Pin || (Pin->Direction != EGPD_Output)) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo) for (UEdGraphPin* Linked : Pin->LinkedTo)
{ {
if (!Linked || !Linked->GetOwningNode()) continue; if (!Linked || !Linked->GetOwningNode()) continue;
@@ -205,7 +186,7 @@ void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObj
} }
} }
bool bIdentical = OnlyInA.Num() == 0 && OnlyInB.Num() == 0 && ConnsOnlyInA.Num() == 0 && ConnsOnlyInB.Num() == 0; bool bIdentical = (OnlyInA.Num() == 0) && (OnlyInB.Num() == 0) && (ConnsOnlyInA.Num() == 0) && (ConnsOnlyInB.Num() == 0);
GD->SetStringField(TEXT("status"), bIdentical ? TEXT("identical") : TEXT("different")); GD->SetStringField(TEXT("status"), bIdentical ? TEXT("identical") : TEXT("different"));
GD->SetNumberField(TEXT("nodeCountA"), GA->Nodes.Num()); GD->SetNumberField(TEXT("nodeCountA"), GA->Nodes.Num());
GD->SetNumberField(TEXT("nodeCountB"), GB->Nodes.Num()); GD->SetNumberField(TEXT("nodeCountB"), GB->Nodes.Num());

View File

@@ -1,30 +1,23 @@
#include "BlueprintMCPHandlers_Interfaces.h"
#include "BlueprintMCPServer.h" #include "BlueprintMCPServer.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonWriter.h"
#include "Serialization/JsonSerializer.h"
#include "UObject/UObjectIterator.h" #include "UObject/UObjectIterator.h"
// ============================================================ // ============================================================
// HandleListInterfaces — list implemented interfaces on a Blueprint // ListInterfaces
// ============================================================ // ============================================================
void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result) void UMCPHandler_ListInterfaces::Handle(const FJsonObject* Json, FJsonObject* Result)
{ {
FString BlueprintName = Json->GetStringField(TEXT("blueprint")); MCPHelper* Helper = MCPHelper::Get();
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError; FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP) if (!BP)
{ {
return MakeErrorJson(Result, LoadError); return Helper->MakeErrorJson(Result, LoadError);
} }
TArray<TSharedPtr<FJsonValue>> InterfacesArr; TArray<TSharedPtr<FJsonValue>> InterfacesArr;
@@ -39,7 +32,6 @@ void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObj
IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName()); IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName());
IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName()); IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName());
// Collect function graph names from the interface
TArray<TSharedPtr<FJsonValue>> FuncArr; TArray<TSharedPtr<FJsonValue>> FuncArr;
for (const UEdGraph* Graph : IfaceDesc.Graphs) for (const UEdGraph* Graph : IfaceDesc.Graphs)
{ {
@@ -53,30 +45,24 @@ void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObj
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj)); InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
} }
Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), InterfacesArr.Num()); Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
Result->SetArrayField(TEXT("interfaces"), InterfacesArr); Result->SetArrayField(TEXT("interfaces"), InterfacesArr);
} }
// ============================================================ // ============================================================
// HandleAddInterface — add a Blueprint Interface implementation // AddInterface
// ============================================================ // ============================================================
void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObject* Result) void UMCPHandler_AddInterface::Handle(const FJsonObject* Json, FJsonObject* Result)
{ {
FString BlueprintName = Json->GetStringField(TEXT("blueprint")); MCPHelper* Helper = MCPHelper::Get();
FString InterfaceName = Json->GetStringField(TEXT("interfaceName"));
if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName"));
}
FString LoadError; FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP) if (!BP)
{ {
return MakeErrorJson(Result, LoadError); return Helper->MakeErrorJson(Result, LoadError);
} }
// Resolve the interface class // Resolve the interface class
@@ -114,7 +100,7 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
if (!InterfaceClass) if (!InterfaceClass)
{ {
FString IfaceLoadError; FString IfaceLoadError;
UBlueprint* IfaceBP = LoadBlueprintByName(InterfaceName, IfaceLoadError); UBlueprint* IfaceBP = Helper->LoadBlueprintByName(InterfaceName, IfaceLoadError);
if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass())) if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass()))
{ {
InterfaceClass = IfaceBP->GeneratedClass; InterfaceClass = IfaceBP->GeneratedClass;
@@ -123,7 +109,7 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
if (!InterfaceClass) if (!InterfaceClass)
{ {
return MakeErrorJson(Result, FString::Printf( return Helper->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."), TEXT("Interface '%s' not found. Provide a Blueprint Interface asset name (e.g. 'BPI_MyInterface') or a native UInterface class name."),
*InterfaceName)); *InterfaceName));
} }
@@ -133,24 +119,23 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
{ {
if (IfaceDesc.Interface == InterfaceClass) if (IfaceDesc.Interface == InterfaceClass)
{ {
return MakeErrorJson(Result, FString::Printf( return Helper->MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is already implemented by Blueprint '%s'"), TEXT("Interface '%s' is already implemented by Blueprint '%s'"),
*InterfaceName, *BlueprintName)); *InterfaceName, *Blueprint));
} }
} }
// Get interface class path for the non-deprecated overload
FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName(); FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"),
*InterfaceClass->GetName(), *BlueprintName); *InterfaceClass->GetName(), *Blueprint);
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath); bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded) if (!bAdded)
{ {
return MakeErrorJson(Result, FString::Printf( return Helper->MakeErrorJson(Result, FString::Printf(
TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"), TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"),
*InterfaceName, *BlueprintName)); *InterfaceName, *Blueprint));
} }
// Collect stub function graph names from the newly added interface entry // Collect stub function graph names from the newly added interface entry
@@ -171,13 +156,13 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
} }
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = SaveBlueprintPackage(BP); if (Save) Helper->SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs, saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"),
*InterfaceClass->GetName(), *BlueprintName, AddedFunctions.Num(), bSaved ? TEXT("true") : TEXT("false")); *InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num());
Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName()); Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
@@ -187,34 +172,21 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
FuncArr.Add(MakeShared<FJsonValueString>(FuncName)); FuncArr.Add(MakeShared<FJsonValueString>(FuncName));
} }
Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr); Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr);
Result->SetBoolField(TEXT("saved"), bSaved);
} }
// ============================================================ // ============================================================
// HandleRemoveInterface — remove a Blueprint Interface implementation // RemoveInterface
// ============================================================ // ============================================================
void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result) void UMCPHandler_RemoveInterface::Handle(const FJsonObject* Json, FJsonObject* Result)
{ {
FString BlueprintName = Json->GetStringField(TEXT("blueprint")); MCPHelper* Helper = MCPHelper::Get();
FString InterfaceName = Json->GetStringField(TEXT("interfaceName"));
if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName"));
}
bool bPreserveFunctions = false;
if (Json->HasField(TEXT("preserveFunctions")))
{
bPreserveFunctions = Json->GetBoolField(TEXT("preserveFunctions"));
}
FString LoadError; FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP) if (!BP)
{ {
return MakeErrorJson(Result, LoadError); return Helper->MakeErrorJson(Result, LoadError);
} }
// Find the interface in ImplementedInterfaces by name (case-insensitive) // Find the interface in ImplementedInterfaces by name (case-insensitive)
@@ -257,9 +229,9 @@ void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonOb
} }
} }
MakeErrorJson(Result, FString::Printf( Helper->MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is not implemented by Blueprint '%s'"), TEXT("Interface '%s' is not implemented by Blueprint '%s'"),
*InterfaceName, *BlueprintName)); *InterfaceName, *Blueprint));
Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList); Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
return; return;
} }
@@ -267,19 +239,18 @@ void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonOb
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"),
*FoundInterface->GetName(), *BlueprintName, bPreserveFunctions ? TEXT("true") : TEXT("false")); *FoundInterface->GetName(), *Blueprint, PreserveFunctions ? TEXT("true") : TEXT("false"));
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, bPreserveFunctions); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = SaveBlueprintPackage(BP); if (Save) Helper->SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s' (saved: %s)"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"),
*FoundInterface->GetName(), *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); *FoundInterface->GetName(), *Blueprint);
Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
Result->SetBoolField(TEXT("preservedFunctions"), bPreserveFunctions); Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions);
Result->SetBoolField(TEXT("saved"), bSaved);
} }

View File

@@ -2667,6 +2667,7 @@ void UMCPHandler_SpawnNode::Handle(const FJsonObject* Json, FJsonObject* Result)
} }
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
if (Save) Helper->SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Spawned node '%s' (class %s) via action '%s' in graph '%s' of '%s'"), UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Spawned node '%s' (class %s) via action '%s' in graph '%s' of '%s'"),
*NewNode->NodeGuid.ToString(), *NewNode->NodeGuid.ToString(),

View File

@@ -684,11 +684,11 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
// Interface tools // Interface tools
Router->BindRoute(FHttpPath(TEXT("/api/add-interface")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/add-interface")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("addInterface"))); QueuedHandler(TEXT("add_interface")));
Router->BindRoute(FHttpPath(TEXT("/api/remove-interface")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/remove-interface")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("removeInterface"))); QueuedHandler(TEXT("remove_interface")));
Router->BindRoute(FHttpPath(TEXT("/api/list-interfaces")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/list-interfaces")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("listInterfaces"))); QueuedHandler(TEXT("list_interfaces")));
// Event Dispatcher tools // Event Dispatcher tools
Router->BindRoute(FHttpPath(TEXT("/api/add-event-dispatcher")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/add-event-dispatcher")), EHttpServerRequestVerbs::VERB_POST,
@@ -720,7 +720,7 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
Router->BindRoute(FHttpPath(TEXT("/api/analyze-rebuild-impact")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/analyze-rebuild-impact")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("analyzeRebuildImpact"))); QueuedHandler(TEXT("analyzeRebuildImpact")));
Router->BindRoute(FHttpPath(TEXT("/api/diff-blueprints")), EHttpServerRequestVerbs::VERB_POST, Router->BindRoute(FHttpPath(TEXT("/api/diff-blueprints")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("diffBlueprints"))); QueuedHandler(TEXT("diff_blueprints")));
// Material read-only tools (Phase 1) // Material read-only tools (Phase 1)
Router->BindRoute(FHttpPath(TEXT("/api/materials")), EHttpServerRequestVerbs::VERB_GET, Router->BindRoute(FHttpPath(TEXT("/api/materials")), EHttpServerRequestVerbs::VERB_GET,
@@ -997,8 +997,8 @@ void FBlueprintMCPServer::RegisterHandlers()
TEXT("addVariable"), TEXT("addVariable"),
TEXT("removeVariable"), TEXT("removeVariable"),
TEXT("setVariableMetadata"), TEXT("setVariableMetadata"),
TEXT("addInterface"), TEXT("add_interface"),
TEXT("removeInterface"), TEXT("remove_interface"),
TEXT("addEventDispatcher"), TEXT("addEventDispatcher"),
TEXT("addFunctionParameter"), TEXT("addFunctionParameter"),
TEXT("addComponent"), TEXT("addComponent"),
@@ -1079,9 +1079,7 @@ void FBlueprintMCPServer::RegisterHandlers()
H(TEXT("addVariable"), &FBlueprintMCPServer::HandleAddVariable); H(TEXT("addVariable"), &FBlueprintMCPServer::HandleAddVariable);
H(TEXT("removeVariable"), &FBlueprintMCPServer::HandleRemoveVariable); H(TEXT("removeVariable"), &FBlueprintMCPServer::HandleRemoveVariable);
H(TEXT("setVariableMetadata"), &FBlueprintMCPServer::HandleSetVariableMetadata); H(TEXT("setVariableMetadata"), &FBlueprintMCPServer::HandleSetVariableMetadata);
H(TEXT("addInterface"), &FBlueprintMCPServer::HandleAddInterface); // add_interface, remove_interface, list_interfaces now handled by new-style registry
H(TEXT("removeInterface"), &FBlueprintMCPServer::HandleRemoveInterface);
H(TEXT("listInterfaces"), &FBlueprintMCPServer::HandleListInterfaces);
H(TEXT("addEventDispatcher"), &FBlueprintMCPServer::HandleAddEventDispatcher); H(TEXT("addEventDispatcher"), &FBlueprintMCPServer::HandleAddEventDispatcher);
H(TEXT("listEventDispatchers"), &FBlueprintMCPServer::HandleListEventDispatchers); H(TEXT("listEventDispatchers"), &FBlueprintMCPServer::HandleListEventDispatchers);
H(TEXT("addFunctionParameter"), &FBlueprintMCPServer::HandleAddFunctionParameter); H(TEXT("addFunctionParameter"), &FBlueprintMCPServer::HandleAddFunctionParameter);
@@ -1093,7 +1091,7 @@ void FBlueprintMCPServer::RegisterHandlers()
H(TEXT("restoreGraph"), &FBlueprintMCPServer::HandleRestoreGraph); H(TEXT("restoreGraph"), &FBlueprintMCPServer::HandleRestoreGraph);
H(TEXT("findDisconnectedPins"), &FBlueprintMCPServer::HandleFindDisconnectedPins); H(TEXT("findDisconnectedPins"), &FBlueprintMCPServer::HandleFindDisconnectedPins);
H(TEXT("analyzeRebuildImpact"), &FBlueprintMCPServer::HandleAnalyzeRebuildImpact); H(TEXT("analyzeRebuildImpact"), &FBlueprintMCPServer::HandleAnalyzeRebuildImpact);
H(TEXT("diffBlueprints"), &FBlueprintMCPServer::HandleDiffBlueprints); // diff_blueprints is now handled by UMCPHandler_DiffBlueprints (new-style registry)
H(TEXT("createStruct"), &FBlueprintMCPServer::HandleCreateStruct); H(TEXT("createStruct"), &FBlueprintMCPServer::HandleCreateStruct);
H(TEXT("createEnum"), &FBlueprintMCPServer::HandleCreateEnum); H(TEXT("createEnum"), &FBlueprintMCPServer::HandleCreateEnum);
H(TEXT("addStructProperty"), &FBlueprintMCPServer::HandleAddStructProperty); H(TEXT("addStructProperty"), &FBlueprintMCPServer::HandleAddStructProperty);

View File

@@ -0,0 +1,30 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "BlueprintMCPHandlers_DiffBlueprints.generated.h"
UCLASS(meta=(ToolName="diff_blueprints"))
class UMCPHandler_DiffBlueprints : public UMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="First blueprint name or package path"))
FString BlueprintA;
UPROPERTY(meta=(Description="Second blueprint name or package path"))
FString BlueprintB;
UPROPERTY(meta=(Optional, Description="Filter to a specific graph name"))
FString Graph;
virtual FString GetDescription() const override
{
return TEXT("Structural diff between two different Blueprints. Compares nodes, "
"connections, and variables across graphs. Use for comparing variants, "
"finding divergence after copy-paste, or auditing consistency.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override;
};

View File

@@ -0,0 +1,74 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "BlueprintMCPHandlers_Interfaces.generated.h"
UCLASS(meta=(ToolName="list_interfaces"))
class UMCPHandler_ListInterfaces : public UMCPHandler
{
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;
};
UCLASS(meta=(ToolName="add_interface"))
class UMCPHandler_AddInterface : public UMCPHandler
{
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;
UPROPERTY(meta=(Optional, Description="Save the blueprint after adding the interface"))
bool Save = false;
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;
};
UCLASS(meta=(ToolName="remove_interface"))
class UMCPHandler_RemoveInterface : public UMCPHandler
{
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;
UPROPERTY(meta=(Optional, Description="Save the blueprint after removing the interface"))
bool Save = 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;
};

View File

@@ -25,6 +25,9 @@ public:
UPROPERTY(meta=(Optional, Description="Y position in the graph")) UPROPERTY(meta=(Optional, Description="Y position in the graph"))
int32 PosY = 0; int32 PosY = 0;
UPROPERTY(meta=(Optional, Description="Save the blueprint after spawning the node"))
bool Save = false;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Create a node in a Blueprint graph using the editor's action database. " return TEXT("Create a node in a Blueprint graph using the editor's action database. "

View File

@@ -193,10 +193,6 @@ private:
void HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result); void HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result);
void HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result); void HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result);
// ----- Interfaces -----
void HandleAddInterface(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result);
void HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result);
// ----- Event Dispatchers ----- // ----- Event Dispatchers -----
void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result); void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result);
@@ -226,8 +222,6 @@ private:
void HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result); void HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result);
void HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result); void HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result);
// ----- Cross-Blueprint comparison (read-only) -----
void HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result);
// ----- Material read-only handlers (Phase 1) ----- // ----- Material read-only handlers (Phase 1) -----
void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result); void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result);