Moving to use of global MCPServer

This commit is contained in:
2026-03-13 13:46:12 -04:00
parent c35cfcd70c
commit e3b5d32345
55 changed files with 227 additions and 266 deletions

Binary file not shown.

View File

@@ -29,7 +29,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UAnimBlueprint* AnimBP = F.Walk(Path).Cast<UAnimBlueprint>(); UAnimBlueprint* AnimBP = F.Walk(Path).Cast<UAnimBlueprint>();
if (!AnimBP) return; if (!AnimBP) return;

View File

@@ -73,8 +73,6 @@ public:
UBlendSpace* BS = Assets.Object(); UBlendSpace* BS = Assets.Object();
// Set axis parameters // Set axis parameters
MCPUtils::PreEdit({BS});
const FBlendParameter& ParamX = BS->GetBlendParameter(0); const FBlendParameter& ParamX = BS->GetBlendParameter(0);
const FBlendParameter& ParamY = BS->GetBlendParameter(1); const FBlendParameter& ParamY = BS->GetBlendParameter(1);
@@ -126,7 +124,6 @@ public:
} }
BS->ValidateSampleData(); BS->ValidateSampleData();
MCPUtils::PostEdit({BS});
// Save // Save
bool bSaved = MCPUtils::SaveGenericPackage(BS); bool bSaved = MCPUtils::SaveGenericPackage(BS);

View File

@@ -45,7 +45,7 @@ public:
return; return;
} }
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
@@ -69,8 +69,6 @@ public:
} }
} }
F.PreEdit();
if (GraphType == TEXT("function")) if (GraphType == TEXT("function"))
{ {
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FName(*Graph), UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FName(*Graph),
@@ -116,7 +114,6 @@ public:
Result.Appendf(TEXT("Created custom event: %s\n"), *MCPUtils::FormatName(NewEvent)); Result.Appendf(TEXT("Created custom event: %s\n"), *MCPUtils::FormatName(NewEvent));
} }
F.PostEdit();
MCPUtils::SaveBlueprintPackage(BP); MCPUtils::SaveBlueprintPackage(BP);
} }
}; };

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
F.Walk(Path); F.Walk(Path);
if (!F.Ok()) return; if (!F.Ok()) return;
@@ -85,9 +85,7 @@ public:
// Remove the graph // Remove the graph
FString GraphName = MCPUtils::FormatName(TargetGraph); FString GraphName = MCPUtils::FormatName(TargetGraph);
F.PreEdit();
FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default); FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default);
F.PostEdit();
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName); Result.Appendf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName);

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>(); UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return; if (!TargetGraph) return;
@@ -73,9 +73,7 @@ public:
} }
} }
F.PreEdit();
FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName); FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName);
F.PostEdit();
MCPUtils::SaveBlueprintPackage(BP); MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Renamed to %s %s\n"), Result.Appendf(TEXT("Renamed to %s %s\n"),

View File

@@ -106,7 +106,6 @@ public:
} }
// Create the SCS node // Create the SCS node
MCPUtils::PreEdit({BP});
USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component)); USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component));
if (!NewNode) if (!NewNode)
{ {
@@ -126,7 +125,6 @@ public:
SCS->AddNode(NewNode); SCS->AddNode(NewNode);
} }
MCPUtils::PostEdit({BP});
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Added component %s (%s)"), Result.Appendf(TEXT("Added component %s (%s)"),

View File

@@ -51,7 +51,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Path).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Path).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
@@ -75,7 +75,6 @@ public:
} }
// Add a member variable with PC_MCDelegate pin type // Add a member variable with PC_MCDelegate pin type
F.PreEdit();
FEdGraphPinType DelegateType; FEdGraphPinType DelegateType;
DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate; DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate;
if (!FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType)) if (!FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType))
@@ -116,7 +115,6 @@ public:
if (!EntryNode) if (!EntryNode)
{ {
F.PostEdit();
MCPUtils::SaveBlueprintPackage(BP); MCPUtils::SaveBlueprintPackage(BP);
Result.Append(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n")); Result.Append(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n"));
return; return;
@@ -137,7 +135,6 @@ public:
} }
} }
F.PostEdit();
MCPUtils::SaveBlueprintPackage(BP); MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Created event dispatcher '%s'"), *DispatcherName); Result.Appendf(TEXT("Created event dispatcher '%s'"), *DispatcherName);

View File

@@ -133,9 +133,7 @@ public:
} }
// Add the parameter pin (EGPD_Output on entry = input to callers) // Add the parameter pin (EGPD_Output on entry = input to callers)
MCPUtils::PreEdit({BP});
EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output); EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output);
MCPUtils::PostEdit({BP});
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Added %s parameter '%s' to %s '%s'%s\n"), Result.Appendf(TEXT("Added %s parameter '%s' to %s '%s'%s\n"),

View File

@@ -55,7 +55,6 @@ public:
} }
FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName(); FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName();
MCPUtils::PreEdit({BP});
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath); bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded) if (!bAdded)
{ {
@@ -66,8 +65,6 @@ public:
} }
// Collect stub function graph names from the newly added interface entry // Collect stub function graph names from the newly added interface entry
MCPUtils::PostEdit({BP});
Result.Appendf(TEXT("Added interface %s\n"), *MCPUtils::FormatName(InterfaceClass)); Result.Appendf(TEXT("Added interface %s\n"), *MCPUtils::FormatName(InterfaceClass));
Result.Appendf(TEXT("Function stubs:\n")); Result.Appendf(TEXT("Function stubs:\n"));
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)

View File

@@ -69,7 +69,6 @@ public:
PinType.ContainerType = EPinContainerType::Array; PinType.ContainerType = EPinContainerType::Array;
// Add the variable // Add the variable
MCPUtils::PreEdit({BP});
if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue)) if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue))
{ {
MCPErrorCallback(Result).SetError(FString::Printf( MCPErrorCallback(Result).SetError(FString::Printf(
@@ -80,7 +79,6 @@ public:
if (!Category.IsEmpty()) if (!Category.IsEmpty())
FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category)); FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category));
MCPUtils::PostEdit({BP});
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Added %s %s to %s\n"), Result.Appendf(TEXT("Added %s %s to %s\n"),

View File

@@ -45,7 +45,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
@@ -128,9 +128,7 @@ public:
} }
// Apply the type change // Apply the type change
F.PreEdit();
Found->VarType = NewPinType; Found->VarType = NewPinType;
F.PostEdit();
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Changed %s to %s.%s\n"), Result.Appendf(TEXT("Changed %s to %s.%s\n"),

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;

View File

@@ -31,7 +31,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
F.Walk(Path); F.Walk(Path);
if (!F.Ok()) return; if (!F.Ok()) return;

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Path).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Path).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;

View File

@@ -29,7 +29,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
F.Walk(Path); F.Walk(Path);
if (!F.Ok()) return; if (!F.Ok()) return;
UBlueprint* BP = F.Cast<UBlueprint>(); UBlueprint* BP = F.Cast<UBlueprint>();

View File

@@ -42,7 +42,6 @@ public:
int32 NodeCount = MCPUtils::AllNodes(BP).Num(); int32 NodeCount = MCPUtils::AllNodes(BP).Num();
// Refresh all nodes // Refresh all nodes
MCPUtils::PreEdit({BP});
FBlueprintEditorUtils::RefreshAllNodes(BP); FBlueprintEditorUtils::RefreshAllNodes(BP);
// Remove orphaned pins from all nodes // Remove orphaned pins from all nodes
@@ -61,9 +60,6 @@ public:
} }
} }
// Mark as modified and recompile after orphan removal
MCPUtils::PostEdit({BP});
// Summary // Summary
Result.Appendf(TEXT("Refreshed %s: %d graphs, %d nodes"), *MCPUtils::FormatName(BP), GraphCount, NodeCount); Result.Appendf(TEXT("Refreshed %s: %d graphs, %d nodes"), *MCPUtils::FormatName(BP), GraphCount, NodeCount);
if (OrphanedPinsRemoved > 0) if (OrphanedPinsRemoved > 0)

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
@@ -83,9 +83,7 @@ public:
FString RemovedName = MCPUtils::FormatName(NodeToRemove->ComponentTemplate); FString RemovedName = MCPUtils::FormatName(NodeToRemove->ComponentTemplate);
// Remove the node (promotes children to parent if it has any — but we've guarded root above) // Remove the node (promotes children to parent if it has any — but we've guarded root above)
F.PreEdit();
SCS->RemoveNodeAndPromoteChildren(NodeToRemove); SCS->RemoveNodeAndPromoteChildren(NodeToRemove);
F.PostEdit();
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);

View File

@@ -65,9 +65,7 @@ public:
} }
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
MCPUtils::PreEdit({BP});
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
MCPUtils::PostEdit({BP});
Result.Appendf(TEXT("Removed interface %s\n"), *MCPUtils::FormatName(FoundInterface)); Result.Appendf(TEXT("Removed interface %s\n"), *MCPUtils::FormatName(FoundInterface));
if (PreserveFunctions) if (PreserveFunctions)

View File

@@ -32,7 +32,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
@@ -62,9 +62,7 @@ public:
FName VarFName = Found->VarName; FName VarFName = Found->VarName;
// RemoveMemberVariable also cleans up Get/Set nodes // RemoveMemberVariable also cleans up Get/Set nodes
F.PreEdit();
FBlueprintEditorUtils::RemoveMemberVariable(BP, VarFName); FBlueprintEditorUtils::RemoveMemberVariable(BP, VarFName);
F.PostEdit();
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
Result.Appendf(TEXT("Removed variable %s from %s.%s\n"), Result.Appendf(TEXT("Removed variable %s from %s.%s\n"),

View File

@@ -61,9 +61,7 @@ public:
} }
// Perform reparent // Perform reparent
MCPUtils::PreEdit({BP});
BP->ParentClass = NewParentClassObj; BP->ParentClass = NewParentClassObj;
MCPUtils::PostEdit({BP});
FBlueprintEditorUtils::RefreshAllNodes(BP); FBlueprintEditorUtils::RefreshAllNodes(BP);
FKismetEditorUtilities::CompileBlueprint(BP); FKismetEditorUtilities::CompileBlueprint(BP);
bool bSaved = MCPUtils::SaveBlueprintPackage(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP);

View File

@@ -28,7 +28,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UObject* Obj = F.Walk(Path).Cast<UObject>(); UObject* Obj = F.Walk(Path).Cast<UObject>();
if (!Obj) return; if (!Obj) return;

View File

@@ -51,11 +51,10 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>(); UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return; if (!TargetGraph) return;
F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
int32 TotalCount = Nodes.Array.Num(); int32 TotalCount = Nodes.Array.Num();
@@ -97,8 +96,6 @@ public:
SuccessCount++; SuccessCount++;
} }
F.PostEdit();
Result.Appendf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount); Result.Appendf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount);
} }
}; };

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>(); UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return; if (!FoundNode) return;
@@ -49,8 +49,6 @@ public:
*NodeTitle, *GraphName)); *NodeTitle, *GraphName));
} }
F.PreEdit();
if (Cast<UMaterialGraphNode>(FoundNode)) if (Cast<UMaterialGraphNode>(FoundNode))
{ {
// Use the material editor's DeleteNodes to properly remove // Use the material editor's DeleteNodes to properly remove
@@ -65,8 +63,6 @@ public:
Graph->RemoveNode(FoundNode); Graph->RemoveNode(FoundNode);
} }
F.PostEdit();
Result.Appendf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName); Result.Appendf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName);
} }
}; };

View File

@@ -41,7 +41,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>(); UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return; if (!TargetGraph) return;
@@ -75,7 +75,6 @@ public:
if (SourceNodes.Num() == 0) return; if (SourceNodes.Num() == 0) return;
F.PreEdit();
// Duplicate each node // Duplicate each node
for (UEdGraphNode* SourceNode : SourceNodes) for (UEdGraphNode* SourceNode : SourceNodes)
{ {
@@ -100,6 +99,5 @@ public:
Result.Appendf(TEXT("Duplicated: %s -> %s\n"), *MCPUtils::FormatName(SourceNode), *MCPUtils::FormatName(NewNode)); Result.Appendf(TEXT("Duplicated: %s -> %s\n"), *MCPUtils::FormatName(SourceNode), *MCPUtils::FormatName(NewNode));
} }
F.PostEdit();
} }
}; };

View File

@@ -28,7 +28,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>(); UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return; if (!FoundNode) return;

View File

@@ -39,7 +39,7 @@ public:
{ {
int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500); int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500);
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>(); UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return; if (!TargetGraph) return;

View File

@@ -31,12 +31,10 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>(); UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return; if (!FoundNode) return;
F.PreEdit();
FoundNode->NodeComment = Comment; FoundNode->NodeComment = Comment;
// Make the comment bubble visible if setting a non-empty comment // Make the comment bubble visible if setting a non-empty comment
@@ -46,8 +44,6 @@ public:
FoundNode->bCommentBubblePinned = true; FoundNode->bCommentBubblePinned = true;
} }
F.PostEdit();
Result.Appendf(TEXT("Comment set on %s\n"), *MCPUtils::FormatName(FoundNode)); Result.Appendf(TEXT("Comment set on %s\n"), *MCPUtils::FormatName(FoundNode));
} }
}; };

View File

@@ -2,8 +2,8 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPServer.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPNotifier.h"
#include "MCPProperty.h" #include "MCPProperty.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
@@ -54,9 +54,9 @@ public:
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema, void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema,
MCPNotifier& N, FStringBuilderBase& Result) FStringBuilderBase& Result)
{ {
MCPFetcher F(Result, N, GraphObj); MCPFetcher F(GraphObj);
UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>(); UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>();
if (!Pin) return; if (!Pin) return;
@@ -80,7 +80,7 @@ public:
Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error); Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error);
return; return;
} }
N.PreEditAddObject(Node); UMCPServer::AddTouchedObject(Node);
K2Schema->TrySetDefaultValue(*Pin, Entry.Value); K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
} }
@@ -89,16 +89,16 @@ public:
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj,
MCPNotifier& N, FStringBuilderBase& Result) FStringBuilderBase& Result)
{ {
MCPFetcher F(Result, N, GraphObj); MCPFetcher F(GraphObj);
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>(); UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return; if (!Node) return;
MCPProperty P = MCPProperty::GetOneExactMatch(Node, CPF_Edit, Entry.Name, Result); MCPProperty P = MCPProperty::GetOneExactMatch(Node, CPF_Edit, Entry.Name, Result);
if (!P) return; if (!P) return;
N.PreEditAddObject(Node); UMCPServer::AddTouchedObject(Node);
if (!P.SetText(Entry.Value, Result)) if (!P.SetText(Entry.Value, Result))
return; return;
@@ -109,8 +109,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Fetch the graph once. // Fetch the graph once.
MCPNotifier N; MCPFetcher GraphFetcher;
MCPFetcher GraphFetcher(Result, N);
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>(); UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
if (!GraphObj) return; if (!GraphObj) return;
@@ -124,7 +123,6 @@ public:
return; return;
} }
N.PreEdit();
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array) for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{ {
FSetNodeDefaultEntry Entry; FSetNodeDefaultEntry Entry;
@@ -132,13 +130,11 @@ public:
continue; continue;
if (K2Schema) if (K2Schema)
HandleK2Entry(Entry, GraphObj, K2Schema, N, Result); HandleK2Entry(Entry, GraphObj, K2Schema, Result);
else if (MGSchema) else if (MGSchema)
HandleMaterialEntry(Entry, GraphObj, N, Result); HandleMaterialEntry(Entry, GraphObj, Result);
} }
N.PostEdit();
Result.Appendf(TEXT("Done.\n")); Result.Appendf(TEXT("Done.\n"));
} }
}; };

View File

@@ -48,12 +48,10 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array) for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
@@ -61,7 +59,7 @@ public:
FMoveNodeEntry Entry; FMoveNodeEntry Entry;
if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal, Result)) continue; if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal, Result)) continue;
MCPFetcher FN(Result, BP); MCPFetcher FN(BP);
UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>(); UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) continue; if (!Node) continue;
@@ -71,7 +69,6 @@ public:
SuccessCount++; SuccessCount++;
} }
F.PostEdit();
Result.Appendf(TEXT("Moved %d/%d nodes.\n"), SuccessCount, Nodes.Array.Num()); Result.Appendf(TEXT("Moved %d/%d nodes.\n"), SuccessCount, Nodes.Array.Num());
} }
}; };

View File

@@ -47,12 +47,10 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>(); UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
if (!G) return; if (!G) return;
F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
int32 TotalCount = Connections.Array.Num(); int32 TotalCount = Connections.Array.Num();
@@ -62,11 +60,11 @@ public:
if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, Result)) if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, Result))
continue; continue;
MCPFetcher FS(Result, G); MCPFetcher FS(G);
UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>(); UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>();
if (!SourcePin) continue; if (!SourcePin) continue;
MCPFetcher FT(Result, G); MCPFetcher FT(G);
UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>(); UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!TargetPin) continue; if (!TargetPin) continue;
@@ -85,8 +83,6 @@ public:
SuccessCount++; SuccessCount++;
} }
F.PostEdit();
Result.Appendf(TEXT("Connected %d/%d pins.\n"), SuccessCount, TotalCount); Result.Appendf(TEXT("Connected %d/%d pins.\n"), SuccessCount, TotalCount);
} }
}; };

View File

@@ -47,12 +47,10 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>(); UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
if (!G) return; if (!G) return;
F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
int32 TotalDisconnected = 0; int32 TotalDisconnected = 0;
@@ -61,7 +59,7 @@ public:
FDisconnectPinEntry Entry; FDisconnectPinEntry Entry;
if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, Result)) continue; if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, Result)) continue;
MCPFetcher FP(Result, G); MCPFetcher FP(G);
UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>(); UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
if (!Pin) continue; if (!Pin) continue;
@@ -69,7 +67,7 @@ public:
if (!Entry.TargetPin.IsEmpty()) if (!Entry.TargetPin.IsEmpty())
{ {
MCPFetcher FT(Result, G); MCPFetcher FT(G);
UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>(); UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!Target) continue; if (!Target) continue;
@@ -100,8 +98,6 @@ public:
TotalDisconnected += DisconnectedCount; TotalDisconnected += DisconnectedCount;
} }
F.PostEdit();
Result.Appendf(TEXT("Done: %d/%d succeeded, %d links broken.\n"), Result.Appendf(TEXT("Done: %d/%d succeeded, %d links broken.\n"),
SuccessCount, Disconnections.Array.Num(), TotalDisconnected); SuccessCount, Disconnections.Array.Num(), TotalDisconnected);
} }

View File

@@ -2,6 +2,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPServer.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "BlueprintExporter.h" #include "BlueprintExporter.h"
@@ -33,13 +34,13 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
F.Walk(Path); F.Walk(Path);
if (!F.Ok()) return; if (!F.Ok()) return;
if (UEdGraph* Graph = Cast<UEdGraph>(F.GetObj())) if (UEdGraph* Graph = Cast<UEdGraph>(F.GetObj()))
{ {
EmitGraph(Graph, Result); EmitGraph(Graph);
return; return;
} }
@@ -48,8 +49,8 @@ public:
TArray<UEdGraph*> Graphs = MCPUtils::AllGraphs(BP); TArray<UEdGraph*> Graphs = MCPUtils::AllGraphs(BP);
for (UEdGraph* Graph : Graphs) for (UEdGraph* Graph : Graphs)
{ {
Result.Appendf(TEXT("\n======== %s ========\n"), *MCPUtils::FormatName(Graph)); UMCPServer::Printf(TEXT("\n======== %s ========\n"), *MCPUtils::FormatName(Graph));
EmitGraph(Graph, Result); EmitGraph(Graph);
} }
return; return;
} }
@@ -59,27 +60,27 @@ public:
MCPUtils::EnsureMaterialGraph(Mat); MCPUtils::EnsureMaterialGraph(Mat);
if (!Mat->MaterialGraph) if (!Mat->MaterialGraph)
{ {
Result.Append(TEXT("ERROR: Could not build MaterialGraph for this material\n")); UMCPServer::Print(TEXT("ERROR: Could not build MaterialGraph for this material\n"));
return; return;
} }
EmitGraph(Mat->MaterialGraph, Result); EmitGraph(Mat->MaterialGraph);
return; return;
} }
Result.Appendf(TEXT("ERROR: Expected a blueprint, material, or graph, got %s\n"), UMCPServer::Printf(TEXT("ERROR: Expected a blueprint, material, or graph, got %s\n"),
*MCPUtils::FormatName(F.GetObj()->GetClass())); *MCPUtils::FormatName(F.GetObj()->GetClass()));
} }
private: private:
void EmitGraph(UEdGraph* Graph, FStringBuilderBase& Result) void EmitGraph(UEdGraph* Graph)
{ {
FlxBlueprintExporter Exporter(Graph); FlxBlueprintExporter Exporter(Graph);
Result.Append(Exporter.GetOutput()); UMCPServer::Print(*Exporter.GetOutput());
FString Details = Exporter.GetDetails(); FString Details = Exporter.GetDetails();
if (!Details.IsEmpty()) if (!Details.IsEmpty())
{ {
Result.Append(TEXT("\n")); UMCPServer::Print(TEXT("\n"));
Result.Append(Details); UMCPServer::Print(*Details);
} }
} }
}; };

View File

@@ -38,7 +38,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>(); UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>();
if (!MI) return; if (!MI) return;
@@ -54,7 +54,6 @@ public:
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ParamID; }); return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ParamID; });
}; };
F.PreEdit();
int32 Removed = 0; int32 Removed = 0;
Removed += RemoveFrom(MI->ScalarParameterValues); Removed += RemoveFrom(MI->ScalarParameterValues);
Removed += RemoveFrom(MI->VectorParameterValues); Removed += RemoveFrom(MI->VectorParameterValues);
@@ -64,7 +63,6 @@ public:
Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues); Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues);
Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues); Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues);
Removed += RemoveFrom(MI->FontParameterValues); Removed += RemoveFrom(MI->FontParameterValues);
F.PostEdit();
if (Removed == 0) if (Removed == 0)
{ {

View File

@@ -74,10 +74,7 @@ public:
if (!MI) return; if (!MI) return;
// Set parent. // Set parent.
TArray<UObject*> Chain = { MI };
MCPUtils::PreEdit(Chain);
MI->Parent = ParentMaterialObj; MI->Parent = ParentMaterialObj;
MCPUtils::PostEdit(Chain);
// Save. // Save.
bool bSaved = MCPUtils::SaveGenericPackage(MI); bool bSaved = MCPUtils::SaveGenericPackage(MI);

View File

@@ -29,7 +29,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>(); UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>();
if (!MI) return; if (!MI) return;

View File

@@ -42,7 +42,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>(); UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>();
if (!MI) return; if (!MI) return;
@@ -71,7 +71,6 @@ public:
EMaterialParameterType Type = Found->Value.Type; EMaterialParameterType Type = Found->Value.Type;
F.PreEdit();
switch (Type) switch (Type)
{ {
case EMaterialParameterType::Scalar: case EMaterialParameterType::Scalar:
@@ -100,7 +99,6 @@ public:
Result.Appendf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type); Result.Appendf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
return; return;
} }
F.PostEdit();
MCPUtils::SaveGenericPackage(MI); MCPUtils::SaveGenericPackage(MI);
Result.Appendf(TEXT("Set '%s' = %s on %s\n"), Result.Appendf(TEXT("Set '%s' = %s on %s\n"),

View File

@@ -29,7 +29,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Load material // Load material
MCPFetcher F(Result); MCPFetcher F;
UMaterial* MaterialObj = F.Asset(Material).Cast<UMaterial>(); UMaterial* MaterialObj = F.Asset(Material).Cast<UMaterial>();
if (!MaterialObj) return; if (!MaterialObj) return;

View File

@@ -65,9 +65,6 @@ public:
if (!MaterialObj) return; if (!MaterialObj) return;
// Apply optional properties. // Apply optional properties.
TArray<UObject*> Chain = { MaterialObj };
MCPUtils::PreEdit(Chain);
if (!Domain.IsEmpty()) if (!Domain.IsEmpty())
MaterialObj->MaterialDomain = ParsedDomain; MaterialObj->MaterialDomain = ParsedDomain;
@@ -76,8 +73,6 @@ public:
MaterialObj->TwoSided = TwoSided ? 1 : 0; MaterialObj->TwoSided = TwoSided ? 1 : 0;
MCPUtils::PostEdit(Chain);
bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj); bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj);
Result.Appendf(TEXT("Created %s\n"), *MaterialObj->GetPathName()); Result.Appendf(TEXT("Created %s\n"), *MaterialObj->GetPathName());

View File

@@ -29,7 +29,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UMaterial* Mat = F.Asset(Path).Cast<UMaterial>(); UMaterial* Mat = F.Asset(Path).Cast<UMaterial>();
if (!Mat) return; if (!Mat) return;

View File

@@ -39,7 +39,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Resolve the path to an object and get its editable template. // Resolve the path to an object and get its editable template.
MCPFetcher F(Result); MCPFetcher F;
UObject* Template = F.Walk(Path).Template().Cast<UObject>(); UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return; if (!Template) return;

View File

@@ -31,7 +31,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F;
UObject* Template = F.Walk(Path).Template().Cast<UObject>(); UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return; if (!Template) return;

View File

@@ -33,7 +33,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Resolve the path to an object and get its editable template. // Resolve the path to an object and get its editable template.
MCPFetcher F(Result); MCPFetcher F;
UObject* Template = F.Walk(Path).Template().Cast<UObject>(); UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return; if (!Template) return;
@@ -59,9 +59,7 @@ public:
Resolved.Emplace(P, ValueStr); Resolved.Emplace(P, ValueStr);
} }
// Apply all changes in a single Pre/PostEditChange bracket. // Apply all changes.
F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
for (auto& [P, ValueStr] : Resolved) for (auto& [P, ValueStr] : Resolved)
{ {
@@ -70,8 +68,6 @@ public:
SuccessCount++; SuccessCount++;
} }
F.PostEdit();
// Save. // Save.
bool bSaved = MCPUtils::SaveGenericPackage(Template); bool bSaved = MCPUtils::SaveGenericPackage(Template);

View File

@@ -52,7 +52,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Resolve the anim blueprint // Resolve the anim blueprint
MCPFetcher F(Result); MCPFetcher F;
UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>(); UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return; if (!AnimBP) return;

View File

@@ -36,7 +36,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Fetch the state machine graph via MCPFetcher // Fetch the state machine graph via MCPFetcher
MCPFetcher F(Result); MCPFetcher F;
F.Walk(Path); F.Walk(Path);
if (!F.Ok()) return; if (!F.Ok()) return;

View File

@@ -46,7 +46,7 @@ public:
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Resolve the anim blueprint // Resolve the anim blueprint
MCPFetcher F(Result); MCPFetcher F;
UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>(); UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return; if (!AnimBP) return;

View File

@@ -81,10 +81,6 @@ public:
TransNode->LogicType = (ETransitionLogicType::Type)LogicType; TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
TransNode->Bidirectional = BBidirectional; TransNode->Bidirectional = BBidirectional;
TArray<UObject*> Chain = { TransNode };
MCPUtils::PreEdit(Chain);
MCPUtils::PostEdit(Chain);
// Compile and save // Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP); FKismetEditorUtilities::CompileBlueprint(AnimBP);
MCPUtils::SaveBlueprintPackage(AnimBP); MCPUtils::SaveBlueprintPackage(AnimBP);

View File

@@ -14,13 +14,6 @@ int32 UBlueprintMCPCommandlet::Main(const FString& Params)
{ {
// The UMCPServer editor subsystem starts the server automatically. // The UMCPServer editor subsystem starts the server automatically.
// We just need to tick it, since FTickableEditorObject doesn't tick in commandlet mode. // We just need to tick it, since FTickableEditorObject doesn't tick in commandlet mode.
UMCPServer* Server = UMCPServer::Get();
if (!Server)
{
UE_LOG(LogTemp, Error, TEXT("BlueprintMCP: Could not find MCP server subsystem"));
return 1;
}
double LastTime = FPlatformTime::Seconds(); double LastTime = FPlatformTime::Seconds();
while (!IsEngineExitRequested()) while (!IsEngineExitRequested())
@@ -29,7 +22,7 @@ int32 UBlueprintMCPCommandlet::Main(const FString& Params)
double DeltaTime = CurrentTime - LastTime; double DeltaTime = CurrentTime - LastTime;
LastTime = CurrentTime; LastTime = CurrentTime;
FTSTicker::GetCoreTicker().Tick(DeltaTime); FTSTicker::GetCoreTicker().Tick(DeltaTime);
Server->Tick(DeltaTime); UMCPServer::TickServer(DeltaTime);
FPlatformProcess::Sleep(0.01f); FPlatformProcess::Sleep(0.01f);
} }

View File

@@ -1,4 +1,5 @@
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
@@ -14,10 +15,12 @@
#include "Engine/LevelScriptBlueprint.h" #include "Engine/LevelScriptBlueprint.h"
#include "Subsystems/AssetEditorSubsystem.h" #include "Subsystems/AssetEditorSubsystem.h"
MCPFetcher& MCPFetcher::SetError(const FString& Msg) void MCPFetcher::SetObj(UObject* InObj) { UMCPServer::AddTouchedObject(InObj); Obj = InObj; ResultPin = nullptr; }
void MCPFetcher::SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; }
MCPFetcher& MCPFetcher::SetError()
{ {
bError = true; bError = true;
ErrorCB.SetError(Msg);
return *this; return *this;
} }
@@ -25,22 +28,22 @@ MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
{ {
bError = true; bError = true;
if (ResultPin) if (ResultPin)
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is a pin, expected %s"), Walker, Expected)); UMCPServer::Printf(TEXT("ERROR: Input to '%s' is a pin, expected %s\n"), Walker, Expected);
else if (Obj) else if (Obj)
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is %s, expected %s"), Walker, *Obj->GetClass()->GetName(), Expected)); UMCPServer::Printf(TEXT("ERROR: Input to '%s' is %s, expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected);
else else
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is null, expected %s"), Walker, Expected)); UMCPServer::Printf(TEXT("ERROR: Input to '%s' is null, expected %s\n"), Walker, Expected);
return *this; return *this;
} }
const TArray<MCPFetcher::FWalker>& MCPFetcher::GetWalkerTable() const TArray<MCPFetcher::FWalker>& MCPFetcher::GetWalkerTable()
{ {
static const TArray<FWalker> Table = { static TArray<FWalker> Table = {
{ TEXT("graph"), TEXT("Find a named UEdGraph (blank name for material graphs)"), &MCPFetcher::Graph }, { TEXT("graph"), TEXT("Find a named UEdGraph (blank name for material graphs)"), &MCPFetcher::Graph },
{ TEXT("node"), TEXT("Find a named UEdGraphNode within a graph or blueprint"), &MCPFetcher::Node }, { TEXT("node"), TEXT("Find a named UEdGraphNode within a graph or blueprint"), &MCPFetcher::Node },
{ TEXT("pin"), TEXT("Find a named UEdGraphPin on a node"), &MCPFetcher::Pin }, { TEXT("pin"), TEXT("Find a named UEdGraphPin on a node"), &MCPFetcher::Pin },
{ TEXT("component"), TEXT("Find a named component in a Blueprint's SCS"), &MCPFetcher::Component }, { TEXT("component"), TEXT("Find a named component in a Blueprint's SCS"), &MCPFetcher::Component },
{ TEXT("levelblueprint"), TEXT("Get the level blueprint from a UWorld"), &MCPFetcher::LevelBlueprint }, { TEXT("levelblueprint"), TEXT("Get the level blueprint from a UWorld"), &MCPFetcher::LevelBlueprint },
}; };
return Table; return Table;
} }
@@ -53,8 +56,8 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
Path.ParseIntoArray(Segments, TEXT(",")); Path.ParseIntoArray(Segments, TEXT(","));
if (Segments.Num() == 0) if (Segments.Num() == 0)
{ {
SetError(TEXT("Empty path")); UMCPServer::Print(TEXT("ERROR: Empty path\n"));
return *this; return SetError();
} }
for (int32 i = 0; i < Segments.Num(); i++) for (int32 i = 0; i < Segments.Num(); i++)
@@ -73,8 +76,8 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
const FWalker* W = GetWalker(Key); const FWalker* W = GetWalker(Key);
if (!W) if (!W)
{ {
SetError(FString::Printf(TEXT("Unknown path step '%s'"), *Key)); UMCPServer::Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
return *this; return SetError();
} }
(this->*W->Func)(Value); (this->*W->Func)(Value);
if (bError) return *this; if (bError) return *this;
@@ -87,18 +90,27 @@ MCPFetcher& MCPFetcher::Asset(const FString& PackagePath)
{ {
SetObj(LoadObject<UObject>(nullptr, *PackagePath)); SetObj(LoadObject<UObject>(nullptr, *PackagePath));
if (!Obj) if (!Obj)
return SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath)); {
UMCPServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
return SetError();
}
OriginalAsset = Obj; OriginalAsset = Obj;
// Open the editor for this asset (or bring it to front if already open). // Open the editor for this asset (or bring it to front if already open).
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>(); UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (!Sub || !Sub->OpenEditorForAsset(Obj)) if (!Sub || !Sub->OpenEditorForAsset(Obj))
return SetError(FString::Printf(TEXT("Could not open editor for '%s'"), *PackagePath)); {
UMCPServer::Printf(TEXT("ERROR: Could not open editor for '%s'\n"), *PackagePath);
return SetError();
}
Editor = Sub->FindEditorForAsset(OriginalAsset, false); Editor = Sub->FindEditorForAsset(OriginalAsset, false);
if (!Editor) if (!Editor)
return SetError(FString::Printf(TEXT("Could not find editor instance for '%s'"), *PackagePath)); {
UMCPServer::Printf(TEXT("ERROR: Could not find editor instance for '%s'\n"), *PackagePath);
return SetError();
}
// If this is a material, use the editor's transient copy. // If this is a material, use the editor's transient copy.
if (UMaterial* Mat = ::Cast<UMaterial>(Obj)) if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
@@ -114,9 +126,10 @@ bool MCPFetcher::CheckAssetIsA(UClass* StaticClass)
if (bError) return false; if (bError) return false;
if (!OriginalAsset || !OriginalAsset->IsA(StaticClass)) if (!OriginalAsset || !OriginalAsset->IsA(StaticClass))
{ {
SetError(FString::Printf(TEXT("Asset is %s, expected %s"), UMCPServer::Printf(TEXT("ERROR: Asset is %s, expected %s\n"),
OriginalAsset ? *OriginalAsset->GetClass()->GetName() : TEXT("null"), OriginalAsset ? *OriginalAsset->GetClass()->GetName() : TEXT("null"),
*StaticClass->GetName())); *StaticClass->GetName());
SetError();
return false; return false;
} }
return true; return true;
@@ -126,7 +139,7 @@ const MCPFetcher::FWalker* MCPFetcher::GetWalker(const FString& Key)
{ {
for (const FWalker& W : GetWalkerTable()) for (const FWalker& W : GetWalkerTable())
{ {
if (StrEq(Key, W.Key)) if (Key.Equals(W.Key, ESearchCase::IgnoreCase))
return &W; return &W;
} }
return nullptr; return nullptr;
@@ -140,10 +153,16 @@ MCPFetcher& MCPFetcher::Graph(const FString& Value)
if (UMaterial* Mat = ::Cast<UMaterial>(Obj)) if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
{ {
if (!Value.IsEmpty()) if (!Value.IsEmpty())
return SetError(FString::Printf(TEXT("Materials do not have named graphs (got '%s')"), *Value)); {
UMCPServer::Printf(TEXT("ERROR: Materials do not have named graphs (got '%s')\n"), *Value);
return SetError();
}
MCPUtils::EnsureMaterialGraph(Mat); MCPUtils::EnsureMaterialGraph(Mat);
if (!Mat->MaterialGraph) if (!Mat->MaterialGraph)
return SetError(FString::Printf(TEXT("Material '%s' has no material graph"), *Mat->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Material '%s' has no material graph\n"), *Mat->GetName());
return SetError();
}
SetObj(Mat->MaterialGraph); SetObj(Mat->MaterialGraph);
return *this; return *this;
} }
@@ -154,9 +173,15 @@ MCPFetcher& MCPFetcher::Graph(const FString& Value)
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value); TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
if (Matches.Num() == 0) if (Matches.Num() == 0)
return SetError(FString::Printf(TEXT("Graph '%s' not found in %s"), *Value, *BP->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Graph '%s' not found in %s\n"), *Value, *BP->GetName());
return SetError();
}
if (Matches.Num() > 1) if (Matches.Num() > 1)
return SetError(FString::Printf(TEXT("Ambiguous graph '%s' in %s — %d matches"), *Value, *BP->GetName(), Matches.Num())); {
UMCPServer::Printf(TEXT("ERROR: Ambiguous graph '%s' in %s — %d matches\n"), *Value, *BP->GetName(), Matches.Num());
return SetError();
}
SetObj(Matches[0]); SetObj(Matches[0]);
return *this; return *this;
@@ -175,11 +200,17 @@ MCPFetcher& MCPFetcher::Node(const FString& Value)
if (!N || !MCPUtils::Identifies(Value, N)) if (!N || !MCPUtils::Identifies(Value, N))
continue; continue;
if (Found) if (Found)
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in graph %s"), *Value, *G->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Ambiguous node '%s' in graph %s\n"), *Value, *G->GetName());
return SetError();
}
Found = N; Found = N;
} }
if (!Found) if (!Found)
return SetError(FString::Printf(TEXT("Node '%s' not found in graph %s"), *Value, *G->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Node '%s' not found in graph %s\n"), *Value, *G->GetName());
return SetError();
}
SetObj(Found); SetObj(Found);
return *this; return *this;
} }
@@ -195,12 +226,18 @@ MCPFetcher& MCPFetcher::Node(const FString& Value)
if (!N || !MCPUtils::Identifies(Value, N)) if (!N || !MCPUtils::Identifies(Value, N))
continue; continue;
if (Found) if (Found)
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in %s"), *Value, *BP->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Ambiguous node '%s' in %s\n"), *Value, *BP->GetName());
return SetError();
}
Found = N; Found = N;
} }
} }
if (!Found) if (!Found)
return SetError(FString::Printf(TEXT("Node '%s' not found in %s"), *Value, *BP->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Node '%s' not found in %s\n"), *Value, *BP->GetName());
return SetError();
}
SetObj(Found); SetObj(Found);
return *this; return *this;
} }
@@ -221,13 +258,19 @@ MCPFetcher& MCPFetcher::Pin(const FString& Value)
if (!MCPUtils::Identifies(Value, P)) if (!MCPUtils::Identifies(Value, P))
continue; continue;
if (Found) if (Found)
return SetError(FString::Printf(TEXT("Ambiguous pin '%s' on node %s"), {
*Value, *MCPUtils::FormatName(N))); UMCPServer::Printf(TEXT("ERROR: Ambiguous pin '%s' on node %s\n"),
*Value, *MCPUtils::FormatName(N));
return SetError();
}
Found = P; Found = P;
} }
if (!Found) if (!Found)
return SetError(FString::Printf(TEXT("Pin '%s' not found on node %s"), {
*Value, *MCPUtils::FormatName(N))); UMCPServer::Printf(TEXT("ERROR: Pin '%s' not found on node %s\n"),
*Value, *MCPUtils::FormatName(N));
return SetError();
}
SetPin(Found); SetPin(Found);
return *this; return *this;
@@ -243,7 +286,10 @@ MCPFetcher& MCPFetcher::Component(const FString& Value)
USimpleConstructionScript* SCS = BP->SimpleConstructionScript; USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS) if (!SCS)
return SetError(FString::Printf(TEXT("Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)"), *BP->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)\n"), *BP->GetName());
return SetError();
}
FName SearchName(*Value); FName SearchName(*Value);
for (USCS_Node* SCSNode : SCS->GetAllNodes()) for (USCS_Node* SCSNode : SCS->GetAllNodes())
@@ -255,7 +301,8 @@ MCPFetcher& MCPFetcher::Component(const FString& Value)
} }
} }
return SetError(FString::Printf(TEXT("Component '%s' not found in %s"), *Value, *BP->GetName())); UMCPServer::Printf(TEXT("ERROR: Component '%s' not found in %s\n"), *Value, *BP->GetName());
return SetError();
} }
MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value) MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value)
@@ -267,11 +314,17 @@ MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value)
return TypeMismatch(TEXT("levelblueprint"), TEXT("World")); return TypeMismatch(TEXT("levelblueprint"), TEXT("World"));
if (!World->PersistentLevel) if (!World->PersistentLevel)
return SetError(TEXT("World has no PersistentLevel")); {
UMCPServer::Print(TEXT("ERROR: World has no PersistentLevel\n"));
return SetError();
}
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true); ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (!LevelBP) if (!LevelBP)
return SetError(TEXT("World has no level blueprint")); {
UMCPServer::Print(TEXT("ERROR: World has no level blueprint\n"));
return SetError();
}
SetObj(LevelBP); SetObj(LevelBP);
return *this; return *this;
@@ -281,12 +334,18 @@ MCPFetcher& MCPFetcher::Template()
{ {
if (bError) return *this; if (bError) return *this;
if (!Obj) if (!Obj)
return SetError(TEXT("Template: object is null")); {
UMCPServer::Print(TEXT("ERROR: Template: object is null\n"));
return SetError();
}
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj)) if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
{ {
if (!BP->GeneratedClass) if (!BP->GeneratedClass)
return SetError(FString::Printf(TEXT("Blueprint '%s' has no GeneratedClass"), *Obj->GetName())); {
UMCPServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
return SetError();
}
SetObj(BP->GeneratedClass->GetDefaultObject()); SetObj(BP->GeneratedClass->GetDefaultObject());
return *this; return *this;
} }
@@ -303,10 +362,16 @@ MCPFetcher& MCPFetcher::ToBlueprint()
if (UWorld* World = ::Cast<UWorld>(Obj)) if (UWorld* World = ::Cast<UWorld>(Obj))
{ {
if (!World->PersistentLevel) if (!World->PersistentLevel)
return SetError(TEXT("ToBlueprint: World has no PersistentLevel")); {
UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no PersistentLevel\n"));
return SetError();
}
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true); ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (!LevelBP) if (!LevelBP)
return SetError(TEXT("ToBlueprint: World has no level blueprint")); {
UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no level blueprint\n"));
return SetError();
}
SetObj(LevelBP); SetObj(LevelBP);
return *this; return *this;
} }
@@ -323,11 +388,13 @@ MCPFetcher& MCPFetcher::ToGraph()
{ {
MCPUtils::EnsureMaterialGraph(Mat); MCPUtils::EnsureMaterialGraph(Mat);
if (!Mat->MaterialGraph) if (!Mat->MaterialGraph)
return SetError(FString::Printf(TEXT("ToGraph: Material '%s' has no material graph"), *Mat->GetName())); {
UMCPServer::Printf(TEXT("ERROR: ToGraph: Material '%s' has no material graph\n"), *Mat->GetName());
return SetError();
}
SetObj(Mat->MaterialGraph); SetObj(Mat->MaterialGraph);
return *this; return *this;
} }
return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material")); return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material"));
} }

View File

@@ -6,25 +6,17 @@
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "MaterialEditingLibrary.h" #include "MaterialEditingLibrary.h"
void MCPNotifier::PreEditAddObject(UObject* Obj) void MCPNotifier::AddTouchedObject(UObject* Obj)
{ {
if (!Obj) return; if (!Obj) return;
bool bAlreadyInSet = false; bool bAlreadyInSet = false;
TouchedSet.Add(Obj, &bAlreadyInSet); TouchedSet.Add(Obj, &bAlreadyInSet);
if (bAlreadyInSet) return; if (bAlreadyInSet) return;
TouchedArray.Add(Obj); TouchedArray.Add(Obj);
if (bInsidePrePost) Obj->PreEditChange(nullptr);
Obj->PreEditChange(nullptr);
} }
void MCPNotifier::PreEdit() void MCPNotifier::SendNotifications()
{
bInsidePrePost = true;
for (UObject* Obj : TouchedArray)
Obj->PreEditChange(nullptr);
}
void MCPNotifier::PostEdit()
{ {
TSet<UEdGraphNode*> Nodes; TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs; TSet<UEdGraph*> Graphs;
@@ -61,5 +53,6 @@ void MCPNotifier::PostEdit()
if (GEditor) if (GEditor)
GEditor->RedrawAllViewports(); GEditor->RedrawAllViewports();
bInsidePrePost = false; TouchedSet.Empty();
TouchedArray.Empty();
} }

View File

@@ -100,15 +100,7 @@
#include "AnimationGraph.h" #include "AnimationGraph.h"
#include "AnimationTransitionGraph.h" #include "AnimationTransitionGraph.h"
// ============================================================ UMCPServer* UMCPServer::GMCPServer = nullptr;
// Get() — retrieve the active server via the editor subsystem
// ============================================================
UMCPServer* UMCPServer::Get()
{
if (!GEditor) return nullptr;
return GEditor->GetEditorSubsystem<UMCPServer>();
}
// ============================================================ // ============================================================
// Initialization and Shutdown // Initialization and Shutdown
@@ -117,6 +109,7 @@ UMCPServer* UMCPServer::Get()
void UMCPServer::Initialize(FSubsystemCollectionBase& Collection) void UMCPServer::Initialize(FSubsystemCollectionBase& Collection)
{ {
Super::Initialize(Collection); Super::Initialize(Collection);
GMCPServer = this;
// Create TCP listen socket // Create TCP listen socket
ISocketSubsystem* SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); ISocketSubsystem* SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
@@ -210,6 +203,7 @@ void UMCPServer::Deinitialize()
LogCapture.Uninstall(); LogCapture.Uninstall();
bRunning = false; bRunning = false;
bShuttingDown = false; bShuttingDown = false;
GMCPServer = nullptr;
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Server stopped.")); UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Server stopped."));
Super::Deinitialize(); Super::Deinitialize();
} }
@@ -245,6 +239,11 @@ void UMCPServer::Tick(float DeltaTime)
} }
} }
void UMCPServer::TickServer(float DeltaTime)
{
if (GMCPServer) GMCPServer->Tick(DeltaTime);
}
bool UMCPServer::IsTickable() const bool UMCPServer::IsTickable() const
{ {
return bRunning; return bRunning;
@@ -301,17 +300,19 @@ FString UMCPServer::HandleRequest(const FString& Line)
// Invoke the handler with log capture. // Invoke the handler with log capture.
LogCapture.CapturedErrors.Empty(); LogCapture.CapturedErrors.Empty();
LogCapture.bEnabled = true; LogCapture.bEnabled = true;
TStringBuilder<32768> TextResult; HandlerOutput.Reset();
Handler->Handle(TextResult); Handler->Handle(HandlerOutput);
Notifier.SendNotifications();
LogCapture.bEnabled = false; LogCapture.bEnabled = false;
for (const FString& Msg : LogCapture.CapturedErrors) for (const FString& Msg : LogCapture.CapturedErrors)
{ {
TextResult.Append(TEXT("LOG: ")); HandlerOutput.Append(TEXT("LOG: "));
TextResult.Append(Msg); HandlerOutput.Append(Msg);
TextResult.Append(TEXT("\n")); HandlerOutput.Append(TEXT("\n"));
} }
LogCapture.CapturedErrors.Empty(); LogCapture.CapturedErrors.Empty();
FString Result = TextResult.ToString(); FString Result = HandlerOutput.ToString();
HandlerOutput.Reset();
for (int32 i = 0; i < Result.Len(); ++i) for (int32 i = 0; i < Result.Len(); ++i)
{ {
if (Result[i] == TEXT('\0')) Result[i] = TEXT(' '); if (Result[i] == TEXT('\0')) Result[i] = TEXT(' ');

View File

@@ -1322,10 +1322,6 @@ FString MCPUtils::GetHandlerName(UClass* HandlerClass)
// Strip "MCP_" prefix // Strip "MCP_" prefix
if (Name.StartsWith(TEXT("MCP_"))) if (Name.StartsWith(TEXT("MCP_")))
Name = Name.Mid(4); Name = Name.Mid(4);
// Strip the remaining underscore between group and action (e.g. "Blueprint_Create" -> "BlueprintCreate")
int32 UnderscoreIdx;
if (Name.FindChar(TEXT('_'), UnderscoreIdx))
Name = Name.Left(UnderscoreIdx) + Name.Mid(UnderscoreIdx + 1);
return Name; return Name;
} }

View File

@@ -2,7 +2,6 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MCPNotifier.h"
class UEdGraphPin; class UEdGraphPin;
class IAssetEditorInstance; class IAssetEditorInstance;
@@ -20,19 +19,17 @@ class IAssetEditorInstance;
// /Game/Tangibles/TAN_Character,component:CharacterMesh0 // /Game/Tangibles/TAN_Character,component:CharacterMesh0
// //
// Builder-style usage: // Builder-style usage:
// MCPFetcher F(cb); // MCPFetcher F;
// if (!F.Walk(Path).Ok()) return; // if (!F.Walk(Path).Ok()) return;
// //
// MCPFetcher F(cb, ExistingObj); // MCPFetcher F(ExistingObj);
// if (!F.Graph("EventGraph").Node("MyNode").Ok()) return; // if (!F.Graph("EventGraph").Node("MyNode").Ok()) return;
// //
class MCPFetcher class MCPFetcher
{ {
public: public:
MCPFetcher(MCPErrorCallback CB) : ErrorCB(CB) {} MCPFetcher() {}
MCPFetcher(MCPErrorCallback CB, UObject* O) : ErrorCB(CB), Obj(O) {} MCPFetcher(UObject* O) : Obj(O) {}
MCPFetcher(MCPErrorCallback CB, MCPNotifier& N) : ErrorCB(CB), Notifier(&N) {}
MCPFetcher(MCPErrorCallback CB, MCPNotifier& N, UObject* O) : ErrorCB(CB), Obj(O), Notifier(&N) {}
// Starting point is always Asset. // Starting point is always Asset.
MCPFetcher& Asset(const FString& PackagePath); MCPFetcher& Asset(const FString& PackagePath);
@@ -90,15 +87,7 @@ public:
return Result; return Result;
} }
void PreEditAddObject(UObject* Obj) { Notifier->PreEditAddObject(Obj); }
void PreEdit() { Notifier->PreEdit(); }
void PostEdit() { Notifier->PostEdit(); }
private: private:
// The error callback is invoked whenever an error is detected.
MCPErrorCallback ErrorCB = nullptr;
// The Current Object or Pin // The Current Object or Pin
UObject* Obj = nullptr; UObject* Obj = nullptr;
UEdGraphPin* ResultPin = nullptr; UEdGraphPin* ResultPin = nullptr;
@@ -110,15 +99,10 @@ private:
// True if an error has occurred. // True if an error has occurred.
bool bError = false; bool bError = false;
// Notifier for tracking touched objects.
MCPNotifier OwnedNotifier;
MCPNotifier* Notifier = &OwnedNotifier;
// Internal methods. // Internal methods.
static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); } void SetObj(UObject* InObj);
void SetObj(UObject* InObj) { Notifier->PreEditAddObject(InObj); Obj = InObj; ResultPin = nullptr; } void SetPin(UEdGraphPin* InPin);
void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; } MCPFetcher& SetError();
MCPFetcher& SetError(const FString& Msg);
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected); MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);
const FWalker* GetWalker(const FString& Key); const FWalker* GetWalker(const FString& Key);
bool CheckAssetIsA(UClass* StaticClass); bool CheckAssetIsA(UClass* StaticClass);

View File

@@ -6,23 +6,13 @@
// Handles PreEditChange/PostEditChange, ReconstructNode, and other // Handles PreEditChange/PostEditChange, ReconstructNode, and other
// notifications that need to happen after modifications. // notifications that need to happen after modifications.
// //
// Usage:
// MCPNotifier N;
// MCPFetcher F(Result, N);
// F.Walk(Path)...
// N.PreEdit();
// // modify stuff
// N.PostEdit();
//
class MCPNotifier class MCPNotifier
{ {
public: public:
void PreEditAddObject(UObject* Obj); void AddTouchedObject(UObject* Obj);
void PreEdit(); void SendNotifications();
void PostEdit();
private: private:
bool bInsidePrePost = false;
TSet<UObject*> TouchedSet; TSet<UObject*> TouchedSet;
TArray<UObject*> TouchedArray; TArray<UObject*> TouchedArray;
}; };

View File

@@ -7,6 +7,7 @@
#include "Async/Future.h" #include "Async/Future.h"
#include "Dom/JsonObject.h" #include "Dom/JsonObject.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MCPNotifier.h"
#include "LogCapture.h" #include "LogCapture.h"
#include "MCPServer.generated.h" #include "MCPServer.generated.h"
@@ -32,18 +33,34 @@ class UMCPServer : public UEditorSubsystem, public FTickableEditorObject
GENERATED_BODY() GENERATED_BODY()
public: public:
/** Get the active server instance via GEditor. */
static UMCPServer* Get();
// UEditorSubsystem // UEditorSubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
// FTickableEditorObject // FTickableEditorObject
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
/** Static entry point for commandlet mode (no FTickableEditorObject). */
static void TickServer(float DeltaTime);
virtual bool IsTickable() const override; virtual bool IsTickable() const override;
virtual TStatId GetStatId() const override; virtual TStatId GetStatId() const override;
/** Track an object that has been modified by the current handler. */
static void AddTouchedObject(UObject* Obj) { GMCPServer->Notifier.AddTouchedObject(Obj); }
/** Send notifications now (also called automatically after the handler returns). */
static void SendNotifications() { GMCPServer->Notifier.SendNotifications(); }
/** Print text to the handler output buffer. */
static void Print(const TCHAR* Text) { GMCPServer->HandlerOutput.Append(Text); }
/** Print formatted text to the handler output buffer. */
template <typename FmtType, typename... ArgTypes>
static void Printf(const FmtType& Fmt, ArgTypes&&... Args)
{
GMCPServer->HandlerOutput.Appendf(Fmt, Forward<ArgTypes>(Args)...);
}
/** Whether the server is currently listening. */ /** Whether the server is currently listening. */
bool IsRunning() const { return bRunning; } bool IsRunning() const { return bRunning; }
@@ -51,8 +68,11 @@ public:
int32 GetPort() const { return Port; } int32 GetPort() const { return Port; }
private: private:
static UMCPServer* GMCPServer;
// ----- Tool dispatch ----- // ----- Tool dispatch -----
MCPNotifier Notifier;
TStringBuilder<16384> HandlerOutput;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TMap<FString, UClass*> MCPHandlerRegistry; // tool name -> UMCPHandler subclass TMap<FString, UClass*> MCPHandlerRegistry; // tool name -> UMCPHandler subclass
void BuildMCPHandlerRegistry(); void BuildMCPHandlerRegistry();