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);
}
}