Create MCPUtils

This commit is contained in:
2026-03-06 15:06:12 -05:00
parent 7e3a4d2fea
commit 9f405ce5ba
22 changed files with 1872 additions and 1736 deletions

View File

@@ -1,4 +1,5 @@
#include "BlueprintMCPServer.h"
#include "MCPUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
@@ -47,19 +48,19 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
if (Name.IsEmpty() || PackagePath.IsEmpty() || SkeletonName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
}
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
FString FullAssetPath = PackagePath / Name;
if (FindBlueprintAsset(Name) || FindBlueprintAsset(FullAssetPath))
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
@@ -114,7 +115,7 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
if (!Skeleton)
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
*SkeletonName));
}
@@ -141,7 +142,7 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
UPackage* Package = CreatePackage(*FullPackagePath);
if (!Package)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
}
// Create the Animation Blueprint
@@ -157,7 +158,7 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
if (!NewAnimBP)
{
return MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint"));
return MCPUtils::MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint"));
}
// Set target skeleton
@@ -167,7 +168,7 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
FKismetEditorUtilities::CompileBlueprint(NewAnimBP);
// Save
bool bSaved = SaveBlueprintPackage(NewAnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(NewAnimBP);
// Refresh asset cache
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
@@ -204,61 +205,6 @@ void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJs
// Tier 2: State Machine Mutation
// ============================================================
// Helper: find a state machine graph by name within an AnimBlueprint
static UAnimationStateMachineGraph* FindStateMachineGraph(UBlueprint* BP, const FString& GraphName)
{
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* Graph : AllGraphs)
{
if (UAnimationStateMachineGraph* SMGraph = Cast<UAnimationStateMachineGraph>(Graph))
{
if (SMGraph->GetName() == GraphName)
{
return SMGraph;
}
}
}
return nullptr;
}
// Helper: find a state node by name within a state machine graph
static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName)
{
for (UEdGraphNode* Node : SMGraph->Nodes)
{
if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
{
if (StateNode->GetStateName() == StateName)
{
return StateNode;
}
}
}
return nullptr;
}
// Helper: find a transition between two states
static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph,
const FString& FromStateName, const FString& ToStateName)
{
for (UEdGraphNode* Node : SMGraph->Nodes)
{
if (UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(Node))
{
UAnimStateNode* FromState = Cast<UAnimStateNode>(TransNode->GetPreviousState());
UAnimStateNode* ToState = Cast<UAnimStateNode>(TransNode->GetNextState());
if (FromState && ToState &&
FromState->GetStateName() == FromStateName &&
ToState->GetStateName() == ToStateName)
{
return TransNode;
}
}
}
return nullptr;
}
void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObject* Result)
{
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
@@ -267,32 +213,32 @@ void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObjec
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
// Check for duplicate state name
if (FindStateByName(SMGraph, StateName))
if (MCPUtils::FindStateByName(SMGraph, StateName))
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' already exists in graph '%s'"), *StateName, *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' already exists in graph '%s'"), *StateName, *GraphName));
}
// Get position
@@ -352,7 +298,7 @@ void FBlueprintMCPServer::HandleAddAnimState(const FJsonObject* Json, FJsonObjec
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);
@@ -369,32 +315,32 @@ void FBlueprintMCPServer::HandleRemoveAnimState(const FJsonObject* Json, FJsonOb
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
// Collect and remove transitions connected to this state
@@ -423,7 +369,7 @@ void FBlueprintMCPServer::HandleRemoveAnimState(const FJsonObject* Json, FJsonOb
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("removedState"), StateName);
@@ -440,38 +386,38 @@ void FBlueprintMCPServer::HandleAddAnimTransition(const FJsonObject* Json, FJson
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || FromStateName.IsEmpty() || ToStateName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* FromState = FindStateByName(SMGraph, FromStateName);
UAnimStateNode* FromState = MCPUtils::FindStateByName(SMGraph, FromStateName);
if (!FromState)
{
return MakeErrorJson(Result, FString::Printf(TEXT("From state '%s' not found"), *FromStateName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("From state '%s' not found"), *FromStateName));
}
UAnimStateNode* ToState = FindStateByName(SMGraph, ToStateName);
UAnimStateNode* ToState = MCPUtils::FindStateByName(SMGraph, ToStateName);
if (!ToState)
{
return MakeErrorJson(Result, FString::Printf(TEXT("To state '%s' not found"), *ToStateName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("To state '%s' not found"), *ToStateName));
}
// Create transition node
@@ -506,7 +452,7 @@ void FBlueprintMCPServer::HandleAddAnimTransition(const FJsonObject* Json, FJson
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("fromState"), FromStateName);
@@ -527,32 +473,32 @@ void FBlueprintMCPServer::HandleSetTransitionRule(const FJsonObject* Json, FJson
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || FromStateName.IsEmpty() || ToStateName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, fromState, toState"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateTransitionNode* TransNode = FindTransition(SMGraph, FromStateName, ToStateName);
UAnimStateTransitionNode* TransNode = MCPUtils::FindTransition(SMGraph, FromStateName, ToStateName);
if (!TransNode)
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Transition from '%s' to '%s' not found in graph '%s'"),
*FromStateName, *ToStateName, *GraphName));
}
@@ -588,12 +534,12 @@ void FBlueprintMCPServer::HandleSetTransitionRule(const FJsonObject* Json, FJson
if (ChangedCount == 0)
{
return MakeErrorJson(Result, TEXT("No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional"));
return MCPUtils::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);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("fromState"), FromStateName);
@@ -619,20 +565,20 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
if (BlueprintName.IsEmpty() || NodeType.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeType"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeType"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Find target graph (default to AnimGraph if not specified)
@@ -655,7 +601,7 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
if (!TargetGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *GraphName));
}
int32 PosX = Json->HasField(TEXT("posX")) ? (int32)Json->GetNumberField(TEXT("posX")) : 0;
@@ -735,14 +681,14 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
}
else
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Unsupported nodeType '%s'. Supported: SequencePlayer, BlendSpacePlayer, StateMachine"),
*NodeType));
}
if (!NewNode)
{
return MakeErrorJson(Result, TEXT("Failed to create anim node"));
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create anim node"));
}
NewNode->NodePosX = PosX;
@@ -752,7 +698,7 @@ void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("nodeType"), NodeType);
@@ -784,7 +730,7 @@ void FBlueprintMCPServer::HandleAddStateMachine(const FJsonObject* Json, FJsonOb
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
// Default name
@@ -815,38 +761,38 @@ void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJson
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty() || AnimAssetName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, animationAsset"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, animationAsset"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
}
// Find the animation asset
@@ -866,7 +812,7 @@ void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJson
if (!AnimSeq)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Animation asset '%s' not found"), *AnimAssetName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Animation asset '%s' not found"), *AnimAssetName));
}
// Find existing SequencePlayer or create one
@@ -894,7 +840,7 @@ void FBlueprintMCPServer::HandleSetStateAnimation(const FJsonObject* Json, FJson
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);
@@ -908,20 +854,20 @@ void FBlueprintMCPServer::HandleListAnimSlots(const FJsonObject* Json, FJsonObje
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Walk all anim nodes to collect slot names
@@ -967,20 +913,20 @@ void FBlueprintMCPServer::HandleListSyncGroups(const FJsonObject* Json, FJsonObj
FString BlueprintName = Json->GetStringField(TEXT("blueprint"));
if (BlueprintName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
// Walk all anim nodes to collect sync group names
@@ -1033,12 +979,12 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
if (Name.IsEmpty() || PackagePath.IsEmpty() || SkeletonName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton"));
}
if (!PackagePath.StartsWith(TEXT("/Game")))
{
return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
}
// Check if asset already exists
@@ -1051,7 +997,7 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
{
if (Asset.AssetName.ToString() == Name || Asset.GetObjectPathString() == FullAssetPath)
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."),
*Name));
}
@@ -1106,7 +1052,7 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
if (!Skeleton)
{
return MakeErrorJson(Result, FString::Printf(
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Skeleton '%s' not found. Provide the skeleton asset name or path. Use '__create_test_skeleton__' for testing."),
*SkeletonName));
}
@@ -1119,14 +1065,14 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
UPackage* Package = CreatePackage(*FullPackagePath);
if (!Package)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
return MCPUtils::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(Result, TEXT("Failed to create Blend Space object"));
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create Blend Space object"));
}
// Set skeleton
@@ -1134,7 +1080,7 @@ void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonO
// Mark dirty and save
NewBS->MarkPackageDirty();
bool bSaved = SaveGenericPackage(NewBS);
bool bSaved = MCPUtils::SaveGenericPackage(NewBS);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false"));
@@ -1154,7 +1100,7 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
FString BlendSpaceName = Json->GetStringField(TEXT("blendSpace"));
if (BlendSpaceName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required field: blendSpace"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blendSpace"));
}
// Load the blend space
@@ -1190,7 +1136,7 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
if (!BS)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
}
// Set axis parameters
@@ -1288,7 +1234,7 @@ void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJ
// Save
BS->MarkPackageDirty();
bool bSaved = SaveGenericPackage(BS);
bool bSaved = MCPUtils::SaveGenericPackage(BS);
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set %d samples on Blend Space '%s' (saved: %s)"),
SamplesSet, *BS->GetName(), bSaved ? TEXT("true") : TEXT("false"));
@@ -1314,38 +1260,38 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || StateName.IsEmpty() || BlendSpaceName.IsEmpty())
{
return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, blendSpace"));
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, stateName, blendSpace"));
}
FString LoadError;
UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError);
if (!BP)
{
return MakeErrorJson(Result, LoadError);
return MCPUtils::MakeErrorJson(Result, LoadError);
}
UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(BP);
if (!AnimBP)
{
return MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("'%s' is not an Animation Blueprint"), *BlueprintName));
}
UAnimationStateMachineGraph* SMGraph = FindStateMachineGraph(BP, GraphName);
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(BP, GraphName);
if (!SMGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State machine graph '%s' not found"), *GraphName));
}
UAnimStateNode* StateNode = FindStateByName(SMGraph, StateName);
UAnimStateNode* StateNode = MCPUtils::FindStateByName(SMGraph, StateName);
if (!StateNode)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *GraphName));
}
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph)
{
return MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("State '%s' has no bound graph"), *StateName));
}
// Find the blend space asset
@@ -1381,7 +1327,7 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
if (!BlendSpaceAsset)
{
return MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blend Space '%s' not found"), *BlendSpaceName));
}
// Find existing BlendSpacePlayer or create one
@@ -1529,7 +1475,7 @@ void FBlueprintMCPServer::HandleSetStateBlendSpace(const FJsonObject* Json, FJso
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);
bool bSaved = SaveBlueprintPackage(AnimBP);
bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP);
Result->SetBoolField(TEXT("success"), true);
Result->SetStringField(TEXT("stateName"), StateName);