Begining refactor of BlueprintMCP

This commit is contained in:
2026-03-05 21:23:59 -05:00
parent 8367bd2221
commit 9b32de024a
24 changed files with 1016 additions and 1719 deletions

View File

@@ -38,14 +38,8 @@
// HandleCreateAnimBlueprint — create a new Animation Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString Name = Json->GetStringField(TEXT("name"));
FString PackagePath = Json->GetStringField(TEXT("packagePath"));
FString SkeletonName = Json->GetStringField(TEXT("skeleton"));
@@ -53,19 +47,19 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
if (Name.IsEmpty() || PackagePath.IsEmpty() || SkeletonName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: name, packagePath, skeleton"));
return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
}
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(TEXT("packagePath must start with '/Game'"));
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
FString FullAssetPath = PackagePath / Name;
if (FindBlueprintAsset(Name) || FindBlueprintAsset(FullAssetPath))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
@@ -120,7 +114,7 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
if (!Skeleton)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
*SkeletonName));
}
@@ -147,7 +141,7 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
UPackage* Package = CreatePackage(*FullPackagePath);
if (!Package)
{
return MakeErrorJson(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
}
// Create the Animation Blueprint
@@ -163,7 +157,7 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
if (!NewAnimBP)
{
return MakeErrorJson(TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint"));
return MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint"));
}
// Set target skeleton
@@ -195,7 +189,6 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"),
*Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprintName"), Name);
Result->SetStringField(TEXT("packagePath"), PackagePath);
@@ -205,7 +198,6 @@ FString FBlueprintMCPServer::HandleCreateAnimBlueprint(const FString& Body)
Result->SetBoolField(TEXT("isAnimBlueprint"), true);
Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames);
return JsonToString(Result);
}
// ============================================================
@@ -267,43 +259,40 @@ static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMG
return nullptr;
}
FString FBlueprintMCPServer::HandleAddAnimState(const FString& Body)
void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString StateName = Json->GetStringField(TEXT("stateName"));
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, stateName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
// Check for duplicate state name
if (FindStateByName(SMGraph, StateName))
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' already exists in graph '%s'"), *StateName, *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' already exists in graph '%s'"), *StateName, *GraphName));
}
// Get position
@@ -365,52 +354,47 @@ FString FBlueprintMCPServer::HandleAddAnimState(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);
Result->SetStringField(TEXT("graph"), GraphName);
Result->SetStringField(TEXT("nodeId"), NewState->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleRemoveAnimState(const FString& Body)
void FBlueprintMCPServer::HandleRemoveAnimState(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString StateName = Json->GetStringField(TEXT("stateName"));
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, stateName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
// Collect and remove transitions connected to this state
@@ -441,22 +425,14 @@ FString FBlueprintMCPServer::HandleRemoveAnimState(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("removedState"), StateName);
Result->SetNumberField(TEXT("removedTransitions"), RemovedTransitions);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleAddAnimTransition(const FString& Body)
void FBlueprintMCPServer::HandleAddAnimTransition(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString FromStateName = Json->GetStringField(TEXT("fromState"));
@@ -464,35 +440,38 @@ FString FBlueprintMCPServer::HandleAddAnimTransition(const FString& Body)
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || FromStateName.IsEmpty() || ToStateName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, fromState, toState"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* FromState = FindStateByName(SMGraph, FromStateName);
if (!FromState)
{
return MakeErrorJson(FString::Printf(TEXT("From state '%s' not found"), *FromStateName));
return MakeErrorJson(Result, FString::Printf(TEXT("From state '%s' not found"), *FromStateName));
}
UAnimStateNode* ToState = FindStateByName(SMGraph, ToStateName);
if (!ToState)
{
return MakeErrorJson(FString::Printf(TEXT("To state '%s' not found"), *ToStateName));
return MakeErrorJson(Result, FString::Printf(TEXT("To state '%s' not found"), *ToStateName));
}
// Create transition node
@@ -529,7 +508,6 @@ FString FBlueprintMCPServer::HandleAddAnimTransition(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("fromState"), FromStateName);
Result->SetStringField(TEXT("toState"), ToStateName);
@@ -538,17 +516,10 @@ FString FBlueprintMCPServer::HandleAddAnimTransition(const FString& Body)
Result->SetNumberField(TEXT("priorityOrder"), TransNode->PriorityOrder);
Result->SetBoolField(TEXT("bBidirectional"), TransNode->Bidirectional);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleSetTransitionRule(const FString& Body)
void FBlueprintMCPServer::HandleSetTransitionRule(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString FromStateName = Json->GetStringField(TEXT("fromState"));
@@ -556,29 +527,32 @@ FString FBlueprintMCPServer::HandleSetTransitionRule(const FString& Body)
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || FromStateName.IsEmpty() || ToStateName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, fromState, toState"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateTransitionNode* TransNode = FindTransition(SMGraph, FromStateName, ToStateName);
if (!TransNode)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Transition from '%s' to '%s' not found in graph '%s'"),
*FromStateName, *ToStateName, *GraphName));
}
@@ -614,14 +588,13 @@ FString FBlueprintMCPServer::HandleSetTransitionRule(const FString& Body)
if (ChangedCount == 0)
{
return MakeErrorJson(TEXT("No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional"));
return MakeErrorJson(Result, TEXT("No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional"));
}
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("fromState"), FromStateName);
Result->SetStringField(TEXT("toState"), ToStateName);
@@ -632,38 +605,34 @@ FString FBlueprintMCPServer::HandleSetTransitionRule(const FString& Body)
Result->SetNumberField(TEXT("logicType"), (int32)TransNode->LogicType.GetValue());
Result->SetBoolField(TEXT("bBidirectional"), TransNode->Bidirectional);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// Tier 3: AnimGraph Blend Tree Mutation
// ============================================================
FString FBlueprintMCPServer::HandleAddAnimNode(const FString& Body)
void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString NodeType = Json->GetStringField(TEXT("nodeType"));
if (BlueprintName.IsEmpty() || NodeType.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeType"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Find target graph (default to AnimGraph if not specified)
@@ -686,7 +655,7 @@ FString FBlueprintMCPServer::HandleAddAnimNode(const FString& Body)
if (!TargetGraph)
{
return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *GraphName));
}
int32 PosX = Json->HasField(TEXT("posX")) ? (int32)Json->GetNumberField(TEXT("posX")) : 0;
@@ -766,14 +735,14 @@ FString FBlueprintMCPServer::HandleAddAnimNode(const FString& Body)
}
else
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Unsupported nodeType '%s'. Supported: SequencePlayer, BlendSpacePlayer, StateMachine"),
*NodeType));
}
if (!NewNode)
{
return MakeErrorJson(TEXT("Failed to create anim node"));
return MakeErrorJson(Result, TEXT("Failed to create anim node"));
}
NewNode->NodePosX = PosX;
@@ -785,7 +754,6 @@ FString FBlueprintMCPServer::HandleAddAnimNode(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("nodeType"), NodeType);
Result->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString());
@@ -803,28 +771,20 @@ FString FBlueprintMCPServer::HandleAddAnimNode(const FString& Body)
}
}
}
return JsonToString(Result);
}
// ============================================================
// Tier 4: Advanced Operations
// ============================================================
FString FBlueprintMCPServer::HandleAddStateMachine(const FString& Body)
void FBlueprintMCPServer::HandleAddStateMachine(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString MachineName = Json->GetStringField(TEXT("name"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
// Default name
@@ -843,21 +803,11 @@ FString FBlueprintMCPServer::HandleAddStateMachine(const FString& Body)
if (Json->HasField(TEXT("posY")))
ForwardJson->SetNumberField(TEXT("posY"), Json->GetNumberField(TEXT("posY")));
FString ForwardBody;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ForwardBody);
FJsonSerializer::Serialize(ForwardJson, Writer);
return HandleAddAnimNode(ForwardBody);
HandleAddAnimNode(&*ForwardJson, Result);
}
FString FBlueprintMCPServer::HandleSetStateAnimation(const FString& Body)
void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString StateName = Json->GetStringField(TEXT("stateName"));
@@ -865,35 +815,38 @@ FString FBlueprintMCPServer::HandleSetStateAnimation(const FString& Body)
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty() || AnimAssetName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, stateName, animationAsset"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, animationAsset"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
}
// Find the animation asset
@@ -913,7 +866,7 @@ FString FBlueprintMCPServer::HandleSetStateAnimation(const FString& Body)
if (!AnimSeq)
{
return MakeErrorJson(FString::Printf(TEXT("Animation asset '%s' not found"), *AnimAssetName));
return MakeErrorJson(Result, FString::Printf(TEXT("Animation asset '%s' not found"), *AnimAssetName));
}
// Find existing SequencePlayer or create one
@@ -943,37 +896,32 @@ FString FBlueprintMCPServer::HandleSetStateAnimation(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);
Result->SetStringField(TEXT("animationAsset"), AnimSeq->GetName());
Result->SetBoolField(TEXT("createdNewNode"), bCreatedNew);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleListAnimSlots(const FString& Body)
void FBlueprintMCPServer::HandleListAnimSlots(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Walk all anim nodes to collect slot names
@@ -1008,36 +956,31 @@ FString FBlueprintMCPServer::HandleListAnimSlots(const FString& Body)
SlotsArr.Add(MakeShared<FJsonValueString>(Slot));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetArrayField(TEXT("slots"), SlotsArr);
Result->SetNumberField(TEXT("count"), SlotsArr.Num());
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleListSyncGroups(const FString& Body)
void FBlueprintMCPServer::HandleListSyncGroups(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Walk all anim nodes to collect sync group names
@@ -1072,38 +1015,30 @@ FString FBlueprintMCPServer::HandleListSyncGroups(const FString& Body)
GroupsArr.Add(MakeShared<FJsonValueString>(Group));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetArrayField(TEXT("syncGroups"), GroupsArr);
Result->SetNumberField(TEXT("count"), GroupsArr.Num());
return JsonToString(Result);
}
// ============================================================
// HandleCreateBlendSpace — create a new 2D Blend Space asset
// ============================================================
FString FBlueprintMCPServer::HandleCreateBlendSpace(const FString& Body)
void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString Name = Json->GetStringField(TEXT("name"));
FString PackagePath = Json->GetStringField(TEXT("packagePath"));
FString SkeletonName = Json->GetStringField(TEXT("skeleton"));
if (Name.IsEmpty() || PackagePath.IsEmpty() || SkeletonName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: name, packagePath, skeleton"));
return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
}
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(TEXT("packagePath must start with '/Game'"));
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
@@ -1116,7 +1051,7 @@ FString FBlueprintMCPServer::HandleCreateBlendSpace(const FString& Body)
{
if (Asset.AssetName.ToString() == Name || Asset.GetObjectPathString() == FullAssetPath)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
@@ -1171,7 +1106,7 @@ FString FBlueprintMCPServer::HandleCreateBlendSpace(const FString& Body)
if (!Skeleton)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
*SkeletonName));
}
@@ -1184,14 +1119,14 @@ FString FBlueprintMCPServer::HandleCreateBlendSpace(const FString& Body)
UPackage* Package = CreatePackage(*FullPackagePath);
if (!Package)
{
return MakeErrorJson(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
}
// Create the Blend Space
UBlendSpace* NewBS = NewObject<UBlendSpace>(Package, FName(*Name), RF_Public | RF_Standalone);
if (!NewBS)
{
return MakeErrorJson(TEXT("Failed to create Blend Space object"));
return MakeErrorJson(Result, TEXT("Failed to create Blend Space object"));
}
// Set skeleton
@@ -1204,30 +1139,22 @@ FString FBlueprintMCPServer::HandleCreateBlendSpace(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), FullAssetPath);
Result->SetStringField(TEXT("skeleton"), Skeleton->GetName());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleSetBlendSpaceSamples — add animation samples to a Blend Space
// ============================================================
FString FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FString& Body)
void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlendSpaceName = Json->GetStringField(TEXT("blendSpace"));
if (BlendSpaceName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blendSpace"));
return MakeErrorJson(Result, TEXT("Missing required field: blendSpace"));
}
// Load the blend space
@@ -1263,7 +1190,7 @@ FString FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FString& Body)
if (!BS)
{
return MakeErrorJson(FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
return MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
}
// Set axis parameters
@@ -1366,26 +1293,18 @@ FString FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set %d samples on Blend Space '%s' (saved: %s)"),
SamplesSet, *BS->GetName(), bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blendSpace"), BS->GetPathName());
Result->SetNumberField(TEXT("samplesSet"), SamplesSet);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleSetStateBlendSpace — place a BlendSpacePlayer in a state
// ============================================================
FString FBlueprintMCPServer::HandleSetStateBlendSpace(const FString& Body)
void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graph"));
FString StateName = Json->GetStringField(TEXT("stateName"));
@@ -1395,35 +1314,38 @@ FString FBlueprintMCPServer::HandleSetStateBlendSpace(const FString& Body)
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty() || BlendSpaceName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, stateName, blendSpace"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, blendSpace"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph)
{
return MakeErrorJson(FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
}
// Find the blend space asset
@@ -1459,7 +1381,7 @@ FString FBlueprintMCPServer::HandleSetStateBlendSpace(const FString& Body)
if (!BlendSpaceAsset)
{
return MakeErrorJson(FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
return MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
}
// Find existing BlendSpacePlayer or create one
@@ -1609,11 +1531,9 @@ FString FBlueprintMCPServer::HandleSetStateBlendSpace(const FString& Body)
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);
Result->SetStringField(TEXT("blendSpace"), BlendSpaceAsset->GetName());
Result->SetStringField(TEXT("nodeId"), BSNode->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
}

View File

@@ -13,31 +13,25 @@
// HandleListComponents — list all components in a Blueprint's SCS
// ============================================================
FString FBlueprintMCPServer::HandleListComponents(const FString& Body)
void FBlueprintMCPServer::HandleListComponents(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"),
*BlueprintName));
}
@@ -95,32 +89,24 @@ FString FBlueprintMCPServer::HandleListComponents(const FString& Body)
ComponentsArr.Add(MakeShared<FJsonValueObject>(CompObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetNumberField(TEXT("count"), ComponentsArr.Num());
Result->SetArrayField(TEXT("components"), ComponentsArr);
return JsonToString(Result);
}
// ============================================================
// HandleAddComponent — add a component to a Blueprint's SCS
// ============================================================
FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
void FBlueprintMCPServer::HandleAddComponent(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString ComponentClassName = Json->GetStringField(TEXT("componentClass"));
FString ComponentName = Json->GetStringField(TEXT("name"));
if (BlueprintName.IsEmpty() || ComponentClassName.IsEmpty() || ComponentName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, componentClass, name"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, componentClass, name"));
}
FString ParentComponentName;
@@ -133,13 +119,13 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"),
*BlueprintName));
}
@@ -150,7 +136,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
{
if (Existing && Existing->GetVariableName().ToString().Equals(ComponentName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A component named '%s' already exists in Blueprint '%s'"),
*ComponentName, *BlueprintName));
}
@@ -197,7 +183,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
if (!ComponentClass)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Component class '%s' not found or is not a subclass of UActorComponent. "
"Common classes: StaticMeshComponent, SkeletalMeshComponent, AudioComponent, "
"SceneComponent, BoxCollisionComponent, SphereCollisionComponent, CapsuleComponent, "
@@ -221,7 +207,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
if (!ParentSCSNode)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Parent component '%s' not found in Blueprint '%s'"),
*ParentComponentName, *BlueprintName));
}
@@ -234,7 +220,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
USCS_Node* NewNode = SCS->CreateNode(ComponentClass, FName(*ComponentName));
if (!NewNode)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Failed to create SCS node for component '%s' with class '%s'"),
*ComponentName, *ComponentClass->GetName()));
}
@@ -257,7 +243,6 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
ParentSCSNode ? *ParentComponentName : TEXT("(root)"),
bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("name"), NewNode->GetVariableName().ToString());
@@ -267,40 +252,33 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body)
Result->SetStringField(TEXT("parentComponent"), ParentSCSNode->GetVariableName().ToString());
}
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleRemoveComponent — remove a component from a Blueprint's SCS
// ============================================================
FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body)
void FBlueprintMCPServer::HandleRemoveComponent(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString ComponentName = Json->GetStringField(TEXT("name"));
if (BlueprintName.IsEmpty() || ComponentName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, name"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, name"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"),
*BlueprintName));
}
@@ -329,19 +307,18 @@ FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body)
}
}
TSharedRef<FJsonObject> ErrorResult = MakeShared<FJsonObject>();
ErrorResult->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Component '%s' not found in Blueprint '%s'"),
*ComponentName, *BlueprintName));
ErrorResult->SetArrayField(TEXT("existingComponents"), CompList);
return JsonToString(ErrorResult);
Result->SetArrayField(TEXT("existingComponents"), CompList);
return;
}
// Prevent removing the root scene component if it has children
const TArray<USCS_Node*>& RootNodes = SCS->GetRootNodes();
if (RootNodes.Contains(NodeToRemove) && NodeToRemove->GetChildNodes().Num() > 0)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Cannot remove component '%s' because it is a root component with %d child(ren). "
"Remove or re-parent the children first."),
*ComponentName, NodeToRemove->GetChildNodes().Num()));
@@ -359,10 +336,8 @@ FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed component '%s' from '%s' (saved: %s)"),
*ComponentName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("name"), ComponentName);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -17,30 +17,24 @@
// HandleDiffBlueprints — structural diff between two Blueprints
// ============================================================
FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body)
void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
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(TEXT("Missing required fields: blueprintA, blueprintB"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprintA, blueprintB"));
}
// Load both blueprints
FString LoadErrorA, LoadErrorB;
UBlueprint* BPA = LoadBlueprintByName(BlueprintA, LoadErrorA);
if (!BPA) return MakeErrorJson(FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA));
if (!BPA) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; }
UBlueprint* BPB = LoadBlueprintByName(BlueprintB, LoadErrorB);
if (!BPB) return MakeErrorJson(FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB));
if (!BPB) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; }
// Helper to gather graphs from a Blueprint
auto GatherGraphs = [&GraphFilter](UBlueprint* BP) -> TArray<UEdGraph*>
@@ -245,7 +239,6 @@ FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body)
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprintA"), BlueprintA);
Result->SetStringField(TEXT("blueprintB"), BlueprintB);
@@ -264,6 +257,4 @@ FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body)
}
TotalDiffs += VarsOnlyInA.Num() + VarsOnlyInB.Num();
Result->SetNumberField(TEXT("totalDifferences"), TotalDiffs);
return JsonToString(Result);
}

View File

@@ -15,35 +15,29 @@
// HandleGetPinInfo — detailed information about a specific pin
// ============================================================
FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body)
void FBlueprintMCPServer::HandleGetPinInfo(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString NodeId = Json->GetStringField(TEXT("nodeId"));
FString PinName = Json->GetStringField(TEXT("pinName"));
if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || PinName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId, pinName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, pinName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UEdGraph* Graph = nullptr;
UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph);
if (!Node)
{
return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId));
return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId));
}
UEdGraphPin* Pin = Node->FindPin(FName(*PinName));
@@ -62,13 +56,11 @@ FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body)
AvailPins.Add(MakeShared<FJsonValueObject>(PinObj));
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId));
E->SetArrayField(TEXT("availablePins"), AvailPins);
return JsonToString(E);
MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId));
Result->SetArrayField(TEXT("availablePins"), AvailPins);
return;
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("nodeId"), NodeId);
@@ -119,22 +111,14 @@ FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body)
}
Result->SetArrayField(TEXT("connectedTo"), Conns);
}
return JsonToString(Result);
}
// ============================================================
// HandleCheckPinCompatibility — pre-flight check for connect_pins
// ============================================================
FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body)
void FBlueprintMCPServer::HandleCheckPinCompatibility(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString SourceNodeId = Json->GetStringField(TEXT("sourceNodeId"));
FString SourcePinName = Json->GetStringField(TEXT("sourcePinName"));
@@ -144,51 +128,50 @@ FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body)
if (BlueprintName.IsEmpty() || SourceNodeId.IsEmpty() || SourcePinName.IsEmpty() ||
TargetNodeId.IsEmpty() || TargetPinName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UEdGraph* SourceGraph = nullptr;
UEdGraphNode* SourceNode = FindNodeByGuid(BP, SourceNodeId, &SourceGraph);
if (!SourceNode)
{
return MakeErrorJson(FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId));
return MakeErrorJson(Result, FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId));
}
UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId);
if (!TargetNode)
{
return MakeErrorJson(FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId));
return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId));
}
UEdGraphPin* SourcePin = SourceNode->FindPin(FName(*SourcePinName));
if (!SourcePin)
{
return MakeErrorJson(FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId));
return MakeErrorJson(Result, FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId));
}
UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName));
if (!TargetPin)
{
return MakeErrorJson(FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId));
return MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId));
}
const UEdGraphSchema* Schema = SourceGraph ? SourceGraph->GetSchema() : nullptr;
if (!Schema)
{
return MakeErrorJson(TEXT("Graph schema not found"));
return MakeErrorJson(Result, TEXT("Graph schema not found"));
}
// Check compatibility using the schema
const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
@@ -236,22 +219,14 @@ FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body)
Result->SetStringField(TEXT("targetPinType"), TargetPin->PinType.PinCategory.ToString());
if (TargetPin->PinType.PinSubCategoryObject.IsValid())
Result->SetStringField(TEXT("targetPinSubtype"), TargetPin->PinType.PinSubCategoryObject->GetName());
return JsonToString(Result);
}
// ============================================================
// HandleListClasses — discover available UClasses
// ============================================================
FString FBlueprintMCPServer::HandleListClasses(const FString& Body)
void FBlueprintMCPServer::HandleListClasses(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString Filter = Json->GetStringField(TEXT("filter"));
FString ParentClassName = Json->GetStringField(TEXT("parentClass"));
int32 Limit = 100;
@@ -273,7 +248,7 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body)
}
if (!ParentClass)
{
return MakeErrorJson(FString::Printf(TEXT("Parent class '%s' not found"), *ParentClassName));
return MakeErrorJson(Result, FString::Printf(TEXT("Parent class '%s' not found"), *ParentClassName));
}
}
@@ -336,7 +311,6 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body)
ClassList.Add(MakeShared<FJsonValueObject>(ClassObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetNumberField(TEXT("count"), ClassList.Num());
Result->SetNumberField(TEXT("totalMatched"), TotalMatched);
@@ -346,27 +320,20 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body)
Result->SetNumberField(TEXT("limit"), Limit);
}
Result->SetArrayField(TEXT("classes"), ClassList);
return JsonToString(Result);
}
// ============================================================
// HandleListFunctions — list Blueprint-callable functions on a class
// ============================================================
FString FBlueprintMCPServer::HandleListFunctions(const FString& Body)
void FBlueprintMCPServer::HandleListFunctions(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString ClassName = Json->GetStringField(TEXT("className"));
FString Filter = Json->GetStringField(TEXT("filter"));
if (ClassName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: className"));
return MakeErrorJson(Result, TEXT("Missing required field: className"));
}
// Find the class
@@ -381,7 +348,7 @@ FString FBlueprintMCPServer::HandleListFunctions(const FString& Body)
}
if (!FoundClass)
{
return MakeErrorJson(FString::Printf(TEXT("Class '%s' not found"), *ClassName));
return MakeErrorJson(Result, FString::Printf(TEXT("Class '%s' not found"), *ClassName));
}
TArray<TSharedPtr<FJsonValue>> FuncList;
@@ -453,32 +420,24 @@ FString FBlueprintMCPServer::HandleListFunctions(const FString& Body)
FuncList.Add(MakeShared<FJsonValueObject>(FuncObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("className"), FoundClass->GetName());
Result->SetNumberField(TEXT("count"), FuncList.Num());
Result->SetArrayField(TEXT("functions"), FuncList);
return JsonToString(Result);
}
// ============================================================
// HandleListProperties — list properties on a class
// ============================================================
FString FBlueprintMCPServer::HandleListProperties(const FString& Body)
void FBlueprintMCPServer::HandleListProperties(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString ClassName = Json->GetStringField(TEXT("className"));
FString Filter = Json->GetStringField(TEXT("filter"));
if (ClassName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: className"));
return MakeErrorJson(Result, TEXT("Missing required field: className"));
}
// Find the class
@@ -493,7 +452,7 @@ FString FBlueprintMCPServer::HandleListProperties(const FString& Body)
}
if (!FoundClass)
{
return MakeErrorJson(FString::Printf(TEXT("Class '%s' not found"), *ClassName));
return MakeErrorJson(Result, FString::Printf(TEXT("Class '%s' not found"), *ClassName));
}
TArray<TSharedPtr<FJsonValue>> PropList;
@@ -540,10 +499,8 @@ FString FBlueprintMCPServer::HandleListProperties(const FString& Body)
PropList.Add(MakeShared<FJsonValueObject>(PropObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("className"), FoundClass->GetName());
Result->SetNumberField(TEXT("count"), PropList.Num());
Result->SetArrayField(TEXT("properties"), PropList);
return JsonToString(Result);
}

View File

@@ -14,20 +14,14 @@
// HandleAddEventDispatcher — create a multicast delegate on a Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
void FBlueprintMCPServer::HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString DispatcherName = Json->GetStringField(TEXT("dispatcherName"));
if (BlueprintName.IsEmpty() || DispatcherName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, dispatcherName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, dispatcherName"));
}
// Load Blueprint
@@ -35,7 +29,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
FName DispatcherFName(*DispatcherName);
@@ -45,7 +39,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
{
if (Var.VarName == DispatcherFName)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A variable or dispatcher named '%s' already exists in Blueprint '%s'"),
*DispatcherName, *BlueprintName));
}
@@ -58,7 +52,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
{
if (Existing && Existing->GetName().Equals(DispatcherName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A graph named '%s' already exists in Blueprint '%s'"),
*DispatcherName, *BlueprintName));
}
@@ -73,7 +67,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
bool bVarAdded = FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType);
if (!bVarAdded)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Failed to add delegate variable for '%s'"), *DispatcherName));
}
@@ -84,7 +78,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!SigGraph)
{
return MakeErrorJson(TEXT("Failed to create delegate signature graph"));
return MakeErrorJson(Result, TEXT("Failed to create delegate signature graph"));
}
K2Schema->CreateDefaultNodesForGraph(*SigGraph);
@@ -121,7 +115,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
// Still save what we have
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
SaveBlueprintPackage(BP);
return MakeErrorJson(TEXT("Event dispatcher created but entry node not found — parameters could not be added"));
return MakeErrorJson(Result, TEXT("Event dispatcher created but entry node not found — parameters could not be added"));
}
for (const TSharedPtr<FJsonValue>& ParamVal : ParamsArr)
@@ -138,7 +132,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(ParamType, PinType, TypeError))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Parameter '%s': %s"), *ParamName, *TypeError));
}
@@ -157,38 +151,30 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added event dispatcher '%s' to '%s' with %d params (saved: %s)"),
*DispatcherName, *BlueprintName, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("dispatcherName"), DispatcherName);
Result->SetArrayField(TEXT("parameters"), AddedParamsJson);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleListEventDispatchers — list all event dispatchers on a Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleListEventDispatchers(const FString& Body)
void FBlueprintMCPServer::HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
TSet<FName> DelegateNameSet;
@@ -237,9 +223,7 @@ FString FBlueprintMCPServer::HandleListEventDispatchers(const FString& Body)
DispatchersArr.Add(MakeShared<FJsonValueObject>(DispObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetNumberField(TEXT("count"), DispatchersArr.Num());
Result->SetArrayField(TEXT("dispatchers"), DispatchersArr);
return JsonToString(Result);
}

View File

@@ -19,20 +19,14 @@
// HandleReparentBlueprint — change a Blueprint's parent class
// ============================================================
FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body)
void FBlueprintMCPServer::HandleReparentBlueprint(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString NewParentName = Json->GetStringField(TEXT("newParentClass"));
if (BlueprintName.IsEmpty() || NewParentName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, newParentClass"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, newParentClass"));
}
// Load Blueprint
@@ -40,7 +34,7 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None");
@@ -72,7 +66,7 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body)
if (!NewParentClass)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Could not find class '%s'. Provide a C++ class name (e.g. 'WebUIHUD') or Blueprint name."),
*NewParentName));
}
@@ -107,27 +101,19 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"),
bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("oldParentClass"), OldParentName);
Result->SetStringField(TEXT("newParentClass"), NewParentActualName);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleCreateBlueprint — create a new Blueprint asset
// ============================================================
FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
void FBlueprintMCPServer::HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprintName"));
FString PackagePath = Json->GetStringField(TEXT("packagePath"));
FString ParentClassName = Json->GetStringField(TEXT("parentClass"));
@@ -135,20 +121,20 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
if (BlueprintName.IsEmpty() || PackagePath.IsEmpty() || ParentClassName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprintName, packagePath, parentClass"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprintName, packagePath, parentClass"));
}
// Validate packagePath starts with /Game
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(TEXT("packagePath must start with '/Game'"));
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
FString FullAssetPath = PackagePath / BlueprintName;
if (FindBlueprintAsset(BlueprintName) || FindBlueprintAsset(FullAssetPath))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
*BlueprintName));
}
@@ -177,7 +163,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
if (!ParentClass)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Could not find parent class '%s'. Provide a C++ class name (e.g. 'Actor', 'Pawn') or Blueprint name."),
*ParentClassName));
}
@@ -200,7 +186,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
}
else if (BlueprintTypeStr != TEXT("Normal"))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Invalid blueprintType '%s'. Valid values: Normal, Interface, FunctionLibrary, MacroLibrary"),
*BlueprintTypeStr));
}
@@ -221,7 +207,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
UPackage* Package = CreatePackage(*FullPackagePath);
if (!Package)
{
return MakeErrorJson(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
}
// Create the Blueprint
@@ -236,7 +222,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
if (!NewBP)
{
return MakeErrorJson(TEXT("FKismetEditorUtilities::CreateBlueprint returned null"));
return MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null"));
}
// Compile
@@ -268,7 +254,6 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"),
*BlueprintName, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprintName"), BlueprintName);
Result->SetStringField(TEXT("packagePath"), PackagePath);
@@ -277,33 +262,26 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body)
Result->SetStringField(TEXT("blueprintType"), BlueprintTypeStr.IsEmpty() ? TEXT("Normal") : BlueprintTypeStr);
Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetArrayField(TEXT("graphs"), GraphNames);
return JsonToString(Result);
}
// ============================================================
// HandleCreateGraph — create a new function, macro, or custom event graph
// ============================================================
FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
void FBlueprintMCPServer::HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graphName"));
FString GraphType = Json->GetStringField(TEXT("graphType"));
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || GraphType.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName, graphType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName, graphType"));
}
if (GraphType != TEXT("function") && GraphType != TEXT("macro") && GraphType != TEXT("customEvent"))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Invalid graphType '%s'. Valid values: function, macro, customEvent"), *GraphType));
}
@@ -312,7 +290,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Check graph name uniqueness
@@ -322,7 +300,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
{
if (Existing && Existing->GetName().Equals(GraphName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A graph named '%s' already exists in Blueprint '%s'"), *GraphName, *BlueprintName));
}
}
@@ -339,7 +317,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
{
if (CE->CustomFunctionName == FName(*GraphName))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A custom event named '%s' already exists in Blueprint '%s'"), *GraphName, *BlueprintName));
}
}
@@ -358,7 +336,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!NewGraph)
{
return MakeErrorJson(TEXT("Failed to create function graph"));
return MakeErrorJson(Result, TEXT("Failed to create function graph"));
}
FBlueprintEditorUtils::AddFunctionGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromObject=*/static_cast<UClass*>(nullptr));
}
@@ -368,7 +346,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!NewGraph)
{
return MakeErrorJson(TEXT("Failed to create macro graph"));
return MakeErrorJson(Result, TEXT("Failed to create macro graph"));
}
FBlueprintEditorUtils::AddMacroGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromClass=*/nullptr);
}
@@ -382,7 +360,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
}
if (!EventGraph)
{
return MakeErrorJson(TEXT("Blueprint has no EventGraph to add a custom event to"));
return MakeErrorJson(Result, TEXT("Blueprint has no EventGraph to add a custom event to"));
}
// Create a custom event node in the EventGraph
@@ -402,7 +380,6 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"),
*GraphType, *GraphName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("graphName"), GraphName);
@@ -412,27 +389,28 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body)
{
Result->SetStringField(TEXT("nodeId"), CreatedNodeId);
}
return JsonToString(Result);
}
// ============================================================
// HandleDeleteGraph — delete a function or macro graph
// ============================================================
FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body)
void FBlueprintMCPServer::HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid()) return MakeErrorJson(TEXT("Invalid JSON body"));
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graphName"));
if (BlueprintName.IsEmpty() || GraphName.IsEmpty())
return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName"));
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
// Find the graph
UEdGraph* TargetGraph = nullptr;
@@ -467,12 +445,12 @@ FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body)
{
if (Graph && Graph->GetName().Equals(GraphName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."),
*GraphName));
}
}
return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting %s graph '%s' from Blueprint '%s'"),
@@ -490,42 +468,42 @@ FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"),
*GraphName, NodeCount, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("graphName"), GraphName);
Result->SetStringField(TEXT("graphType"), GraphType);
Result->SetNumberField(TEXT("nodeCount"), NodeCount);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleRenameGraph — rename a function or macro graph
// ============================================================
FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body)
void FBlueprintMCPServer::HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid()) return MakeErrorJson(TEXT("Invalid JSON body"));
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString GraphName = Json->GetStringField(TEXT("graphName"));
FString NewName = Json->GetStringField(TEXT("newName"));
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || NewName.IsEmpty())
return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName, newName"));
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName, newName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP) return MakeErrorJson(LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
}
// Check if it's an UbergraphPage — disallow rename
for (UEdGraph* Graph : BP->UbergraphPages)
{
if (Graph && Graph->GetName().Equals(GraphName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."),
*GraphName));
}
@@ -558,7 +536,9 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body)
}
if (!TargetGraph)
return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName));
{
return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName));
}
// Check for name collision
TArray<UEdGraph*> AllGraphs;
@@ -567,7 +547,7 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body)
{
if (Existing && Existing != TargetGraph && Existing->GetName().Equals(NewName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("A graph named '%s' already exists in Blueprint '%s'"), *NewName, *BlueprintName));
}
}
@@ -583,12 +563,10 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"),
*GraphName, *NewName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("oldName"), GraphName);
Result->SetStringField(TEXT("newName"), TargetGraph->GetName());
Result->SetStringField(TEXT("graphType"), GraphType);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -12,25 +12,19 @@
// HandleListInterfaces — list implemented interfaces on a Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleListInterfaces(const FString& Body)
void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
TArray<TSharedPtr<FJsonValue>> InterfacesArr;
@@ -59,38 +53,30 @@ FString FBlueprintMCPServer::HandleListInterfaces(const FString& Body)
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
Result->SetArrayField(TEXT("interfaces"), InterfacesArr);
return JsonToString(Result);
}
// ============================================================
// HandleAddInterface — add a Blueprint Interface implementation
// ============================================================
FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString InterfaceName = Json->GetStringField(TEXT("interfaceName"));
if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, interfaceName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Resolve the interface class
@@ -137,7 +123,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
if (!InterfaceClass)
{
return MakeErrorJson(FString::Printf(
return 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));
}
@@ -147,7 +133,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
{
if (IfaceDesc.Interface == InterfaceClass)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is already implemented by Blueprint '%s'"),
*InterfaceName, *BlueprintName));
}
@@ -162,7 +148,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
if (!bAdded)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"),
*InterfaceName, *BlueprintName));
}
@@ -190,7 +176,6 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
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"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
@@ -203,27 +188,20 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body)
}
Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleRemoveInterface — remove a Blueprint Interface implementation
// ============================================================
FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body)
void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString InterfaceName = Json->GetStringField(TEXT("interfaceName"));
if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, interfaceName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName"));
}
bool bPreserveFunctions = false;
@@ -236,7 +214,7 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Find the interface in ImplementedInterfaces by name (case-insensitive)
@@ -279,12 +257,11 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body)
}
}
TSharedRef<FJsonObject> ErrorResult = MakeShared<FJsonObject>();
ErrorResult->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Interface '%s' is not implemented by Blueprint '%s'"),
*InterfaceName, *BlueprintName));
ErrorResult->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
return JsonToString(ErrorResult);
Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
return;
}
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
@@ -300,11 +277,9 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s' (saved: %s)"),
*FoundInterface->GetName(), *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
Result->SetBoolField(TEXT("preservedFunctions"), bPreserveFunctions);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -20,34 +20,28 @@
// HandleCreateMaterialInstance — create a new Material Instance Constant
// ============================================================
FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body)
void FBlueprintMCPServer::HandleCreateMaterialInstance(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString Name = Json->GetStringField(TEXT("name"));
FString PackagePath = Json->GetStringField(TEXT("packagePath"));
FString ParentMaterialName = Json->GetStringField(TEXT("parentMaterial"));
if (Name.IsEmpty() || PackagePath.IsEmpty() || ParentMaterialName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: name, packagePath, parentMaterial"));
return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, parentMaterial"));
}
// Validate packagePath starts with /Game
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(TEXT("packagePath must start with '/Game'"));
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
FString FullAssetPath = PackagePath / Name;
if (FindMaterialInstanceAsset(Name) || FindMaterialInstanceAsset(FullAssetPath))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Material Instance '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
@@ -80,7 +74,7 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body)
if (!ParentMaterial)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Parent material '%s' not found. Provide a Material or Material Instance name/path."),
*ParentMaterialName));
}
@@ -95,13 +89,13 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body)
UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialInstanceConstant::StaticClass(), Factory);
if (!NewAsset)
{
return MakeErrorJson(FString::Printf(TEXT("Failed to create Material Instance asset '%s' in '%s'"), *Name, *PackagePath));
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create Material Instance asset '%s' in '%s'"), *Name, *PackagePath));
}
UMaterialInstanceConstant* MI = Cast<UMaterialInstanceConstant>(NewAsset);
if (!MI)
{
return MakeErrorJson(TEXT("Created asset is not a UMaterialInstanceConstant"));
return MakeErrorJson(Result, TEXT("Created asset is not a UMaterialInstanceConstant"));
}
// Set parent
@@ -120,38 +114,30 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Instance '%s' with parent '%s' (saved: %s)"),
*Name, *ParentMaterial->GetName(), bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("name"), Name);
Result->SetStringField(TEXT("path"), MI->GetPathName());
Result->SetStringField(TEXT("parent"), ParentMaterial->GetPathName());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleSetMaterialInstanceParameter — set a parameter override on an MI
// ============================================================
FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& Body)
void FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString MIName = Json->GetStringField(TEXT("materialInstance"));
FString ParamName = Json->GetStringField(TEXT("parameterName"));
if (MIName.IsEmpty() || ParamName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: materialInstance, parameterName"));
return MakeErrorJson(Result, TEXT("Missing required fields: materialInstance, parameterName"));
}
if (!Json->HasField(TEXT("value")))
{
return MakeErrorJson(TEXT("Missing required field: value"));
return MakeErrorJson(Result, TEXT("Missing required field: value"));
}
bool bDryRun = false;
@@ -165,7 +151,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(MIName, LoadError);
if (!MI)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Determine the parameter type — explicit or auto-detect from parent
@@ -237,7 +223,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
if (TypeStr.IsEmpty())
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Could not determine parameter type for '%s'. Specify the 'type' field explicitly (scalar, vector, texture, staticSwitch)."),
*ParamName));
}
@@ -266,7 +252,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
const TSharedPtr<FJsonObject>* ValueObj = nullptr;
if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid())
{
return MakeErrorJson(TEXT("For vector parameters, 'value' must be an object with r, g, b (and optional a) fields."));
return MakeErrorJson(Result, TEXT("For vector parameters, 'value' must be an object with r, g, b (and optional a) fields."));
}
double R = (*ValueObj)->GetNumberField(TEXT("r"));
@@ -288,13 +274,13 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
FString TexturePath = Json->GetStringField(TEXT("value"));
if (TexturePath.IsEmpty())
{
return MakeErrorJson(TEXT("For texture parameters, 'value' must be a texture asset path string."));
return MakeErrorJson(Result, TEXT("For texture parameters, 'value' must be a texture asset path string."));
}
UTexture* TextureObj = LoadObject<UTexture>(nullptr, *TexturePath);
if (!TextureObj)
{
return MakeErrorJson(FString::Printf(TEXT("Could not load texture at path '%s'"), *TexturePath));
return MakeErrorJson(Result, FString::Printf(TEXT("Could not load texture at path '%s'"), *TexturePath));
}
if (!bDryRun)
@@ -342,7 +328,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
}
else
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Unknown parameter type '%s'. Valid types: scalar, vector, texture, staticSwitch"),
*TypeStr));
}
@@ -359,7 +345,6 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
bDryRun ? TEXT("[DRY RUN] Would set") : TEXT("Set"),
*ParamName, *NewValueDescription, *MIName);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("materialInstance"), MIName);
Result->SetStringField(TEXT("parameterName"), ParamName);
@@ -369,29 +354,27 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B
{
Result->SetBoolField(TEXT("dryRun"), true);
}
return JsonToString(Result);
}
// ============================================================
// HandleGetMaterialInstanceParameters — list all parameters on an MI
// ============================================================
FString FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const FJsonObject* Json, FJsonObject* Result)
{
const FString* NameParam = Params.Find(TEXT("name"));
if (!NameParam || NameParam->IsEmpty())
FString NameParam = Json->GetStringField(TEXT("name"));
if (NameParam.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required query parameter: name"));
return MakeErrorJson(Result, TEXT("Missing required query parameter: name"));
}
FString LoadError;
UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(*NameParam, LoadError);
UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(NameParam, LoadError);
if (!MI)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("name"), MI->GetName());
Result->SetStringField(TEXT("path"), MI->GetPathName());
@@ -610,28 +593,20 @@ FString FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const TMap<FStr
Result->SetArrayField(TEXT("vectorParameters"), VectorArr);
Result->SetArrayField(TEXT("textureParameters"), TextureArr);
Result->SetArrayField(TEXT("staticSwitchParameters"), StaticSwitchArr);
return JsonToString(Result);
}
// ============================================================
// HandleReparentMaterialInstance — change parent of an MI
// ============================================================
FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
void FBlueprintMCPServer::HandleReparentMaterialInstance(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString MIName = Json->GetStringField(TEXT("materialInstance"));
FString NewParentName = Json->GetStringField(TEXT("newParent"));
if (MIName.IsEmpty() || NewParentName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: materialInstance, newParent"));
return MakeErrorJson(Result, TEXT("Missing required fields: materialInstance, newParent"));
}
bool bDryRun = false;
@@ -645,7 +620,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(MIName, LoadError);
if (!MI)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Capture old parent
@@ -679,7 +654,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
if (!NewParent)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("New parent material '%s' not found. Provide a Material or Material Instance name/path."),
*NewParentName));
}
@@ -691,7 +666,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
{
if (Check == MI)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Cannot reparent '%s' to '%s' — this would create a circular parent chain."),
*MIName, *NewParentName));
}
@@ -723,7 +698,6 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
*MIName, bSaved ? TEXT("true") : TEXT("false"));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("materialInstance"), MIName);
Result->SetStringField(TEXT("oldParent"), OldParentPath);
@@ -732,5 +706,4 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body)
{
Result->SetBoolField(TEXT("dryRun"), true);
}
return JsonToString(Result);
}

View File

@@ -34,13 +34,13 @@
// HandleListMaterials — list Material and MaterialInstance assets
// ============================================================
FString FBlueprintMCPServer::HandleListMaterials(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleListMaterials(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Filter = Params.Find(TEXT("filter"));
const FString* TypeFilter = Params.Find(TEXT("type"));
FString Filter = Json->GetStringField(TEXT("filter"));
FString TypeFilter = Json->GetStringField(TEXT("type"));
bool bIncludeMaterials = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("material");
bool bIncludeInstances = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("instance");
bool bIncludeMaterials = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("material");
bool bIncludeInstances = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("instance");
TArray<TSharedPtr<FJsonValue>> Entries;
@@ -51,10 +51,10 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap<FString, FString>& P
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (Filter && !Filter->IsEmpty())
if (!Filter.IsEmpty())
{
if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(*Filter, ESearchCase::IgnoreCase))
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -75,10 +75,10 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap<FString, FString>& P
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (Filter && !Filter->IsEmpty())
if (!Filter.IsEmpty())
{
if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(*Filter, ESearchCase::IgnoreCase))
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -94,26 +94,24 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap<FString, FString>& P
int32 Total = AllMaterialAssets.Num() + AllMaterialInstanceAssets.Num();
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), Total);
Result->SetArrayField(TEXT("materials"), Entries);
return JsonToString(Result);
}
// ============================================================
// HandleGetMaterial — detailed info about a material or instance
// ============================================================
FString FBlueprintMCPServer::HandleGetMaterial(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetMaterial(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
if (!Name || Name->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
if (Name.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
}
FString DecodedName = UrlDecode(*Name);
FString DecodedName = UrlDecode(Name);
// Try loading as UMaterial first
FString LoadError;
@@ -122,7 +120,6 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap<FString, FString>& Par
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *Material->GetName());
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("name"), Material->GetName());
Result->SetStringField(TEXT("path"), Material->GetPathName());
Result->SetStringField(TEXT("type"), TEXT("Material"));
@@ -268,7 +265,7 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap<FString, FString>& Par
}
Result->SetNumberField(TEXT("textureSampleCount"), TextureSampleCount);
return JsonToString(Result);
return;
}
// Try loading as MaterialInstance
@@ -278,7 +275,6 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap<FString, FString>& Par
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName());
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("name"), MI->GetName());
Result->SetStringField(TEXT("path"), MI->GetPathName());
Result->SetStringField(TEXT("type"), TEXT("MaterialInstance"));
@@ -343,31 +339,31 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap<FString, FString>& Par
Result->SetArrayField(TEXT("overriddenParameters"), OverriddenParams);
return JsonToString(Result);
return;
}
return MakeErrorJson(FString::Printf(TEXT("Material or MaterialInstance '%s' not found. Use list_materials to see available assets."), *DecodedName));
MakeErrorJson(Result, FString::Printf(TEXT("Material or MaterialInstance '%s' not found. Use list_materials to see available assets."), *DecodedName));
}
// ============================================================
// HandleGetMaterialGraph — serialized graph for a material
// ============================================================
FString FBlueprintMCPServer::HandleGetMaterialGraph(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetMaterialGraph(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
if (!Name || Name->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
if (Name.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
}
FString DecodedName = UrlDecode(*Name);
FString DecodedName = UrlDecode(Name);
FString LoadError;
UMaterial* Material = LoadMaterialByName(DecodedName, LoadError);
if (!Material)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialGraph — material '%s'"), *Material->GetName());
@@ -385,45 +381,39 @@ FString FBlueprintMCPServer::HandleGetMaterialGraph(const TMap<FString, FString>
if (!Material->MaterialGraph)
{
return MakeErrorJson(TEXT("Could not build MaterialGraph for this material"));
return MakeErrorJson(Result, TEXT("Could not build MaterialGraph for this material"));
}
TSharedPtr<FJsonObject> GraphJson = SerializeGraph(Material->MaterialGraph);
if (!GraphJson.IsValid())
{
return MakeErrorJson(TEXT("Failed to serialize material graph"));
return MakeErrorJson(Result, TEXT("Failed to serialize material graph"));
}
// Add material name context
GraphJson->SetStringField(TEXT("material"), Material->GetName());
GraphJson->SetStringField(TEXT("materialPath"), Material->GetPathName());
CopyJsonFields(GraphJson.Get(), Result);
return JsonToString(GraphJson.ToSharedRef());
// Add material name context
Result->SetStringField(TEXT("material"), Material->GetName());
Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
}
// ============================================================
// HandleDescribeMaterial — human-readable material description
// ============================================================
FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body)
void FBlueprintMCPServer::HandleDescribeMaterial(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString MaterialName = Json->GetStringField(TEXT("material"));
if (MaterialName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: material"));
return MakeErrorJson(Result, TEXT("Missing required field: material"));
}
FString LoadError;
UMaterial* Material = LoadMaterialByName(MaterialName, LoadError);
if (!Material)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *Material->GetName());
@@ -439,7 +429,7 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body)
if (!Material->MaterialGraph)
{
return MakeErrorJson(TEXT("Could not build MaterialGraph for this material"));
return MakeErrorJson(Result, TEXT("Could not build MaterialGraph for this material"));
}
// Recursive helper: trace backwards from a pin and build a description string
@@ -559,7 +549,7 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body)
if (!RootNode)
{
return MakeErrorJson(TEXT("Could not find root node in material graph"));
return MakeErrorJson(Result, TEXT("Could not find root node in material graph"));
}
for (UEdGraphPin* Pin : RootNode->Pins)
@@ -585,7 +575,6 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body)
InputDescriptions.Add(MakeShared<FJsonValueObject>(InputObj));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), Material->GetName());
Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
@@ -609,28 +598,26 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body)
{
Result->SetStringField(TEXT("description"), TextDesc);
}
return JsonToString(Result);
}
// ============================================================
// HandleSearchMaterials — search expressions and parameters
// ============================================================
FString FBlueprintMCPServer::HandleSearchMaterials(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleSearchMaterials(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Query = Params.Find(TEXT("query"));
if (!Query || Query->IsEmpty())
FString Query = Json->GetStringField(TEXT("query"));
if (Query.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'query' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'query' parameter"));
}
FString DecodedQuery = UrlDecode(*Query);
FString DecodedQuery = UrlDecode(Query);
int32 MaxResults = 50;
if (const FString* M = Params.Find(TEXT("maxResults")))
if (Json->HasField(TEXT("maxResults")))
{
MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 200);
MaxResults = FMath::Clamp((int32)Json->GetNumberField(TEXT("maxResults")), 1, 200);
}
TArray<TSharedPtr<FJsonValue>> Results;
@@ -698,29 +685,21 @@ FString FBlueprintMCPServer::HandleSearchMaterials(const TMap<FString, FString>&
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("query"), DecodedQuery);
Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results);
return JsonToString(Result);
}
// ============================================================
// HandleFindMaterialReferences — find assets referencing a material
// ============================================================
FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body)
void FBlueprintMCPServer::HandleFindMaterialReferences(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString MaterialName = Json->GetStringField(TEXT("material"));
if (MaterialName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: material"));
return MakeErrorJson(Result, TEXT("Missing required field: material"));
}
// Try to find the material's package path
@@ -742,7 +721,7 @@ FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body)
if (PackagePath.IsEmpty())
{
return MakeErrorJson(FString::Printf(TEXT("Material '%s' not found. Use list_materials to see available assets."), *MaterialName));
return MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' not found. Use list_materials to see available assets."), *MaterialName));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindMaterialReferences — '%s' (package: %s)"), *MaterialName, *PackagePath);
@@ -761,22 +740,20 @@ FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body)
RefArray.Add(MakeShared<FJsonValueString>(RefStr));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("material"), MaterialName);
Result->SetStringField(TEXT("packagePath"), PackagePath);
Result->SetNumberField(TEXT("totalReferencers"), RefArray.Num());
Result->SetArrayField(TEXT("referencers"), RefArray);
return JsonToString(Result);
}
// ============================================================
// HandleListMaterialFunctions — list MaterialFunction assets
// ============================================================
FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleListMaterialFunctions(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Filter = Params.Find(TEXT("filter"));
FString Filter = Json->GetStringField(TEXT("filter"));
TArray<TSharedPtr<FJsonValue>> Entries;
@@ -785,10 +762,10 @@ FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMap<FString, FSt
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (Filter && !Filter->IsEmpty())
if (!Filter.IsEmpty())
{
if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(*Filter, ESearchCase::IgnoreCase))
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -800,37 +777,34 @@ FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMap<FString, FSt
Entries.Add(MakeShared<FJsonValueObject>(Entry));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), AllMaterialFunctionAssets.Num());
Result->SetArrayField(TEXT("functions"), Entries);
return JsonToString(Result);
}
// ============================================================
// HandleGetMaterialFunction — detailed info about a material function
// ============================================================
FString FBlueprintMCPServer::HandleGetMaterialFunction(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetMaterialFunction(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
if (!Name || Name->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
if (Name.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
}
FString DecodedName = UrlDecode(*Name);
FString DecodedName = UrlDecode(Name);
FString LoadError;
UMaterialFunction* MF = LoadMaterialFunctionByName(DecodedName, LoadError);
if (!MF)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName());
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("name"), MF->GetName());
Result->SetStringField(TEXT("path"), MF->GetPathName());
Result->SetStringField(TEXT("description"), MF->GetDescription());
@@ -891,26 +865,18 @@ FString FBlueprintMCPServer::HandleGetMaterialFunction(const TMap<FString, FStri
Result->SetObjectField(TEXT("graph"), GraphJson);
}
}
return JsonToString(Result);
}
// ============================================================
// HandleValidateMaterial — force recompile and check for errors
// ============================================================
FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body)
void FBlueprintMCPServer::HandleValidateMaterial(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString MaterialName = Json->GetStringField(TEXT("material"));
if (MaterialName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: material"));
return MakeErrorJson(Result, TEXT("Missing required field: material"));
}
// Load material
@@ -918,7 +884,7 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body)
UMaterial* Material = LoadMaterialByName(MaterialName, LoadError);
if (!Material)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *Material->GetName());
@@ -962,7 +928,6 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body)
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("valid"), bValid);
Result->SetStringField(TEXT("material"), Material->GetName());
Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
@@ -973,6 +938,4 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Material '%s' validation %s (%d errors)"),
*Material->GetName(), bValid ? TEXT("passed") : TEXT("failed"), ErrorArray.Num());
return JsonToString(Result);
}

View File

@@ -13,14 +13,8 @@
#include "Serialization/JsonSerializer.h"
#include "UObject/UObjectIterator.h"
FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
void FBlueprintMCPServer::HandleChangeFunctionParamType(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString FunctionName = Json->GetStringField(TEXT("functionName"));
FString ParamName = Json->GetStringField(TEXT("paramName"));
@@ -28,7 +22,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty() || NewTypeName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName, newType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName, newType"));
}
// Load Blueprint
@@ -36,7 +30,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references)
@@ -44,7 +38,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(NewTypeName, NewPinType, TypeError))
{
return MakeErrorJson(TypeError);
return MakeErrorJson(Result, TypeError);
}
// Find the entry node: K2Node_FunctionEntry in a function graph,
@@ -117,12 +111,11 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Function or custom event '%s' not found in Blueprint '%s'"),
*FunctionName, *BlueprintName));
E->SetArrayField(TEXT("availableFunctionsAndEvents"), Available);
return JsonToString(E);
Result->SetArrayField(TEXT("availableFunctionsAndEvents"), Available);
return;
}
// Find the UserDefinedPin matching paramName
@@ -149,12 +142,11 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Parameter '%s' not found in %s '%s'"),
*ParamName, *FoundNodeType, *FunctionName));
E->SetArrayField(TEXT("availableParams"), ParamNames);
return JsonToString(E);
Result->SetArrayField(TEXT("availableParams"), ParamNames);
return;
}
// Check for dry run
@@ -189,7 +181,6 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("functionName"), FunctionName);
@@ -199,7 +190,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString());
Result->SetNumberField(TEXT("connectionsAtRisk"), AffectedPins.Num());
Result->SetArrayField(TEXT("affectedPins"), AffectedPins);
return JsonToString(Result);
return;
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Changing param '%s' in %s '%s' of '%s' to %s"),
@@ -223,7 +214,6 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
// Serialize the updated entry node state
TSharedPtr<FJsonObject> UpdatedNodeState = SerializeNode(EntryNode);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("functionName"), FunctionName);
@@ -236,28 +226,21 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body)
{
Result->SetObjectField(TEXT("updatedNode"), UpdatedNodeState);
}
return JsonToString(Result);
}
// ============================================================
// HandleRemoveFunctionParameter
// ============================================================
FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
void FBlueprintMCPServer::HandleRemoveFunctionParameter(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString FunctionName = Json->GetStringField(TEXT("functionName"));
FString ParamName = Json->GetStringField(TEXT("paramName"));
if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName"));
}
// Load Blueprint
@@ -265,7 +248,7 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Find the entry node
@@ -337,12 +320,11 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Function or custom event '%s' not found in Blueprint '%s'"),
*FunctionName, *BlueprintName));
E->SetArrayField(TEXT("availableFunctionsAndEvents"), Available);
return JsonToString(E);
Result->SetArrayField(TEXT("availableFunctionsAndEvents"), Available);
return;
}
// Find and remove the UserDefinedPin matching paramName
@@ -369,12 +351,11 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Parameter '%s' not found in %s '%s'"),
*ParamName, *FoundNodeType, *FunctionName));
E->SetArrayField(TEXT("availableParams"), ParamNames);
return JsonToString(E);
Result->SetArrayField(TEXT("availableParams"), ParamNames);
return;
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing param '%s' from %s '%s' in '%s'"),
@@ -398,7 +379,6 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Parameter removed, save %s"),
bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("functionName"), FunctionName);
@@ -406,21 +386,14 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body)
Result->SetStringField(TEXT("nodeType"), FoundNodeType);
Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleAddFunctionParameter
// ============================================================
FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
void FBlueprintMCPServer::HandleAddFunctionParameter(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString FunctionName = Json->GetStringField(TEXT("functionName"));
FString ParamName = Json->GetStringField(TEXT("paramName"));
@@ -428,7 +401,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty() || ParamType.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName, paramType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName, paramType"));
}
// Load Blueprint
@@ -436,7 +409,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Resolve param type
@@ -444,7 +417,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(ParamType, PinType, TypeError))
{
return MakeErrorJson(TypeError);
return MakeErrorJson(Result, TypeError);
}
// Find the entry node using 3 strategies
@@ -559,12 +532,11 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
FString::Printf(TEXT("%s (event dispatcher)"), *DN.ToString())));
}
TSharedRef<FJsonObject> ErrorResult = MakeShared<FJsonObject>();
ErrorResult->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Function, custom event, or event dispatcher '%s' not found in Blueprint '%s'"),
*FunctionName, *BlueprintName));
ErrorResult->SetArrayField(TEXT("availableFunctions"), AvailFuncs);
return JsonToString(ErrorResult);
Result->SetArrayField(TEXT("availableFunctions"), AvailFuncs);
return;
}
// Check for duplicate parameter name
@@ -572,7 +544,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
{
if (Existing.IsValid() && Existing->PinName.ToString().Equals(ParamName, ESearchCase::IgnoreCase))
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Parameter '%s' already exists on '%s'"), *ParamName, *FunctionName));
}
}
@@ -589,7 +561,6 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added parameter '%s' to '%s' in '%s' (saved: %s)"),
*ParamName, *FunctionName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("functionName"), FunctionName);
@@ -597,5 +568,4 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body)
Result->SetStringField(TEXT("paramType"), ParamType);
Result->SetStringField(TEXT("nodeType"), NodeType);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -24,14 +24,14 @@
// Request handlers
// ============================================================
FString FBlueprintMCPServer::HandleList(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleList(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Filter = Params.Find(TEXT("filter"));
const FString* ParentClassFilter = Params.Find(TEXT("parentClass"));
const FString* TypeFilter = Params.Find(TEXT("type"));
FString Filter = Json->GetStringField(TEXT("filter"));
FString ParentClassFilter = Json->GetStringField(TEXT("parentClass"));
FString TypeFilter = Json->GetStringField(TEXT("type"));
// type: "all" (default), "regular", "level"
bool bIncludeRegular = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("regular");
bool bIncludeLevel = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("level");
bool bIncludeRegular = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("regular");
bool bIncludeLevel = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("level");
TArray<TSharedPtr<FJsonValue>> Entries;
if (bIncludeRegular)
@@ -40,10 +40,10 @@ FString FBlueprintMCPServer::HandleList(const TMap<FString, FString>& Params)
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (Filter && !Filter->IsEmpty())
if (!Filter.IsEmpty())
{
if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(*Filter, ESearchCase::IgnoreCase))
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -58,9 +58,9 @@ FString FBlueprintMCPServer::HandleList(const TMap<FString, FString>& Params)
ParentClass = ParentClass.Mid(DotIndex + 1);
}
if (ParentClassFilter && !ParentClassFilter->IsEmpty())
if (!ParentClassFilter.IsEmpty())
{
if (!ParentClass.Contains(*ParentClassFilter, ESearchCase::IgnoreCase))
if (!ParentClass.Contains(ParentClassFilter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -80,19 +80,19 @@ FString FBlueprintMCPServer::HandleList(const TMap<FString, FString>& Params)
FString Name = Asset.AssetName.ToString();
FString Path = Asset.PackageName.ToString();
if (Filter && !Filter->IsEmpty())
if (!Filter.IsEmpty())
{
if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(*Filter, ESearchCase::IgnoreCase))
if (!Name.Contains(Filter, ESearchCase::IgnoreCase) &&
!Path.Contains(Filter, ESearchCase::IgnoreCase))
{
continue;
}
}
// No parent class filter for level blueprints
if (ParentClassFilter && !ParentClassFilter->IsEmpty())
if (!ParentClassFilter.IsEmpty())
{
if (!FString(TEXT("LevelScriptActor")).Contains(*ParentClassFilter, ESearchCase::IgnoreCase))
if (!FString(TEXT("LevelScriptActor")).Contains(ParentClassFilter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -106,48 +106,47 @@ FString FBlueprintMCPServer::HandleList(const TMap<FString, FString>& Params)
Entries.Add(MakeShared<FJsonValueObject>(Entry));
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("count"), Entries.Num());
Result->SetNumberField(TEXT("total"), AllBlueprintAssets.Num() + AllMapAssets.Num());
Result->SetArrayField(TEXT("blueprints"), Entries);
return JsonToString(Result);
}
FString FBlueprintMCPServer::HandleGetBlueprint(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetBlueprint(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
if (!Name || Name->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
if (Name.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(*Name, LoadError);
UBlueprint* BP = LoadBlueprintByName(Name, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
return JsonToString(SerializeBlueprint(BP));
TSharedRef<FJsonObject> Tmp = SerializeBlueprint(BP);
CopyJsonFields(&*Tmp, Result);
}
FString FBlueprintMCPServer::HandleGetGraph(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleGetGraph(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
const FString* GraphName = Params.Find(TEXT("graph"));
if (!Name || Name->IsEmpty() || !GraphName || GraphName->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
FString GraphName = Json->GetStringField(TEXT("graph"));
if (Name.IsEmpty() || GraphName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' or 'graph' parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' or 'graph' parameter"));
}
// URL-decode graph name to handle spaces encoded as '+' or '%20'
FString DecodedGraphName = UrlDecode(*GraphName);
FString DecodedGraphName = UrlDecode(GraphName);
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(*Name, LoadError);
UBlueprint* BP = LoadBlueprintByName(Name, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
TArray<UEdGraph*> AllGraphs;
@@ -160,7 +159,8 @@ FString FBlueprintMCPServer::HandleGetGraph(const TMap<FString, FString>& Params
TSharedPtr<FJsonObject> GraphJson = SerializeGraph(Graph);
if (GraphJson.IsValid())
{
return JsonToString(GraphJson.ToSharedRef());
CopyJsonFields(GraphJson.Get(), Result);
return;
}
}
}
@@ -174,28 +174,24 @@ FString FBlueprintMCPServer::HandleGetGraph(const TMap<FString, FString>& Params
GraphNames.Add(MakeShared<FJsonValueString>(Graph->GetName()));
}
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
E->SetArrayField(TEXT("availableGraphs"), GraphNames);
return JsonToString(E);
MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
Result->SetArrayField(TEXT("availableGraphs"), GraphNames);
}
FString FBlueprintMCPServer::HandleSearch(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleSearch(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Query = Params.Find(TEXT("query"));
if (!Query || Query->IsEmpty())
FString Query = Json->GetStringField(TEXT("query"));
if (Query.IsEmpty())
{
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), TEXT("Missing 'query' parameter"));
return JsonToString(E);
return MakeErrorJson(Result, TEXT("Missing 'query' parameter"));
}
const FString* PathFilter = Params.Find(TEXT("path"));
FString PathFilter = Json->GetStringField(TEXT("path"));
int32 MaxResults = 50;
if (const FString* M = Params.Find(TEXT("maxResults")))
if (Json->HasField(TEXT("maxResults")))
{
MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 200);
MaxResults = FMath::Clamp(FCString::Atoi(*Json->GetStringField(TEXT("maxResults"))), 1, 200);
}
// Build a combined list of all searchable blueprints (regular + level)
@@ -236,10 +232,10 @@ FString FBlueprintMCPServer::HandleSearch(const TMap<FString, FString>& Params)
VarName = VS->GetVarName().ToString();
}
bool bMatch = Title.Contains(*Query, ESearchCase::IgnoreCase) ||
(!FuncName.IsEmpty() && FuncName.Contains(*Query, ESearchCase::IgnoreCase)) ||
(!EventName.IsEmpty() && EventName.Contains(*Query, ESearchCase::IgnoreCase)) ||
(!VarName.IsEmpty() && VarName.Contains(*Query, ESearchCase::IgnoreCase));
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
if (bMatch)
{
@@ -264,7 +260,7 @@ FString FBlueprintMCPServer::HandleSearch(const TMap<FString, FString>& Params)
if (Results.Num() >= MaxResults) break;
FString Path = Asset.PackageName.ToString();
if (PathFilter && !PathFilter->IsEmpty() && !Path.Contains(*PathFilter, ESearchCase::IgnoreCase))
if (!PathFilter.IsEmpty() && !Path.Contains(PathFilter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -281,7 +277,7 @@ FString FBlueprintMCPServer::HandleSearch(const TMap<FString, FString>& Params)
if (Results.Num() >= MaxResults) break;
FString Path = MapAsset.PackageName.ToString();
if (PathFilter && !PathFilter->IsEmpty() && !Path.Contains(*PathFilter, ESearchCase::IgnoreCase))
if (!PathFilter.IsEmpty() && !Path.Contains(PathFilter, ESearchCase::IgnoreCase))
{
continue;
}
@@ -300,32 +296,30 @@ FString FBlueprintMCPServer::HandleSearch(const TMap<FString, FString>& Params)
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("query"), *Query);
Result->SetStringField(TEXT("query"), Query);
Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results);
return JsonToString(Result);
}
// ============================================================
// HandleTestSave — load a Blueprint and save it unmodified (diagnostic)
// ============================================================
FString FBlueprintMCPServer::HandleTestSave(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleTestSave(const FJsonObject* Json, FJsonObject* Result)
{
const FString* Name = Params.Find(TEXT("name"));
if (!Name || Name->IsEmpty())
FString Name = Json->GetStringField(TEXT("name"));
if (Name.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'name' query parameter"));
return MakeErrorJson(Result, TEXT("Missing 'name' query parameter"));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), **Name);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Name);
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(*Name, LoadError);
UBlueprint* BP = LoadBlueprintByName(Name, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"),
@@ -335,30 +329,28 @@ FString FBlueprintMCPServer::HandleTestSave(const TMap<FString, FString>& Params
// Attempt save with NO modifications
bool bSaved = SaveBlueprintPackage(BP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("blueprint"), *Name);
Result->SetStringField(TEXT("blueprint"), Name);
Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName());
Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleFindReferences — find all Blueprints referencing an asset
// ============================================================
FString FBlueprintMCPServer::HandleFindReferences(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleFindReferences(const FJsonObject* Json, FJsonObject* Result)
{
const FString* AssetPath = Params.Find(TEXT("assetPath"));
if (!AssetPath || AssetPath->IsEmpty())
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
if (AssetPath.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'assetPath' query parameter"));
return MakeErrorJson(Result, TEXT("Missing 'assetPath' query parameter"));
}
IAssetRegistry& Registry = *IAssetRegistry::Get();
TArray<FName> Referencers;
Registry.GetReferencers(FName(**AssetPath), Referencers);
Registry.GetReferencers(FName(*AssetPath), Referencers);
// Build set of known Blueprint package names for filtering
TSet<FString> BlueprintPackages;
@@ -382,36 +374,34 @@ FString FBlueprintMCPServer::HandleFindReferences(const TMap<FString, FString>&
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("assetPath"), **AssetPath);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num());
Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num());
Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs);
Result->SetNumberField(TEXT("otherReferencerCount"), OtherRefs.Num());
Result->SetArrayField(TEXT("otherReferencers"), OtherRefs);
return JsonToString(Result);
}
// ============================================================
// HandleSearchByType — find all usages of a type across blueprints
// ============================================================
FString FBlueprintMCPServer::HandleSearchByType(const TMap<FString, FString>& Params)
void FBlueprintMCPServer::HandleSearchByType(const FJsonObject* Json, FJsonObject* Result)
{
const FString* TypeNamePtr = Params.Find(TEXT("typeName"));
if (!TypeNamePtr || TypeNamePtr->IsEmpty())
FString TypeNameRaw = Json->GetStringField(TEXT("typeName"));
if (TypeNameRaw.IsEmpty())
{
return MakeErrorJson(TEXT("Missing 'typeName' query parameter"));
return MakeErrorJson(Result, TEXT("Missing 'typeName' query parameter"));
}
FString TypeName = UrlDecode(*TypeNamePtr);
const FString* Filter = Params.Find(TEXT("filter"));
FString FilterStr = Filter ? UrlDecode(*Filter) : FString();
FString TypeName = UrlDecode(TypeNameRaw);
FString FilterRaw = Json->GetStringField(TEXT("filter"));
FString FilterStr = FilterRaw.IsEmpty() ? FString() : UrlDecode(FilterRaw);
int32 MaxResults = 200;
if (const FString* M = Params.Find(TEXT("maxResults")))
if (Json->HasField(TEXT("maxResults")))
{
MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 500);
MaxResults = FMath::Clamp(FCString::Atoi(*Json->GetStringField(TEXT("maxResults"))), 1, 500);
}
// Strip F/E/U prefix for comparison
@@ -636,9 +626,7 @@ FString FBlueprintMCPServer::HandleSearchByType(const TMap<FString, FString>& Pa
SearchOneBlueprint(MapName, Path, LevelBP, true);
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("typeName"), TypeName);
Result->SetNumberField(TEXT("resultCount"), Results.Num());
Result->SetArrayField(TEXT("results"), Results);
return JsonToString(Result);
}

View File

@@ -254,18 +254,12 @@ bool FBlueprintMCPServer::LoadSnapshotFromDisk(const FString& SnapshotId, FGraph
// HandleSnapshotGraph
// ============================================================
FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body)
void FBlueprintMCPServer::HandleSnapshotGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString GraphFilter;
@@ -276,7 +270,7 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating snapshot for blueprint '%s'"), *BlueprintName);
@@ -305,7 +299,7 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body)
if (GraphsToCapture.Num() == 0 && !GraphFilter.IsEmpty())
{
return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in blueprint '%s'"), *GraphFilter, *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in blueprint '%s'"), *GraphFilter, *BlueprintName));
}
int32 TotalConnections = 0;
@@ -336,32 +330,24 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body)
*Snapshot.SnapshotId, GraphsToCapture.Num(), TotalConnections);
// Build response
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("status"), TEXT("ok"));
Result->SetStringField(TEXT("snapshotId"), Snapshot.SnapshotId);
Result->SetStringField(TEXT("blueprint"), BP->GetName());
Result->SetArrayField(TEXT("graphs"), GraphSummaries);
Result->SetNumberField(TEXT("totalConnections"), TotalConnections);
return JsonToString(Result);
}
// ============================================================
// HandleDiffGraph
// ============================================================
FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body)
void FBlueprintMCPServer::HandleDiffGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString SnapshotId = Json->GetStringField(TEXT("snapshotId"));
if (BlueprintName.IsEmpty() || SnapshotId.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, snapshotId"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, snapshotId"));
}
FString GraphFilter;
@@ -374,7 +360,7 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body)
{
if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot))
{
return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId));
return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId));
}
SnapshotPtr = &LoadedSnapshot;
}
@@ -384,7 +370,7 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Diffing blueprint '%s' against snapshot '%s'"), *BlueprintName, *SnapshotId);
@@ -545,7 +531,6 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body)
}
// Build result
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("status"), TEXT("ok"));
Result->SetStringField(TEXT("blueprint"), BP->GetName());
Result->SetStringField(TEXT("snapshotId"), SnapshotId);
@@ -563,27 +548,19 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Diff complete — %d severed, %d new, %d type changes, %d missing nodes"),
SeveredArr.Num(), NewConnsArr.Num(), TypeChangesArr.Num(), MissingNodesArr.Num());
return JsonToString(Result);
}
// ============================================================
// HandleRestoreGraph
// ============================================================
FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body)
void FBlueprintMCPServer::HandleRestoreGraph(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString SnapshotId = Json->GetStringField(TEXT("snapshotId"));
if (BlueprintName.IsEmpty() || SnapshotId.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, snapshotId"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, snapshotId"));
}
FString GraphFilter;
@@ -602,7 +579,7 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body)
{
if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot))
{
return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId));
return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId));
}
SnapshotPtr = &LoadedSnapshot;
}
@@ -612,7 +589,7 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Restoring connections from snapshot '%s' for blueprint '%s' (dryRun=%s)"),
@@ -775,28 +752,20 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Restore complete — %d reconnected, %d failed, saved=%s"),
Reconnected, Failed, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("status"), TEXT("ok"));
Result->SetNumberField(TEXT("reconnected"), Reconnected);
Result->SetNumberField(TEXT("failed"), Failed);
Result->SetArrayField(TEXT("details"), DetailsArr);
Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetBoolField(TEXT("dryRun"), bDryRun);
return JsonToString(Result);
}
// ============================================================
// HandleFindDisconnectedPins
// ============================================================
FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body)
void FBlueprintMCPServer::HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName;
Json->TryGetStringField(TEXT("blueprint"), BlueprintName);
@@ -808,7 +777,7 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body)
if (BlueprintName.IsEmpty() && PathFilter.IsEmpty() && SnapshotId.IsEmpty())
{
return MakeErrorJson(TEXT("Provide at least one of: blueprint, filter, or snapshotId"));
return MakeErrorJson(Result, TEXT("Provide at least one of: blueprint, filter, or snapshotId"));
}
// Optionally load snapshot for definite-break detection
@@ -1086,7 +1055,6 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindDisconnectedPins complete — %d HIGH, %d MEDIUM, %d total across %d blueprints"),
HighCount, MediumCount, ResultsArr.Num(), BlueprintsScanned);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetArrayField(TEXT("results"), ResultsArr);
TSharedRef<FJsonObject> Summary = MakeShared<FJsonObject>();
@@ -1095,26 +1063,18 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body)
Summary->SetNumberField(TEXT("total"), ResultsArr.Num());
Summary->SetNumberField(TEXT("blueprintsScanned"), BlueprintsScanned);
Result->SetObjectField(TEXT("summary"), Summary);
return JsonToString(Result);
}
// ============================================================
// HandleAnalyzeRebuildImpact
// ============================================================
FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body)
void FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString ModuleName = Json->GetStringField(TEXT("moduleName"));
if (ModuleName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: moduleName"));
return MakeErrorJson(Result, TEXT("Missing required field: moduleName"));
}
// Optional struct name filter
@@ -1359,7 +1319,6 @@ FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body)
AffectedBlueprints.Num(), TotalBreakMakeNodes, TotalConnectionsAtRisk);
// Build response
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetStringField(TEXT("moduleName"), ModuleName);
Result->SetArrayField(TEXT("typesFound"), TypesFoundArr);
@@ -1384,6 +1343,4 @@ FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body)
Summary->SetNumberField(TEXT("totalBreakMakeNodes"), TotalBreakMakeNodes);
Summary->SetNumberField(TEXT("totalConnectionsAtRisk"), TotalConnectionsAtRisk);
Result->SetObjectField(TEXT("summary"), Summary);
return JsonToString(Result);
}

View File

@@ -20,18 +20,12 @@
// HandleCreateStruct — create a new UserDefinedStruct asset
// ============================================================
FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body)
void FBlueprintMCPServer::HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
if (AssetPath.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/S_MyStruct')"));
return MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/S_MyStruct')"));
}
// Split path into package path and asset name
@@ -44,12 +38,12 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body)
}
else
{
return MakeErrorJson(TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')"));
return MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')"));
}
if (AssetName.IsEmpty())
{
return MakeErrorJson(TEXT("Invalid asset name in assetPath"));
return MakeErrorJson(Result, TEXT("Invalid asset name in assetPath"));
}
// Check if asset already exists
@@ -57,7 +51,7 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body)
FAssetData ExistingAsset = ARM.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath + TEXT(".") + AssetName));
if (ExistingAsset.IsValid())
{
return MakeErrorJson(FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath));
return MakeErrorJson(Result, FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath));
}
// Create the struct using the AssetTools factory
@@ -69,13 +63,13 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body)
if (!NewAsset)
{
return MakeErrorJson(TEXT("Failed to create UserDefinedStruct asset"));
return MakeErrorJson(Result, TEXT("Failed to create UserDefinedStruct asset"));
}
UUserDefinedStruct* NewStruct = Cast<UUserDefinedStruct>(NewAsset);
if (!NewStruct)
{
return MakeErrorJson(TEXT("Created asset is not a UserDefinedStruct"));
return MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedStruct"));
}
// Add properties if specified
@@ -139,31 +133,23 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedStruct '%s' with %d properties, save %s"),
*AssetPath, PropsAdded, bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("assetName"), AssetName);
Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleCreateEnum — create a new UserDefinedEnum asset
// ============================================================
FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body)
void FBlueprintMCPServer::HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
if (AssetPath.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/E_MyEnum')"));
return MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/E_MyEnum')"));
}
// Split path
@@ -176,19 +162,19 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body)
}
else
{
return MakeErrorJson(TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')"));
return MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')"));
}
if (AssetName.IsEmpty())
{
return MakeErrorJson(TEXT("Invalid asset name in assetPath"));
return MakeErrorJson(Result, TEXT("Invalid asset name in assetPath"));
}
// Get values
const TArray<TSharedPtr<FJsonValue>>* ValuesArray = nullptr;
if (!Json->TryGetArrayField(TEXT("values"), ValuesArray) || !ValuesArray || ValuesArray->Num() == 0)
{
return MakeErrorJson(TEXT("Missing or empty required field: values (array of strings)"));
return MakeErrorJson(Result, TEXT("Missing or empty required field: values (array of strings)"));
}
TArray<FString> EnumValues;
@@ -199,7 +185,7 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body)
}
if (EnumValues.Num() == 0)
{
return MakeErrorJson(TEXT("No valid enum values provided"));
return MakeErrorJson(Result, TEXT("No valid enum values provided"));
}
// Create the enum using AssetTools
@@ -211,13 +197,13 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body)
if (!NewAsset)
{
return MakeErrorJson(TEXT("Failed to create UserDefinedEnum asset"));
return MakeErrorJson(Result, TEXT("Failed to create UserDefinedEnum asset"));
}
UUserDefinedEnum* NewEnum = Cast<UUserDefinedEnum>(NewAsset);
if (!NewEnum)
{
return MakeErrorJson(TEXT("Created asset is not a UserDefinedEnum"));
return MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedEnum"));
}
// Add enum values — UUserDefinedEnum starts with a MAX value.
@@ -241,34 +227,26 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedEnum '%s' with %d values, save %s"),
*AssetPath, EnumValues.Num(), bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("assetName"), AssetName);
Result->SetNumberField(TEXT("valueCount"), EnumValues.Num());
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleAddStructProperty — add a property to UserDefinedStruct
// ============================================================
FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body)
void FBlueprintMCPServer::HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
FString PropName = Json->GetStringField(TEXT("name"));
FString PropType = Json->GetStringField(TEXT("type"));
if (AssetPath.IsEmpty() || PropName.IsEmpty() || PropType.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: assetPath, name, type"));
return MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name, type"));
}
// Find the struct
@@ -281,7 +259,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body)
}
if (!Struct)
{
return MakeErrorJson(FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
return MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
}
// Resolve type
@@ -289,7 +267,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(PropType, PinType, TypeError))
{
return MakeErrorJson(FString::Printf(TEXT("Cannot resolve type '%s': %s"), *PropType, *TypeError));
return MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *PropType, *TypeError));
}
// Snapshot existing GUIDs so we can find the newly added one
@@ -302,7 +280,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body)
bool bAdded = FStructureEditorUtils::AddVariable(Struct, PinType);
if (!bAdded)
{
return MakeErrorJson(TEXT("Failed to add property to struct"));
return MakeErrorJson(Result, TEXT("Failed to add property to struct"));
}
// Find the new variable by diffing GUID sets and rename it
@@ -325,33 +303,25 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added property '%s' (%s) to struct '%s', save %s"),
*PropName, *PropType, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("propertyName"), PropName);
Result->SetStringField(TEXT("propertyType"), PropType);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleRemoveStructProperty — remove a property from UserDefinedStruct
// ============================================================
FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body)
void FBlueprintMCPServer::HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
FString PropName = Json->GetStringField(TEXT("name"));
if (AssetPath.IsEmpty() || PropName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: assetPath, name"));
return MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name"));
}
// Find the struct
@@ -363,7 +333,7 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body)
}
if (!Struct)
{
return MakeErrorJson(FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
return MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
}
// Find the property GUID by name
@@ -387,16 +357,15 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body)
{
AvailProps.Add(MakeShared<FJsonValueString>(Var.FriendlyName));
}
TSharedRef<FJsonObject> E = MakeShared<FJsonObject>();
E->SetStringField(TEXT("error"), FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *PropName, *AssetPath));
E->SetArrayField(TEXT("availableProperties"), AvailProps);
return JsonToString(E);
MakeErrorJson(Result, FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *PropName, *AssetPath));
Result->SetArrayField(TEXT("availableProperties"), AvailProps);
return;
}
bool bRemoved = FStructureEditorUtils::RemoveVariable(Struct, TargetGuid);
if (!bRemoved)
{
return MakeErrorJson(FString::Printf(TEXT("Failed to remove property '%s'"), *PropName));
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to remove property '%s'"), *PropName));
}
// Save
@@ -409,10 +378,8 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed property '%s' from struct '%s', save %s"),
*PropName, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("assetPath"), AssetPath);
Result->SetStringField(TEXT("removedProperty"), PropName);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -175,18 +175,12 @@ static TSharedRef<FJsonObject> ValidateSingleBlueprint(UBlueprint* BP, const FSt
// HandleValidateBlueprint — compile without saving, report errors + captured log messages
// ============================================================
FString FBlueprintMCPServer::HandleValidateBlueprint(const FString& Body)
void FBlueprintMCPServer::HandleValidateBlueprint(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required field: blueprint"));
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
// Load Blueprint
@@ -194,36 +188,25 @@ FString FBlueprintMCPServer::HandleValidateBlueprint(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating blueprint '%s'"), *BlueprintName);
TSharedRef<FJsonObject> Result = ValidateSingleBlueprint(BP, BlueprintName);
return JsonToString(Result);
TSharedRef<FJsonObject> ValidationResult = ValidateSingleBlueprint(BP, BlueprintName);
CopyJsonFields(&*ValidationResult, Result);
}
// ============================================================
// HandleValidateAllBlueprints — bulk validation
// ============================================================
FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
void FBlueprintMCPServer::HandleValidateAllBlueprints(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
// Body is optional — empty body means validate all
FString Filter;
bool bCountOnly = false;
int32 Offset = 0;
int32 Limit = 0;
if (Json.IsValid())
{
Filter = Json->GetStringField(TEXT("filter"));
bCountOnly = Json->GetBoolField(TEXT("countOnly"));
Offset = (int32)Json->GetNumberField(TEXT("offset"));
Limit = (int32)Json->GetNumberField(TEXT("limit"));
}
FString Filter = Json->GetStringField(TEXT("filter"));
bool bCountOnly = Json->GetBoolField(TEXT("countOnly"));
int32 Offset = (int32)Json->GetNumberField(TEXT("offset"));
int32 Limit = (int32)Json->GetNumberField(TEXT("limit"));
// First pass: collect matching asset indices (string comparisons only, no GetAsset())
TArray<int32> MatchingIndices;
@@ -247,13 +230,12 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
// countOnly: return count without compiling anything
if (bCountOnly)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("totalMatching"), TotalMatching);
if (!Filter.IsEmpty())
{
Result->SetStringField(TEXT("filter"), Filter);
}
return JsonToString(Result);
return;
}
// Compute range
@@ -284,13 +266,13 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
TotalChecked++;
TSharedRef<FJsonObject> Result = ValidateSingleBlueprint(BP, AssetName);
TSharedRef<FJsonObject> ValidationResult = ValidateSingleBlueprint(BP, AssetName);
bool bValid = Result->GetBoolField(TEXT("isValid"));
int32 Errors = (int32)Result->GetNumberField(TEXT("errorCount"));
int32 Warnings = (int32)Result->GetNumberField(TEXT("warningCount"));
bool bValid = ValidationResult->GetBoolField(TEXT("isValid"));
int32 Errors = (int32)ValidationResult->GetNumberField(TEXT("errorCount"));
int32 Warnings = (int32)ValidationResult->GetNumberField(TEXT("warningCount"));
if (Result->HasField(TEXT("compileWarning")))
if (ValidationResult->HasField(TEXT("compileWarning")))
{
TotalCrashed++;
}
@@ -303,8 +285,8 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
{
TotalFailed++;
// Include path for context in bulk results
Result->SetStringField(TEXT("path"), PackagePath);
FailedArr.Add(MakeShared<FJsonValueObject>(Result));
ValidationResult->SetStringField(TEXT("path"), PackagePath);
FailedArr.Add(MakeShared<FJsonValueObject>(ValidationResult));
}
// Log progress every 50 blueprints
@@ -318,7 +300,6 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Bulk validation complete — %d checked, %d passed, %d failed, %d crashed"),
TotalChecked, TotalPassed, TotalFailed, TotalCrashed);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("totalMatching"), TotalMatching);
Result->SetNumberField(TEXT("totalChecked"), TotalChecked);
Result->SetNumberField(TEXT("totalPassed"), TotalPassed);
@@ -332,5 +313,4 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body)
{
Result->SetStringField(TEXT("filter"), Filter);
}
return JsonToString(Result);
}

View File

@@ -15,14 +15,8 @@
// HandleChangeVariableType — change a Blueprint member variable's type
// ============================================================
FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
void FBlueprintMCPServer::HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString VariableName = Json->GetStringField(TEXT("variable"));
FString NewTypeName = Json->GetStringField(TEXT("newType"));
@@ -34,7 +28,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || NewTypeName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, variable, newType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable, newType"));
}
// Load Blueprint
@@ -42,7 +36,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Verify variable exists
@@ -57,7 +51,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
}
if (!bVarFound)
{
return MakeErrorJson(FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
return MakeErrorJson(Result, FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
}
// Build the new pin type using shared resolver
@@ -75,7 +69,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(ResolveInput, NewPinType, TypeError))
{
return MakeErrorJson(TypeError);
return MakeErrorJson(Result, TypeError);
}
// Derive typeCategory from the resolved pin type for the response
@@ -170,7 +164,6 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
if (bDryRun)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("dryRun"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("variable"), VariableName);
@@ -178,7 +171,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
Result->SetStringField(TEXT("typeCategory"), TypeCategory);
Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num());
Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes);
return JsonToString(Result);
return;
}
// Directly modify the variable type in the description array.
@@ -211,7 +204,6 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("variable"), VariableName);
@@ -220,28 +212,21 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body)
Result->SetBoolField(TEXT("saved"), bSaved);
Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar);
Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes);
return JsonToString(Result);
}
// ============================================================
// HandleAddVariable — add a new member variable to a Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
void FBlueprintMCPServer::HandleAddVariable(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString VariableName = Json->GetStringField(TEXT("variableName"));
FString VariableType = Json->GetStringField(TEXT("variableType"));
if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || VariableType.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, variableName, variableType"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName, variableType"));
}
FString Category;
@@ -267,7 +252,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Check for duplicate variable name
@@ -276,7 +261,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
{
if (Var.VarName == VarFName)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Variable '%s' already exists in Blueprint '%s'"), *VariableName, *BlueprintName));
}
}
@@ -286,7 +271,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
FString TypeError;
if (!ResolveTypeFromString(VariableType, PinType, TypeError))
{
return MakeErrorJson(TypeError);
return MakeErrorJson(Result, TypeError);
}
// Set container type for arrays
@@ -302,7 +287,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
bool bSuccess = FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue);
if (!bSuccess)
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("FBlueprintEditorUtils::AddMemberVariable failed for '%s'"), *VariableName));
}
@@ -318,7 +303,6 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"),
*VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("variableName"), VariableName);
@@ -329,27 +313,20 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body)
}
Result->SetBoolField(TEXT("isArray"), bIsArray);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleRemoveVariable — remove a member variable from a Blueprint
// ============================================================
FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body)
void FBlueprintMCPServer::HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString VariableName = Json->GetStringField(TEXT("variableName"));
if (BlueprintName.IsEmpty() || VariableName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, variableName"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName"));
}
// Load Blueprint
@@ -357,7 +334,7 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Find variable by name (case-insensitive)
@@ -382,11 +359,10 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body)
AvailVars.Add(MakeShared<FJsonValueString>(Var.VarName.ToString()));
}
TSharedRef<FJsonObject> ErrorResult = MakeShared<FJsonObject>();
ErrorResult->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
ErrorResult->SetArrayField(TEXT("availableVariables"), AvailVars);
return JsonToString(ErrorResult);
Result->SetArrayField(TEXT("availableVariables"), AvailVars);
return;
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing variable '%s' from Blueprint '%s'"),
@@ -401,32 +377,24 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body)
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"),
*VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false"));
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("variableName"), VariableName);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}
// ============================================================
// HandleSetVariableMetadata — set variable properties (category, tooltip, replication, etc.)
// ============================================================
FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
void FBlueprintMCPServer::HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result)
{
TSharedPtr<FJsonObject> Json = ParseBodyJson(Body);
if (!Json.IsValid())
{
return MakeErrorJson(TEXT("Invalid JSON body"));
}
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
FString VariableName = Json->GetStringField(TEXT("variable"));
if (BlueprintName.IsEmpty() || VariableName.IsEmpty())
{
return MakeErrorJson(TEXT("Missing required fields: blueprint, variable"));
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable"));
}
// Load Blueprint
@@ -434,7 +402,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(LoadError);
return MakeErrorJson(Result, LoadError);
}
// Find the variable
@@ -456,11 +424,10 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
{
AvailableVars.Add(MakeShared<FJsonValueString>(Var.VarName.ToString()));
}
TSharedRef<FJsonObject> ErrResult = MakeShared<FJsonObject>();
ErrResult->SetStringField(TEXT("error"), FString::Printf(
MakeErrorJson(Result, FString::Printf(
TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName));
ErrResult->SetArrayField(TEXT("availableVariables"), AvailableVars);
return JsonToString(ErrResult);
Result->SetArrayField(TEXT("availableVariables"), AvailableVars);
return;
}
TArray<TSharedPtr<FJsonValue>> Changes;
@@ -521,7 +488,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
}
else
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Invalid replication value '%s'. Valid: none, replicated, repNotify"), *ReplicationStr));
}
@@ -590,7 +557,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
}
else
{
return MakeErrorJson(FString::Printf(
return MakeErrorJson(Result, FString::Printf(
TEXT("Invalid editability value '%s'. Valid: editAnywhere, editDefaultsOnly, editInstanceOnly, none"),
*Editability));
}
@@ -603,7 +570,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
if (Changes.Num() == 0)
{
return MakeErrorJson(TEXT("No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability"));
return MakeErrorJson(Result, TEXT("No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability"));
}
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetVariableMetadata on '%s.%s' — %d field(s) changed"),
@@ -612,11 +579,9 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body)
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = SaveBlueprintPackage(BP);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("blueprint"), BlueprintName);
Result->SetStringField(TEXT("variable"), VariableName);
Result->SetArrayField(TEXT("changes"), Changes);
Result->SetBoolField(TEXT("saved"), bSaved);
return JsonToString(Result);
}

View File

@@ -210,6 +210,20 @@ FString FBlueprintMCPServer::MakeErrorJson(const FString& Message)
return JsonToString(E);
}
void FBlueprintMCPServer::MakeErrorJson(FJsonObject* Result, const FString& Message)
{
Result->Values.Empty();
Result->SetStringField(TEXT("error"), Message);
}
void FBlueprintMCPServer::CopyJsonFields(const FJsonObject* Source, FJsonObject* Dest)
{
for (const auto& KV : Source->Values)
{
Dest->SetField(KV.Key, KV.Value);
}
}
FString FBlueprintMCPServer::UrlDecode(const FString& EncodedString)
{
FString Result;
@@ -547,9 +561,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
FHttpRequestHandler::CreateLambda(
[this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
FString Resp = HandleList(Request.QueryParams);
TSharedRef<FJsonObject> Params = MakeShared<FJsonObject>();
for (const auto& KV : Request.QueryParams)
{
Params->SetStringField(KV.Key, KV.Value);
}
TSharedRef<FJsonObject> ListResult = MakeShared<FJsonObject>();
HandleList(&*Params, &*ListResult);
TUniquePtr<FHttpServerResponse> R = FHttpServerResponse::Create(
Resp, TEXT("application/json"));
JsonToString(ListResult), TEXT("application/json"));
OnComplete(MoveTemp(R));
return true;
}));
@@ -693,9 +713,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
FHttpRequestHandler::CreateLambda(
[this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
FString Resp = HandleListMaterials(Request.QueryParams);
TSharedRef<FJsonObject> Params = MakeShared<FJsonObject>();
for (const auto& KV : Request.QueryParams)
{
Params->SetStringField(KV.Key, KV.Value);
}
TSharedRef<FJsonObject> ListResult = MakeShared<FJsonObject>();
HandleListMaterials(&*Params, &*ListResult);
TUniquePtr<FHttpServerResponse> R = FHttpServerResponse::Create(
Resp, TEXT("application/json"));
JsonToString(ListResult), TEXT("application/json"));
OnComplete(MoveTemp(R));
return true;
}));
@@ -743,9 +769,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode)
FHttpRequestHandler::CreateLambda(
[this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
{
FString Resp = HandleListMaterialFunctions(Request.QueryParams);
TSharedRef<FJsonObject> Params = MakeShared<FJsonObject>();
for (const auto& KV : Request.QueryParams)
{
Params->SetStringField(KV.Key, KV.Value);
}
TSharedRef<FJsonObject> ListResult = MakeShared<FJsonObject>();
HandleListMaterialFunctions(&*Params, &*ListResult);
TUniquePtr<FHttpServerResponse> R = FHttpServerResponse::Create(
Resp, TEXT("application/json"));
JsonToString(ListResult), TEXT("application/json"));
OnComplete(MoveTemp(R));
return true;
}));
@@ -852,7 +884,23 @@ bool FBlueprintMCPServer::ProcessOneRequest()
return false;
}
FString Response;
// Build Params JSON: either from POST body or from GET query params
TSharedPtr<FJsonObject> Params;
if (!Req->Body.IsEmpty())
{
Params = ParseBodyJson(Req->Body);
}
if (!Params.IsValid())
{
Params = MakeShared<FJsonObject>();
for (const auto& KV : Req->QueryParams)
{
Params->SetStringField(KV.Key, KV.Value);
}
}
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
if (FRequestHandler* Handler = HandlerMap.Find(Req->Endpoint))
{
// Wrap mutation endpoints in an undo transaction so users can Ctrl+Z
@@ -862,7 +910,7 @@ bool FBlueprintMCPServer::ProcessOneRequest()
GEditor->BeginTransaction(FText::FromString(FString::Printf(TEXT("BlueprintMCP: %s"), *Req->Endpoint)));
}
Response = (*Handler)(Req->QueryParams, Req->Body);
(*Handler)(Params.Get(), &*Result);
if (bIsMutation && GEditor)
{
@@ -871,10 +919,11 @@ bool FBlueprintMCPServer::ProcessOneRequest()
}
else
{
Response = MakeErrorJson(FString::Printf(TEXT("Unknown endpoint: %s"), *Req->Endpoint));
MakeErrorJson(&*Result, FString::Printf(TEXT("Unknown endpoint: %s"), *Req->Endpoint));
}
// Send the response back via the HTTP callback (non-blocking)
FString Response = JsonToString(Result);
TUniquePtr<FHttpServerResponse> HttpResp = FHttpServerResponse::Create(
Response, TEXT("application/json"));
Req->OnComplete(MoveTemp(HttpResp));
@@ -946,121 +995,116 @@ void FBlueprintMCPServer::RegisterHandlers()
TEXT("setStateAnimation"),
};
// GET handlers (use QueryParams, ignore Body)
HandlerMap.Add(TEXT("blueprint"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetBlueprint(P); });
HandlerMap.Add(TEXT("graph"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetGraph(P); });
HandlerMap.Add(TEXT("search"), [this](const TMap<FString, FString>& P, const FString&) { return HandleSearch(P); });
HandlerMap.Add(TEXT("references"), [this](const TMap<FString, FString>& P, const FString&) { return HandleFindReferences(P); });
HandlerMap.Add(TEXT("testSave"), [this](const TMap<FString, FString>& P, const FString&) { return HandleTestSave(P); });
HandlerMap.Add(TEXT("searchByType"), [this](const TMap<FString, FString>& P, const FString&) { return HandleSearchByType(P); });
// All handlers have uniform signature: void(const FJsonObject&, FJsonObject&)
auto H = [this](const TCHAR* Name, void(FBlueprintMCPServer::*Fn)(const FJsonObject*, FJsonObject*))
{
HandlerMap.Add(Name, [this, Fn](const FJsonObject* Json, FJsonObject* Result) { (this->*Fn)(Json, Result); });
};
// Rescan handler (game thread, no body needed)
HandlerMap.Add(TEXT("rescan"), [this](const TMap<FString, FString>&, const FString&) { return HandleRescan(); });
// POST handlers (use Body, ignore QueryParams)
HandlerMap.Add(TEXT("replaceFunctionCalls"), [this](const TMap<FString, FString>&, const FString& B) { return HandleReplaceFunctionCalls(B); });
HandlerMap.Add(TEXT("changeVariableType"), [this](const TMap<FString, FString>&, const FString& B) { return HandleChangeVariableType(B); });
HandlerMap.Add(TEXT("changeFunctionParamType"), [this](const TMap<FString, FString>&, const FString& B) { return HandleChangeFunctionParamType(B); });
HandlerMap.Add(TEXT("removeFunctionParameter"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveFunctionParameter(B); });
HandlerMap.Add(TEXT("deleteAsset"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDeleteAsset(B); });
HandlerMap.Add(TEXT("connectPins"), [this](const TMap<FString, FString>&, const FString& B) { return HandleConnectPins(B); });
HandlerMap.Add(TEXT("disconnectPin"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDisconnectPin(B); });
HandlerMap.Add(TEXT("refreshAllNodes"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRefreshAllNodes(B); });
HandlerMap.Add(TEXT("setPinDefault"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetPinDefault(B); });
HandlerMap.Add(TEXT("moveNode"), [this](const TMap<FString, FString>&, const FString& B) { return HandleMoveNode(B); });
HandlerMap.Add(TEXT("getNodeComment"), [this](const TMap<FString, FString>&, const FString& B) { return HandleGetNodeComment(B); });
HandlerMap.Add(TEXT("setNodeComment"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetNodeComment(B); });
HandlerMap.Add(TEXT("getPinInfo"), [this](const TMap<FString, FString>&, const FString& B) { return HandleGetPinInfo(B); });
HandlerMap.Add(TEXT("checkPinCompatibility"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCheckPinCompatibility(B); });
HandlerMap.Add(TEXT("listClasses"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListClasses(B); });
HandlerMap.Add(TEXT("listFunctions"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListFunctions(B); });
HandlerMap.Add(TEXT("listProperties"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListProperties(B); });
HandlerMap.Add(TEXT("changeStructNodeType"), [this](const TMap<FString, FString>&, const FString& B) { return HandleChangeStructNodeType(B); });
HandlerMap.Add(TEXT("deleteNode"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDeleteNode(B); });
HandlerMap.Add(TEXT("duplicateNodes"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDuplicateNodes(B); });
HandlerMap.Add(TEXT("validateBlueprint"), [this](const TMap<FString, FString>&, const FString& B) { return HandleValidateBlueprint(B); });
HandlerMap.Add(TEXT("validateAllBlueprints"), [this](const TMap<FString, FString>&, const FString& B) { return HandleValidateAllBlueprints(B); });
HandlerMap.Add(TEXT("addNode"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddNode(B); });
HandlerMap.Add(TEXT("searchNodeActions"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSearchNodeActions(B); });
HandlerMap.Add(TEXT("spawnNode"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSpawnNode(B); });
HandlerMap.Add(TEXT("renameAsset"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRenameAsset(B); });
HandlerMap.Add(TEXT("reparentBlueprint"), [this](const TMap<FString, FString>&, const FString& B) { return HandleReparentBlueprint(B); });
HandlerMap.Add(TEXT("setBlueprintDefault"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetBlueprintDefault(B); });
HandlerMap.Add(TEXT("createBlueprint"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateBlueprint(B); });
HandlerMap.Add(TEXT("createGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateGraph(B); });
HandlerMap.Add(TEXT("deleteGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDeleteGraph(B); });
HandlerMap.Add(TEXT("renameGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRenameGraph(B); });
HandlerMap.Add(TEXT("addVariable"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddVariable(B); });
HandlerMap.Add(TEXT("removeVariable"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveVariable(B); });
HandlerMap.Add(TEXT("setVariableMetadata"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetVariableMetadata(B); });
HandlerMap.Add(TEXT("addInterface"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddInterface(B); });
HandlerMap.Add(TEXT("removeInterface"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveInterface(B); });
HandlerMap.Add(TEXT("listInterfaces"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListInterfaces(B); });
HandlerMap.Add(TEXT("addEventDispatcher"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddEventDispatcher(B); });
HandlerMap.Add(TEXT("listEventDispatchers"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListEventDispatchers(B); });
HandlerMap.Add(TEXT("addFunctionParameter"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddFunctionParameter(B); });
HandlerMap.Add(TEXT("addComponent"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddComponent(B); });
HandlerMap.Add(TEXT("removeComponent"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveComponent(B); });
HandlerMap.Add(TEXT("listComponents"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListComponents(B); });
HandlerMap.Add(TEXT("snapshotGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSnapshotGraph(B); });
HandlerMap.Add(TEXT("diffGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDiffGraph(B); });
HandlerMap.Add(TEXT("restoreGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRestoreGraph(B); });
HandlerMap.Add(TEXT("findDisconnectedPins"), [this](const TMap<FString, FString>&, const FString& B) { return HandleFindDisconnectedPins(B); });
HandlerMap.Add(TEXT("analyzeRebuildImpact"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAnalyzeRebuildImpact(B); });
HandlerMap.Add(TEXT("diffBlueprints"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDiffBlueprints(B); });
HandlerMap.Add(TEXT("createStruct"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateStruct(B); });
HandlerMap.Add(TEXT("createEnum"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateEnum(B); });
HandlerMap.Add(TEXT("addStructProperty"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddStructProperty(B); });
HandlerMap.Add(TEXT("removeStructProperty"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveStructProperty(B); });
// Material GET handlers
HandlerMap.Add(TEXT("getMaterial"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetMaterial(P); });
HandlerMap.Add(TEXT("getMaterialGraph"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetMaterialGraph(P); });
HandlerMap.Add(TEXT("searchMaterials"), [this](const TMap<FString, FString>& P, const FString&) { return HandleSearchMaterials(P); });
HandlerMap.Add(TEXT("getMaterialInstanceParams"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetMaterialInstanceParameters(P); });
HandlerMap.Add(TEXT("getMaterialFunction"), [this](const TMap<FString, FString>& P, const FString&) { return HandleGetMaterialFunction(P); });
// Material POST handlers
HandlerMap.Add(TEXT("describeMaterial"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDescribeMaterial(B); });
HandlerMap.Add(TEXT("findMaterialReferences"), [this](const TMap<FString, FString>&, const FString& B) { return HandleFindMaterialReferences(B); });
HandlerMap.Add(TEXT("createMaterial"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateMaterial(B); });
HandlerMap.Add(TEXT("setMaterialProperty"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetMaterialProperty(B); });
HandlerMap.Add(TEXT("addMaterialExpression"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddMaterialExpression(B); });
HandlerMap.Add(TEXT("deleteMaterialExpression"),[this](const TMap<FString, FString>&, const FString& B) { return HandleDeleteMaterialExpression(B); });
HandlerMap.Add(TEXT("connectMaterialPins"), [this](const TMap<FString, FString>&, const FString& B) { return HandleConnectMaterialPins(B); });
HandlerMap.Add(TEXT("disconnectMaterialPin"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDisconnectMaterialPin(B); });
HandlerMap.Add(TEXT("setExpressionValue"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetExpressionValue(B); });
HandlerMap.Add(TEXT("moveMaterialExpression"), [this](const TMap<FString, FString>&, const FString& B) { return HandleMoveMaterialExpression(B); });
HandlerMap.Add(TEXT("createMaterialInstance"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateMaterialInstance(B); });
HandlerMap.Add(TEXT("setMaterialInstanceParameter"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetMaterialInstanceParameter(B); });
HandlerMap.Add(TEXT("reparentMaterialInstance"),[this](const TMap<FString, FString>&, const FString& B) { return HandleReparentMaterialInstance(B); });
HandlerMap.Add(TEXT("createMaterialFunction"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateMaterialFunction(B); });
HandlerMap.Add(TEXT("snapshotMaterialGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSnapshotMaterialGraph(B); });
HandlerMap.Add(TEXT("diffMaterialGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleDiffMaterialGraph(B); });
HandlerMap.Add(TEXT("restoreMaterialGraph"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRestoreMaterialGraph(B); });
HandlerMap.Add(TEXT("validateMaterial"), [this](const TMap<FString, FString>&, const FString& B) { return HandleValidateMaterial(B); });
// Animation Blueprint handlers
HandlerMap.Add(TEXT("createAnimBlueprint"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateAnimBlueprint(B); });
HandlerMap.Add(TEXT("addAnimState"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddAnimState(B); });
HandlerMap.Add(TEXT("removeAnimState"), [this](const TMap<FString, FString>&, const FString& B) { return HandleRemoveAnimState(B); });
HandlerMap.Add(TEXT("addAnimTransition"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddAnimTransition(B); });
HandlerMap.Add(TEXT("setTransitionRule"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetTransitionRule(B); });
HandlerMap.Add(TEXT("addAnimNode"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddAnimNode(B); });
HandlerMap.Add(TEXT("addStateMachine"), [this](const TMap<FString, FString>&, const FString& B) { return HandleAddStateMachine(B); });
HandlerMap.Add(TEXT("setStateAnimation"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetStateAnimation(B); });
HandlerMap.Add(TEXT("listAnimSlots"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListAnimSlots(B); });
HandlerMap.Add(TEXT("listSyncGroups"), [this](const TMap<FString, FString>&, const FString& B) { return HandleListSyncGroups(B); });
HandlerMap.Add(TEXT("createBlendSpace"), [this](const TMap<FString, FString>&, const FString& B) { return HandleCreateBlendSpace(B); });
HandlerMap.Add(TEXT("setBlendSpaceSamples"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetBlendSpaceSamples(B); });
HandlerMap.Add(TEXT("setStateBlendSpace"), [this](const TMap<FString, FString>&, const FString& B) { return HandleSetStateBlendSpace(B); });
H(TEXT("rescan"), &FBlueprintMCPServer::HandleRescan);
H(TEXT("blueprint"), &FBlueprintMCPServer::HandleGetBlueprint);
H(TEXT("graph"), &FBlueprintMCPServer::HandleGetGraph);
H(TEXT("search"), &FBlueprintMCPServer::HandleSearch);
H(TEXT("references"), &FBlueprintMCPServer::HandleFindReferences);
H(TEXT("testSave"), &FBlueprintMCPServer::HandleTestSave);
H(TEXT("searchByType"), &FBlueprintMCPServer::HandleSearchByType);
H(TEXT("replaceFunctionCalls"), &FBlueprintMCPServer::HandleReplaceFunctionCalls);
H(TEXT("changeVariableType"), &FBlueprintMCPServer::HandleChangeVariableType);
H(TEXT("changeFunctionParamType"), &FBlueprintMCPServer::HandleChangeFunctionParamType);
H(TEXT("removeFunctionParameter"), &FBlueprintMCPServer::HandleRemoveFunctionParameter);
H(TEXT("deleteAsset"), &FBlueprintMCPServer::HandleDeleteAsset);
H(TEXT("connectPins"), &FBlueprintMCPServer::HandleConnectPins);
H(TEXT("disconnectPin"), &FBlueprintMCPServer::HandleDisconnectPin);
H(TEXT("refreshAllNodes"), &FBlueprintMCPServer::HandleRefreshAllNodes);
H(TEXT("setPinDefault"), &FBlueprintMCPServer::HandleSetPinDefault);
H(TEXT("moveNode"), &FBlueprintMCPServer::HandleMoveNode);
H(TEXT("getNodeComment"), &FBlueprintMCPServer::HandleGetNodeComment);
H(TEXT("setNodeComment"), &FBlueprintMCPServer::HandleSetNodeComment);
H(TEXT("getPinInfo"), &FBlueprintMCPServer::HandleGetPinInfo);
H(TEXT("checkPinCompatibility"), &FBlueprintMCPServer::HandleCheckPinCompatibility);
H(TEXT("listClasses"), &FBlueprintMCPServer::HandleListClasses);
H(TEXT("listFunctions"), &FBlueprintMCPServer::HandleListFunctions);
H(TEXT("listProperties"), &FBlueprintMCPServer::HandleListProperties);
H(TEXT("changeStructNodeType"), &FBlueprintMCPServer::HandleChangeStructNodeType);
H(TEXT("deleteNode"), &FBlueprintMCPServer::HandleDeleteNode);
H(TEXT("duplicateNodes"), &FBlueprintMCPServer::HandleDuplicateNodes);
H(TEXT("validateBlueprint"), &FBlueprintMCPServer::HandleValidateBlueprint);
H(TEXT("validateAllBlueprints"), &FBlueprintMCPServer::HandleValidateAllBlueprints);
H(TEXT("addNode"), &FBlueprintMCPServer::HandleAddNode);
H(TEXT("searchNodeActions"), &FBlueprintMCPServer::HandleSearchNodeActions);
H(TEXT("spawnNode"), &FBlueprintMCPServer::HandleSpawnNode);
H(TEXT("renameAsset"), &FBlueprintMCPServer::HandleRenameAsset);
H(TEXT("reparentBlueprint"), &FBlueprintMCPServer::HandleReparentBlueprint);
H(TEXT("setBlueprintDefault"), &FBlueprintMCPServer::HandleSetBlueprintDefault);
H(TEXT("createBlueprint"), &FBlueprintMCPServer::HandleCreateBlueprint);
H(TEXT("createGraph"), &FBlueprintMCPServer::HandleCreateGraph);
H(TEXT("deleteGraph"), &FBlueprintMCPServer::HandleDeleteGraph);
H(TEXT("renameGraph"), &FBlueprintMCPServer::HandleRenameGraph);
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);
H(TEXT("addEventDispatcher"), &FBlueprintMCPServer::HandleAddEventDispatcher);
H(TEXT("listEventDispatchers"), &FBlueprintMCPServer::HandleListEventDispatchers);
H(TEXT("addFunctionParameter"), &FBlueprintMCPServer::HandleAddFunctionParameter);
H(TEXT("addComponent"), &FBlueprintMCPServer::HandleAddComponent);
H(TEXT("removeComponent"), &FBlueprintMCPServer::HandleRemoveComponent);
H(TEXT("listComponents"), &FBlueprintMCPServer::HandleListComponents);
H(TEXT("snapshotGraph"), &FBlueprintMCPServer::HandleSnapshotGraph);
H(TEXT("diffGraph"), &FBlueprintMCPServer::HandleDiffGraph);
H(TEXT("restoreGraph"), &FBlueprintMCPServer::HandleRestoreGraph);
H(TEXT("findDisconnectedPins"), &FBlueprintMCPServer::HandleFindDisconnectedPins);
H(TEXT("analyzeRebuildImpact"), &FBlueprintMCPServer::HandleAnalyzeRebuildImpact);
H(TEXT("diffBlueprints"), &FBlueprintMCPServer::HandleDiffBlueprints);
H(TEXT("createStruct"), &FBlueprintMCPServer::HandleCreateStruct);
H(TEXT("createEnum"), &FBlueprintMCPServer::HandleCreateEnum);
H(TEXT("addStructProperty"), &FBlueprintMCPServer::HandleAddStructProperty);
H(TEXT("removeStructProperty"), &FBlueprintMCPServer::HandleRemoveStructProperty);
H(TEXT("getMaterial"), &FBlueprintMCPServer::HandleGetMaterial);
H(TEXT("getMaterialGraph"), &FBlueprintMCPServer::HandleGetMaterialGraph);
H(TEXT("searchMaterials"), &FBlueprintMCPServer::HandleSearchMaterials);
H(TEXT("getMaterialInstanceParams"),&FBlueprintMCPServer::HandleGetMaterialInstanceParameters);
H(TEXT("getMaterialFunction"), &FBlueprintMCPServer::HandleGetMaterialFunction);
H(TEXT("describeMaterial"), &FBlueprintMCPServer::HandleDescribeMaterial);
H(TEXT("findMaterialReferences"), &FBlueprintMCPServer::HandleFindMaterialReferences);
H(TEXT("createMaterial"), &FBlueprintMCPServer::HandleCreateMaterial);
H(TEXT("setMaterialProperty"), &FBlueprintMCPServer::HandleSetMaterialProperty);
H(TEXT("addMaterialExpression"), &FBlueprintMCPServer::HandleAddMaterialExpression);
H(TEXT("deleteMaterialExpression"), &FBlueprintMCPServer::HandleDeleteMaterialExpression);
H(TEXT("connectMaterialPins"), &FBlueprintMCPServer::HandleConnectMaterialPins);
H(TEXT("disconnectMaterialPin"), &FBlueprintMCPServer::HandleDisconnectMaterialPin);
H(TEXT("setExpressionValue"), &FBlueprintMCPServer::HandleSetExpressionValue);
H(TEXT("moveMaterialExpression"), &FBlueprintMCPServer::HandleMoveMaterialExpression);
H(TEXT("createMaterialInstance"), &FBlueprintMCPServer::HandleCreateMaterialInstance);
H(TEXT("setMaterialInstanceParameter"), &FBlueprintMCPServer::HandleSetMaterialInstanceParameter);
H(TEXT("reparentMaterialInstance"), &FBlueprintMCPServer::HandleReparentMaterialInstance);
H(TEXT("createMaterialFunction"), &FBlueprintMCPServer::HandleCreateMaterialFunction);
H(TEXT("snapshotMaterialGraph"), &FBlueprintMCPServer::HandleSnapshotMaterialGraph);
H(TEXT("diffMaterialGraph"), &FBlueprintMCPServer::HandleDiffMaterialGraph);
H(TEXT("restoreMaterialGraph"), &FBlueprintMCPServer::HandleRestoreMaterialGraph);
H(TEXT("validateMaterial"), &FBlueprintMCPServer::HandleValidateMaterial);
H(TEXT("createAnimBlueprint"), &FBlueprintMCPServer::HandleCreateAnimBlueprint);
H(TEXT("addAnimState"), &FBlueprintMCPServer::HandleAddAnimState);
H(TEXT("removeAnimState"), &FBlueprintMCPServer::HandleRemoveAnimState);
H(TEXT("addAnimTransition"), &FBlueprintMCPServer::HandleAddAnimTransition);
H(TEXT("setTransitionRule"), &FBlueprintMCPServer::HandleSetTransitionRule);
H(TEXT("addAnimNode"), &FBlueprintMCPServer::HandleAddAnimNode);
H(TEXT("addStateMachine"), &FBlueprintMCPServer::HandleAddStateMachine);
H(TEXT("setStateAnimation"), &FBlueprintMCPServer::HandleSetStateAnimation);
H(TEXT("listAnimSlots"), &FBlueprintMCPServer::HandleListAnimSlots);
H(TEXT("listSyncGroups"), &FBlueprintMCPServer::HandleListSyncGroups);
H(TEXT("createBlendSpace"), &FBlueprintMCPServer::HandleCreateBlendSpace);
H(TEXT("setBlendSpaceSamples"), &FBlueprintMCPServer::HandleSetBlendSpaceSamples);
H(TEXT("setStateBlendSpace"), &FBlueprintMCPServer::HandleSetStateBlendSpace);
}
// ============================================================
// HandleRescan — refresh cached asset lists from asset registry
// ============================================================
FString FBlueprintMCPServer::HandleRescan()
void FBlueprintMCPServer::HandleRescan(const FJsonObject* Json, FJsonObject* Result)
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Rescanning asset registry..."));
@@ -1092,13 +1136,12 @@ FString FBlueprintMCPServer::HandleRescan()
OldMI, AllMaterialInstanceAssets.Num(),
OldMF, AllMaterialFunctionAssets.Num());
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
J->SetStringField(TEXT("status"), TEXT("ok"));
J->SetNumberField(TEXT("blueprintCount"), AllBlueprintAssets.Num());
J->SetNumberField(TEXT("mapCount"), AllMapAssets.Num());
J->SetNumberField(TEXT("materialCount"), AllMaterialAssets.Num());
J->SetNumberField(TEXT("materialInstanceCount"), AllMaterialInstanceAssets.Num());
J->SetNumberField(TEXT("materialFunctionCount"), AllMaterialFunctionAssets.Num());
Result->SetStringField(TEXT("status"), TEXT("ok"));
Result->SetNumberField(TEXT("blueprintCount"), AllBlueprintAssets.Num());
Result->SetNumberField(TEXT("mapCount"), AllMapAssets.Num());
Result->SetNumberField(TEXT("materialCount"), AllMaterialAssets.Num());
Result->SetNumberField(TEXT("materialInstanceCount"), AllMaterialInstanceAssets.Num());
Result->SetNumberField(TEXT("materialFunctionCount"), AllMaterialFunctionAssets.Num());
TSharedRef<FJsonObject> Delta = MakeShared<FJsonObject>();
Delta->SetNumberField(TEXT("blueprints"), AllBlueprintAssets.Num() - OldBP);
@@ -1106,9 +1149,7 @@ FString FBlueprintMCPServer::HandleRescan()
Delta->SetNumberField(TEXT("materials"), AllMaterialAssets.Num() - OldMat);
Delta->SetNumberField(TEXT("materialInstances"), AllMaterialInstanceAssets.Num() - OldMI);
Delta->SetNumberField(TEXT("materialFunctions"), AllMaterialFunctionAssets.Num() - OldMF);
J->SetObjectField(TEXT("delta"), Delta);
return JsonToString(J);
Result->SetObjectField(TEXT("delta"), Delta);
}
// ============================================================

View File

@@ -94,8 +94,8 @@ public:
int32 GetMaterialInstanceCount() const { return AllMaterialInstanceAssets.Num(); }
private:
// ----- TMap-based request dispatch -----
using FRequestHandler = TFunction<FString(const TMap<FString, FString>&, const FString&)>;
// ----- Request dispatch -----
using FRequestHandler = TFunction<void(const FJsonObject* Json, FJsonObject* Result)>;
TMap<FString, FRequestHandler> HandlerMap;
TSet<FString> MutationEndpoints;
void RegisterHandlers();
@@ -119,162 +119,162 @@ private:
bool bIsEditor = false;
// ----- Asset registry rescan -----
FString HandleRescan();
void HandleRescan(const FJsonObject* Json, FJsonObject* Result);
// ----- Request handlers (read-only) -----
FString HandleList(const TMap<FString, FString>& Params);
FString HandleGetBlueprint(const TMap<FString, FString>& Params);
FString HandleGetGraph(const TMap<FString, FString>& Params);
FString HandleSearch(const TMap<FString, FString>& Params);
FString HandleFindReferences(const TMap<FString, FString>& Params);
FString HandleSearchByType(const TMap<FString, FString>& Params);
void HandleList(const FJsonObject* Json, FJsonObject* Result);
void HandleGetBlueprint(const FJsonObject* Json, FJsonObject* Result);
void HandleGetGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleSearch(const FJsonObject* Json, FJsonObject* Result);
void HandleFindReferences(const FJsonObject* Json, FJsonObject* Result);
void HandleSearchByType(const FJsonObject* Json, FJsonObject* Result);
// ----- Request handlers (write) -----
FString HandleReplaceFunctionCalls(const FString& Body);
FString HandleChangeVariableType(const FString& Body);
FString HandleChangeFunctionParamType(const FString& Body);
FString HandleRemoveFunctionParameter(const FString& Body);
FString HandleDeleteAsset(const FString& Body);
FString HandleDeleteNode(const FString& Body);
FString HandleDuplicateNodes(const FString& Body);
FString HandleAddNode(const FString& Body);
FString HandleRenameAsset(const FString& Body);
void HandleReplaceFunctionCalls(const FJsonObject* Json, FJsonObject* Result);
void HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result);
void HandleChangeFunctionParamType(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveFunctionParameter(const FJsonObject* Json, FJsonObject* Result);
void HandleDeleteAsset(const FJsonObject* Json, FJsonObject* Result);
void HandleDeleteNode(const FJsonObject* Json, FJsonObject* Result);
void HandleDuplicateNodes(const FJsonObject* Json, FJsonObject* Result);
void HandleAddNode(const FJsonObject* Json, FJsonObject* Result);
void HandleRenameAsset(const FJsonObject* Json, FJsonObject* Result);
// ----- Validation (read-only, no save) -----
FString HandleValidateBlueprint(const FString& Body);
FString HandleValidateAllBlueprints(const FString& Body);
void HandleValidateBlueprint(const FJsonObject* Json, FJsonObject* Result);
void HandleValidateAllBlueprints(const FJsonObject* Json, FJsonObject* Result);
// ----- Pin manipulation (write) -----
FString HandleConnectPins(const FString& Body);
FString HandleDisconnectPin(const FString& Body);
FString HandleRefreshAllNodes(const FString& Body);
FString HandleSetPinDefault(const FString& Body);
FString HandleMoveNode(const FString& Body);
FString HandleGetNodeComment(const FString& Body);
FString HandleSetNodeComment(const FString& Body);
void HandleConnectPins(const FJsonObject* Json, FJsonObject* Result);
void HandleDisconnectPin(const FJsonObject* Json, FJsonObject* Result);
void HandleRefreshAllNodes(const FJsonObject* Json, FJsonObject* Result);
void HandleSetPinDefault(const FJsonObject* Json, FJsonObject* Result);
void HandleMoveNode(const FJsonObject* Json, FJsonObject* Result);
void HandleGetNodeComment(const FJsonObject* Json, FJsonObject* Result);
void HandleSetNodeComment(const FJsonObject* Json, FJsonObject* Result);
// ----- Pin introspection (read-only) -----
FString HandleGetPinInfo(const FString& Body);
FString HandleCheckPinCompatibility(const FString& Body);
void HandleGetPinInfo(const FJsonObject* Json, FJsonObject* Result);
void HandleCheckPinCompatibility(const FJsonObject* Json, FJsonObject* Result);
// ----- Class/function discovery (read-only) -----
FString HandleListClasses(const FString& Body);
FString HandleListFunctions(const FString& Body);
FString HandleListProperties(const FString& Body);
void HandleListClasses(const FJsonObject* Json, FJsonObject* Result);
void HandleListFunctions(const FJsonObject* Json, FJsonObject* Result);
void HandleListProperties(const FJsonObject* Json, FJsonObject* Result);
// ----- Struct node manipulation (write) -----
FString HandleChangeStructNodeType(const FString& Body);
void HandleChangeStructNodeType(const FJsonObject* Json, FJsonObject* Result);
// ----- Reparent -----
FString HandleReparentBlueprint(const FString& Body);
void HandleReparentBlueprint(const FJsonObject* Json, FJsonObject* Result);
// ----- Create -----
FString HandleCreateBlueprint(const FString& Body);
FString HandleCreateGraph(const FString& Body);
void HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result);
void HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result);
// ----- User-defined types -----
FString HandleCreateStruct(const FString& Body);
FString HandleCreateEnum(const FString& Body);
FString HandleAddStructProperty(const FString& Body);
FString HandleRemoveStructProperty(const FString& Body);
void HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result);
void HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result);
void HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result);
// ----- Graph manipulation -----
FString HandleDeleteGraph(const FString& Body);
FString HandleRenameGraph(const FString& Body);
void HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result);
// ----- Variables -----
FString HandleAddVariable(const FString& Body);
FString HandleRemoveVariable(const FString& Body);
FString HandleSetVariableMetadata(const FString& Body);
void HandleAddVariable(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result);
void HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result);
// ----- Interfaces -----
FString HandleAddInterface(const FString& Body);
FString HandleRemoveInterface(const FString& Body);
FString HandleListInterfaces(const FString& Body);
void HandleAddInterface(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result);
void HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result);
// ----- Event Dispatchers -----
FString HandleAddEventDispatcher(const FString& Body);
FString HandleListEventDispatchers(const FString& Body);
void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result);
void HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result);
// ----- Function Parameters -----
FString HandleAddFunctionParameter(const FString& Body);
void HandleAddFunctionParameter(const FJsonObject* Json, FJsonObject* Result);
// ----- Components -----
FString HandleAddComponent(const FString& Body);
FString HandleRemoveComponent(const FString& Body);
FString HandleListComponents(const FString& Body);
void HandleAddComponent(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveComponent(const FJsonObject* Json, FJsonObject* Result);
void HandleListComponents(const FJsonObject* Json, FJsonObject* Result);
// ----- Property defaults -----
FString HandleSetBlueprintDefault(const FString& Body);
void HandleSetBlueprintDefault(const FJsonObject* Json, FJsonObject* Result);
// ----- Generic node spawning via action database -----
FString HandleSearchNodeActions(const FString& Body);
FString HandleSpawnNode(const FString& Body);
void HandleSearchNodeActions(const FJsonObject* Json, FJsonObject* Result);
void HandleSpawnNode(const FJsonObject* Json, FJsonObject* Result);
// ----- Diagnostic -----
FString HandleTestSave(const TMap<FString, FString>& Params);
void HandleTestSave(const FJsonObject* Json, FJsonObject* Result);
// ----- Snapshot / Safety tools (write) -----
FString HandleSnapshotGraph(const FString& Body);
FString HandleDiffGraph(const FString& Body);
FString HandleRestoreGraph(const FString& Body);
FString HandleFindDisconnectedPins(const FString& Body);
FString HandleAnalyzeRebuildImpact(const FString& Body);
void HandleSnapshotGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleDiffGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleRestoreGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result);
void HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result);
// ----- Cross-Blueprint comparison (read-only) -----
FString HandleDiffBlueprints(const FString& Body);
void HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result);
// ----- Material read-only handlers (Phase 1) -----
FString HandleListMaterials(const TMap<FString, FString>& Params);
FString HandleGetMaterial(const TMap<FString, FString>& Params);
FString HandleGetMaterialGraph(const TMap<FString, FString>& Params);
FString HandleDescribeMaterial(const FString& Body);
FString HandleSearchMaterials(const TMap<FString, FString>& Params);
FString HandleFindMaterialReferences(const FString& Body);
void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result);
void HandleGetMaterial(const FJsonObject* Json, FJsonObject* Result);
void HandleGetMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleDescribeMaterial(const FJsonObject* Json, FJsonObject* Result);
void HandleSearchMaterials(const FJsonObject* Json, FJsonObject* Result);
void HandleFindMaterialReferences(const FJsonObject* Json, FJsonObject* Result);
// ----- Material mutation handlers (Phase 2) -----
FString HandleCreateMaterial(const FString& Body);
FString HandleSetMaterialProperty(const FString& Body);
FString HandleAddMaterialExpression(const FString& Body);
FString HandleDeleteMaterialExpression(const FString& Body);
FString HandleConnectMaterialPins(const FString& Body);
FString HandleDisconnectMaterialPin(const FString& Body);
FString HandleSetExpressionValue(const FString& Body);
FString HandleMoveMaterialExpression(const FString& Body);
void HandleCreateMaterial(const FJsonObject* Json, FJsonObject* Result);
void HandleSetMaterialProperty(const FJsonObject* Json, FJsonObject* Result);
void HandleAddMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
void HandleDeleteMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
void HandleConnectMaterialPins(const FJsonObject* Json, FJsonObject* Result);
void HandleDisconnectMaterialPin(const FJsonObject* Json, FJsonObject* Result);
void HandleSetExpressionValue(const FJsonObject* Json, FJsonObject* Result);
void HandleMoveMaterialExpression(const FJsonObject* Json, FJsonObject* Result);
// ----- Material instance handlers (Phase 3) -----
FString HandleCreateMaterialInstance(const FString& Body);
FString HandleSetMaterialInstanceParameter(const FString& Body);
FString HandleGetMaterialInstanceParameters(const TMap<FString, FString>& Params);
FString HandleReparentMaterialInstance(const FString& Body);
void HandleCreateMaterialInstance(const FJsonObject* Json, FJsonObject* Result);
void HandleSetMaterialInstanceParameter(const FJsonObject* Json, FJsonObject* Result);
void HandleGetMaterialInstanceParameters(const FJsonObject* Json, FJsonObject* Result);
void HandleReparentMaterialInstance(const FJsonObject* Json, FJsonObject* Result);
// ----- Material function handlers (Phase 4) -----
FString HandleListMaterialFunctions(const TMap<FString, FString>& Params);
FString HandleGetMaterialFunction(const TMap<FString, FString>& Params);
FString HandleCreateMaterialFunction(const FString& Body);
void HandleListMaterialFunctions(const FJsonObject* Json, FJsonObject* Result);
void HandleGetMaterialFunction(const FJsonObject* Json, FJsonObject* Result);
void HandleCreateMaterialFunction(const FJsonObject* Json, FJsonObject* Result);
// ----- Material validation -----
FString HandleValidateMaterial(const FString& Body);
void HandleValidateMaterial(const FJsonObject* Json, FJsonObject* Result);
// ----- Material snapshot/diff/restore (Phase 5) -----
FString HandleSnapshotMaterialGraph(const FString& Body);
FString HandleDiffMaterialGraph(const FString& Body);
FString HandleRestoreMaterialGraph(const FString& Body);
void HandleSnapshotMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleDiffMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
void HandleRestoreMaterialGraph(const FJsonObject* Json, FJsonObject* Result);
// ----- Animation Blueprint handlers -----
FString HandleCreateAnimBlueprint(const FString& Body);
FString HandleAddAnimState(const FString& Body);
FString HandleRemoveAnimState(const FString& Body);
FString HandleAddAnimTransition(const FString& Body);
FString HandleSetTransitionRule(const FString& Body);
FString HandleAddAnimNode(const FString& Body);
FString HandleAddStateMachine(const FString& Body);
FString HandleSetStateAnimation(const FString& Body);
FString HandleListAnimSlots(const FString& Body);
FString HandleListSyncGroups(const FString& Body);
FString HandleCreateBlendSpace(const FString& Body);
FString HandleSetBlendSpaceSamples(const FString& Body);
FString HandleSetStateBlendSpace(const FString& Body);
void HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result);
void HandleAddAnimState(const FJsonObject* Json, FJsonObject* Result);
void HandleRemoveAnimState(const FJsonObject* Json, FJsonObject* Result);
void HandleAddAnimTransition(const FJsonObject* Json, FJsonObject* Result);
void HandleSetTransitionRule(const FJsonObject* Json, FJsonObject* Result);
void HandleAddAnimNode(const FJsonObject* Json, FJsonObject* Result);
void HandleAddStateMachine(const FJsonObject* Json, FJsonObject* Result);
void HandleSetStateAnimation(const FJsonObject* Json, FJsonObject* Result);
void HandleListAnimSlots(const FJsonObject* Json, FJsonObject* Result);
void HandleListSyncGroups(const FJsonObject* Json, FJsonObject* Result);
void HandleCreateBlendSpace(const FJsonObject* Json, FJsonObject* Result);
void HandleSetBlendSpaceSamples(const FJsonObject* Json, FJsonObject* Result);
void HandleSetStateBlendSpace(const FJsonObject* Json, FJsonObject* Result);
// ----- Serialization -----
TSharedRef<FJsonObject> SerializeBlueprint(UBlueprint* BP);
@@ -292,7 +292,9 @@ private:
UEdGraphNode* FindNodeByGuid(UBlueprint* BP, const FString& GuidString, UEdGraph** OutGraph = nullptr);
TSharedPtr<FJsonObject> ParseBodyJson(const FString& Body);
FString MakeErrorJson(const FString& Message);
void MakeErrorJson(FJsonObject* Result, const FString& Message);
bool SaveBlueprintPackage(UBlueprint* BP);
static void CopyJsonFields(const FJsonObject* Source, FJsonObject* Dest);
static FString UrlDecode(const FString& EncodedString);
// ----- Material helpers -----