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 "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.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"
// ============================================================
// HandleDiffBlueprints — structural diff between two Blueprints
// ============================================================
void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result)
void UMCPHandler_DiffBlueprints::Handle(const FJsonObject* Json, FJsonObject* Result)
{
FString BlueprintA = Json->GetStringField(TEXT("blueprintA"));
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"));
}
MCPHelper* Helper = MCPHelper::Get();
// Load both blueprints
FString LoadErrorA, LoadErrorB;
UBlueprint* BPA = LoadBlueprintByName(BlueprintA, LoadErrorA);
if (!BPA) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; }
UBlueprint* BPA = Helper->LoadBlueprintByName(BlueprintA, LoadErrorA);
if (!BPA) { Helper->MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; }
UBlueprint* BPB = LoadBlueprintByName(BlueprintB, LoadErrorB);
if (!BPB) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; }
UBlueprint* BPB = Helper->LoadBlueprintByName(BlueprintB, LoadErrorB);
if (!BPB) { Helper->MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; }
// Helper to gather graphs from a Blueprint
auto GatherGraphs = [&GraphFilter](UBlueprint* BP) -> TArray<UEdGraph*>
auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*>
{
TArray<UEdGraph*> Graphs;
for (UEdGraph* G : BP->UbergraphPages)
{
if (!G) continue;
if (!GraphFilter.IsEmpty() && G->GetName() != GraphFilter) continue;
if (!Graph.IsEmpty() && (G->GetName() != Graph)) continue;
Graphs.Add(G);
}
for (UEdGraph* G : BP->FunctionGraphs)
{
if (!G) continue;
if (!GraphFilter.IsEmpty() && G->GetName() != GraphFilter) continue;
if (!Graph.IsEmpty() && (G->GetName() != Graph)) continue;
Graphs.Add(G);
}
return Graphs;
@@ -167,7 +148,7 @@ void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObj
if (!N) continue;
for (UEdGraphPin* Pin : N->Pins)
{
if (!Pin || Pin->Direction != EGPD_Output) continue;
if (!Pin || (Pin->Direction != EGPD_Output)) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo)
{
if (!Linked || !Linked->GetOwningNode()) continue;
@@ -180,7 +161,7 @@ void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObj
if (!N) continue;
for (UEdGraphPin* Pin : N->Pins)
{
if (!Pin || Pin->Direction != EGPD_Output) continue;
if (!Pin || (Pin->Direction != EGPD_Output)) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo)
{
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->SetNumberField(TEXT("nodeCountA"), GA->Nodes.Num());
GD->SetNumberField(TEXT("nodeCountB"), GB->Nodes.Num());

View File

@@ -1,30 +1,23 @@
#include "BlueprintMCPHandlers_Interfaces.h"
#include "BlueprintMCPServer.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.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"
// ============================================================
// 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"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
MCPHelper* Helper = MCPHelper::Get();
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return Helper->MakeErrorJson(Result, LoadError);
}
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("classPath"), IfaceDesc.Interface->GetPathName());
// Collect function graph names from the interface
TArray<TSharedPtr<FJsonValue>> FuncArr;
for (const UEdGraph* Graph : IfaceDesc.Graphs)
{
@@ -53,30 +45,24 @@ void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObj
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
}
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
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"));
FString InterfaceName = Json->GetStringField(TEXT("interfaceName"));
if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName"));
}
MCPHelper* Helper = MCPHelper::Get();
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return Helper->MakeErrorJson(Result, LoadError);
}
// Resolve the interface class
@@ -114,7 +100,7 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
if (!InterfaceClass)
{
FString IfaceLoadError;
UBlueprint* IfaceBP = LoadBlueprintByName(InterfaceName, IfaceLoadError);
UBlueprint* IfaceBP = Helper->LoadBlueprintByName(InterfaceName, IfaceLoadError);
if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass()))
{
InterfaceClass = IfaceBP->GeneratedClass;
@@ -123,7 +109,7 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
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."),
*InterfaceName));
}
@@ -133,24 +119,23 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
{
if (IfaceDesc.Interface == InterfaceClass)
{
return MakeErrorJson(Result, FString::Printf(
return Helper->MakeErrorJson(Result, FString::Printf(
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();
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"),
*InterfaceClass->GetName(), *BlueprintName);
*InterfaceClass->GetName(), *Blueprint);
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded)
{
return MakeErrorJson(Result, FString::Printf(
return Helper->MakeErrorJson(Result, FString::Printf(
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
@@ -171,13 +156,13 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
}
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)"),
*InterfaceClass->GetName(), *BlueprintName, AddedFunctions.Num(), bSaved ? TEXT("true") : TEXT("false"));
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"), BlueprintName);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
@@ -187,34 +172,21 @@ void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObjec
FuncArr.Add(MakeShared<FJsonValueString>(FuncName));
}
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"));
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"));
}
MCPHelper* Helper = MCPHelper::Get();
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return Helper->MakeErrorJson(Result, LoadError);
}
// 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'"),
*InterfaceName, *BlueprintName));
*InterfaceName, *Blueprint));
Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
return;
}
@@ -267,19 +239,18 @@ void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonOb
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
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);
bool bSaved = SaveBlueprintPackage(BP);
if (Save) Helper->SaveBlueprintPackage(BP);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s' (saved: %s)"),
*FoundInterface->GetName(), *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"),
*FoundInterface->GetName(), *Blueprint);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("blueprint"), Blueprint);
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
Result->SetBoolField(TEXT("preservedFunctions"), bPreserveFunctions);
Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions);
}

View File

@@ -2667,6 +2667,7 @@ void UMCPHandler_SpawnNode::Handle(const FJsonObject* Json, FJsonObject* Result)
}
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'"),
*NewNode->NodeGuid.ToString(),

View File

@@ -684,11 +684,11 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
// Interface tools
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,
QueuedHandler(TEXT("removeInterface")));
QueuedHandler(TEXT("remove_interface")));
Router->BindRoute(FHttpPath(TEXT("/api/list-interfaces")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("listInterfaces")));
QueuedHandler(TEXT("list_interfaces")));
// Event Dispatcher tools
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,
QueuedHandler(TEXT("analyzeRebuildImpact")));
Router->BindRoute(FHttpPath(TEXT("/api/diff-blueprints")), EHttpServerRequestVerbs::VERB_POST,
QueuedHandler(TEXT("diffBlueprints")));
QueuedHandler(TEXT("diff_blueprints")));
// Material read-only tools (Phase 1)
Router->BindRoute(FHttpPath(TEXT("/api/materials")), EHttpServerRequestVerbs::VERB_GET,
@@ -997,8 +997,8 @@ void FBlueprintMCPServer::RegisterHandlers()
TEXT("addVariable"),
TEXT("removeVariable"),
TEXT("setVariableMetadata"),
TEXT("addInterface"),
TEXT("removeInterface"),
TEXT("add_interface"),
TEXT("remove_interface"),
TEXT("addEventDispatcher"),
TEXT("addFunctionParameter"),
TEXT("addComponent"),
@@ -1079,9 +1079,7 @@ void FBlueprintMCPServer::RegisterHandlers()
H(TEXT("addVariable"), &FBlueprintMCPServer::HandleAddVariable);
H(TEXT("removeVariable"), &FBlueprintMCPServer::HandleRemoveVariable);
H(TEXT("setVariableMetadata"), &FBlueprintMCPServer::HandleSetVariableMetadata);
H(TEXT("addInterface"), &FBlueprintMCPServer::HandleAddInterface);
H(TEXT("removeInterface"), &FBlueprintMCPServer::HandleRemoveInterface);
H(TEXT("listInterfaces"), &FBlueprintMCPServer::HandleListInterfaces);
// add_interface, remove_interface, list_interfaces now handled by new-style registry
H(TEXT("addEventDispatcher"), &FBlueprintMCPServer::HandleAddEventDispatcher);
H(TEXT("listEventDispatchers"), &FBlueprintMCPServer::HandleListEventDispatchers);
H(TEXT("addFunctionParameter"), &FBlueprintMCPServer::HandleAddFunctionParameter);
@@ -1093,7 +1091,7 @@ void FBlueprintMCPServer::RegisterHandlers()
H(TEXT("restoreGraph"), &FBlueprintMCPServer::HandleRestoreGraph);
H(TEXT("findDisconnectedPins"), &FBlueprintMCPServer::HandleFindDisconnectedPins);
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("createEnum"), &FBlueprintMCPServer::HandleCreateEnum);
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"))
int32 PosY = 0;
UPROPERTY(meta=(Optional, Description="Save the blueprint after spawning the node"))
bool Save = false;
virtual FString GetDescription() const override
{
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 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 -----
void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result);
@@ -226,8 +222,6 @@ private:
void HandleFindDisconnectedPins(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) -----
void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result);