diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp index 83324dbc..c6a4101a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers.cpp @@ -5,3 +5,4 @@ #include "MCPHandlers_AssetMutation.h" #include "MCPHandlers_Validation.h" #include "MCPHandlers_UserTypes.h" +#include "MCPHandlers_AnimMutation.h" diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.cpp deleted file mode 100644 index f8d0723e..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.cpp +++ /dev/null @@ -1,577 +0,0 @@ -#include "MCPAssetFinder.h" -#include "MCPServer.h" -#include "MCPUtils.h" -#include "Engine/Blueprint.h" -#include "EdGraph/EdGraph.h" -#include "EdGraph/EdGraphNode.h" -#include "EdGraph/EdGraphPin.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Dom/JsonValue.h" -#include "Serialization/JsonReader.h" -#include "Serialization/JsonWriter.h" -#include "Serialization/JsonSerializer.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "UObject/SavePackage.h" - -// Animation Blueprint includes -#include "Animation/AnimBlueprint.h" -#include "Animation/AnimBlueprintGeneratedClass.h" -#include "Animation/Skeleton.h" -#include "AnimGraphNode_StateMachine.h" -#include "AnimGraphNode_AssetPlayerBase.h" -#include "AnimGraphNode_SequencePlayer.h" -#include "AnimGraphNode_BlendSpacePlayer.h" -#include "AnimGraphNode_Base.h" -#include "Animation/AnimSequence.h" -#include "Animation/BlendSpace.h" - -// ============================================================ -// HandleCreateAnimBlueprint — create a new Animation Blueprint -// ============================================================ - -void FBlueprintMCPServer::HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result) -{ - FString Name = Json->GetStringField(TEXT("name")); - FString PackagePath = Json->GetStringField(TEXT("packagePath")); - FString SkeletonName = Json->GetStringField(TEXT("skeleton")); - FString ParentClassName = Json->GetStringField(TEXT("parentClass")); - - if (Name.IsEmpty() || PackagePath.IsEmpty() || SkeletonName.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton")); - } - - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); - } - - // Check if asset already exists - FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } - - // Resolve skeleton - USkeleton* Skeleton = UMCPAssetFinder::LoadAsset(SkeletonName, Result); - if (!Skeleton) return; - - // Resolve parent class (default: UAnimInstance) - UClass* ParentClass = UAnimInstance::StaticClass(); - if (!ParentClassName.IsEmpty() && ParentClassName != TEXT("AnimInstance")) - { - for (TObjectIterator It; It; ++It) - { - if (It->GetName() == ParentClassName && It->IsChildOf(UAnimInstance::StaticClass())) - { - ParentClass = *It; - break; - } - } - } - - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating AnimBlueprint '%s' in '%s' with skeleton '%s'"), - *Name, *PackagePath, *Skeleton->GetName()); - - // Create the package - FString FullPackagePath = PackagePath / Name; - UPackage* Package = CreatePackage(*FullPackagePath); - if (!Package) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); - } - - // Create the Animation Blueprint - UAnimBlueprint* NewAnimBP = CastChecked( - FKismetEditorUtilities::CreateBlueprint( - ParentClass, - Package, - FName(*Name), - BPTYPE_Normal, - UAnimBlueprint::StaticClass(), - UAnimBlueprintGeneratedClass::StaticClass() - )); - - if (!NewAnimBP) - { - return MCPUtils::MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint")); - } - - // Set target skeleton - NewAnimBP->TargetSkeleton = Skeleton; - - // Compile - FKismetEditorUtilities::CompileBlueprint(NewAnimBP); - - // Save - bool bSaved = MCPUtils::SaveBlueprintPackage(NewAnimBP); - - - // Collect graph names - TArray> GraphNames; - TArray AllGraphs; - NewAnimBP->GetAllGraphs(AllGraphs); - for (UEdGraph* Graph : AllGraphs) - { - if (Graph) - { - GraphNames.Add(MakeShared(Graph->GetName())); - } - } - - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"), - *Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprintName"), Name); - Result->SetStringField(TEXT("packagePath"), PackagePath); - Result->SetStringField(TEXT("assetPath"), FullAssetPath); - Result->SetStringField(TEXT("targetSkeleton"), Skeleton->GetName()); - Result->SetStringField(TEXT("parentClass"), ParentClass->GetName()); - Result->SetBoolField(TEXT("isAnimBlueprint"), true); - Result->SetBoolField(TEXT("saved"), bSaved); - Result->SetArrayField(TEXT("graphs"), GraphNames); -} - -// ============================================================ -// Tier 3: AnimGraph Blend Tree Mutation -// ============================================================ - -void FBlueprintMCPServer::HandleAddAnimNode(const FJsonObject* Json, FJsonObject* Result) -{ - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); - FString GraphName = Json->GetStringField(TEXT("graph")); - FString NodeType = Json->GetStringField(TEXT("nodeType")); - - if (BlueprintName.IsEmpty() || NodeType.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeType")); - } - - UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(BlueprintName, Result); - if (!AnimBP) return; - - // Find target graph (default to AnimGraph if not specified) - UEdGraph* TargetGraph = nullptr; - if (GraphName.IsEmpty()) - { - GraphName = TEXT("AnimGraph"); - } - - TArray AllGraphs; - AnimBP->GetAllGraphs(AllGraphs); - for (UEdGraph* Graph : AllGraphs) - { - if (Graph->GetName() == GraphName) - { - TargetGraph = Graph; - break; - } - } - - if (!TargetGraph) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *GraphName)); - } - - int32 PosX = Json->HasField(TEXT("posX")) ? (int32)Json->GetNumberField(TEXT("posX")) : 0; - int32 PosY = Json->HasField(TEXT("posY")) ? (int32)Json->GetNumberField(TEXT("posY")) : 0; - - UAnimGraphNode_Base* NewNode = nullptr; - - if (NodeType == TEXT("SequencePlayer")) - { - UAnimGraphNode_SequencePlayer* SeqNode = NewObject(TargetGraph); - SeqNode->CreateNewGuid(); - SeqNode->PostPlacedNewNode(); - SeqNode->AllocateDefaultPins(); - - // Optionally set animation asset - FString AnimAssetName = Json->GetStringField(TEXT("animationAsset")); - if (!AnimAssetName.IsEmpty()) - { - FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName); - if (FoundAnimAsset) - { - UAnimSequence* AnimSeq = Cast(FoundAnimAsset->GetAsset()); - if (AnimSeq) - { - SeqNode->SetAnimationAsset(AnimSeq); - } - } - } - - NewNode = SeqNode; - } - else if (NodeType == TEXT("BlendSpacePlayer")) - { - UAnimGraphNode_BlendSpacePlayer* BSNode = NewObject(TargetGraph); - BSNode->CreateNewGuid(); - BSNode->PostPlacedNewNode(); - BSNode->AllocateDefaultPins(); - - // Optionally set blend space asset - FString BSAssetName = Json->GetStringField(TEXT("animationAsset")); - if (!BSAssetName.IsEmpty()) - { - FAssetData* FoundBSAsset = UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), BSAssetName); - if (FoundBSAsset) - { - UBlendSpace* BS = Cast(FoundBSAsset->GetAsset()); - if (BS) - { - BSNode->SetAnimationAsset(BS); - } - } - } - - NewNode = BSNode; - } - else if (NodeType == TEXT("StateMachine")) - { - UAnimGraphNode_StateMachine* SMNode = NewObject(TargetGraph); - SMNode->CreateNewGuid(); - SMNode->PostPlacedNewNode(); - SMNode->AllocateDefaultPins(); - NewNode = SMNode; - } - else - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Unsupported nodeType '%s'. Supported: SequencePlayer, BlendSpacePlayer, StateMachine"), - *NodeType)); - } - - if (!NewNode) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create anim node")); - } - - NewNode->NodePosX = PosX; - NewNode->NodePosY = PosY; - TargetGraph->AddNode(NewNode, false, false); - NewNode->SetFlags(RF_Transactional); - - // Compile and save - FKismetEditorUtilities::CompileBlueprint(AnimBP); - bool bSaved = MCPUtils::SaveBlueprintPackage(AnimBP); - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("nodeType"), NodeType); - Result->SetStringField(TEXT("nodeId"), NewNode->NodeGuid.ToString()); - Result->SetStringField(TEXT("graph"), GraphName); - Result->SetBoolField(TEXT("saved"), bSaved); - - // For StateMachine, report the sub-graph name - if (NodeType == TEXT("StateMachine")) - { - if (UAnimGraphNode_StateMachine* SMNode = Cast(NewNode)) - { - if (SMNode->EditorStateMachineGraph) - { - Result->SetStringField(TEXT("stateMachineGraph"), SMNode->EditorStateMachineGraph->GetName()); - } - } - } -} - -// ============================================================ -// Tier 4: Advanced Operations -// ============================================================ - -void FBlueprintMCPServer::HandleAddStateMachine(const FJsonObject* Json, FJsonObject* Result) -{ - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); - FString MachineName = Json->GetStringField(TEXT("name")); - - if (BlueprintName.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); - } - - // Default name - if (MachineName.IsEmpty()) - { - MachineName = TEXT("NewStateMachine"); - } - - // Delegate to HandleAddAnimNode with StateMachine type and AnimGraph as target - TSharedRef ForwardJson = MakeShared(); - ForwardJson->SetStringField(TEXT("blueprint"), BlueprintName); - ForwardJson->SetStringField(TEXT("graph"), TEXT("AnimGraph")); - ForwardJson->SetStringField(TEXT("nodeType"), TEXT("StateMachine")); - if (Json->HasField(TEXT("posX"))) - ForwardJson->SetNumberField(TEXT("posX"), Json->GetNumberField(TEXT("posX"))); - if (Json->HasField(TEXT("posY"))) - ForwardJson->SetNumberField(TEXT("posY"), Json->GetNumberField(TEXT("posY"))); - - HandleAddAnimNode(&*ForwardJson, Result); -} - -void FBlueprintMCPServer::HandleListAnimSlots(const FJsonObject* Json, FJsonObject* Result) -{ - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); - if (BlueprintName.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); - } - - UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(BlueprintName, Result); - if (!AnimBP) return; - - // Walk all anim nodes to collect slot names - TSet SlotNames; - TArray AllGraphs; - AnimBP->GetAllGraphs(AllGraphs); - for (UEdGraph* Graph : AllGraphs) - { - for (UEdGraphNode* Node : Graph->Nodes) - { - if (UAnimGraphNode_Base* AnimNode = Cast(Node)) - { - // Check for SlotName property via reflection - for (TFieldIterator PropIt(AnimNode->GetClass()); PropIt; ++PropIt) - { - if (PropIt->GetName().Contains(TEXT("SlotName")) || PropIt->GetName().Contains(TEXT("Slot"))) - { - FName SlotValue = PropIt->GetPropertyValue_InContainer(AnimNode); - if (!SlotValue.IsNone()) - { - SlotNames.Add(SlotValue.ToString()); - } - } - } - } - } - } - - TArray> SlotsArr; - for (const FString& Slot : SlotNames) - { - SlotsArr.Add(MakeShared(Slot)); - } - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), BlueprintName); - Result->SetArrayField(TEXT("slots"), SlotsArr); - Result->SetNumberField(TEXT("count"), SlotsArr.Num()); -} - -void FBlueprintMCPServer::HandleListSyncGroups(const FJsonObject* Json, FJsonObject* Result) -{ - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); - if (BlueprintName.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); - } - - UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(BlueprintName, Result); - if (!AnimBP) return; - - // Walk all anim nodes to collect sync group names - TSet SyncGroupNames; - TArray AllGraphs; - AnimBP->GetAllGraphs(AllGraphs); - for (UEdGraph* Graph : AllGraphs) - { - for (UEdGraphNode* Node : Graph->Nodes) - { - if (UAnimGraphNode_Base* AnimNode = Cast(Node)) - { - // Check for SyncGroup/GroupName property via reflection - for (TFieldIterator PropIt(AnimNode->GetClass()); PropIt; ++PropIt) - { - if (PropIt->GetName().Contains(TEXT("SyncGroup")) || PropIt->GetName().Contains(TEXT("GroupName"))) - { - FName GroupValue = PropIt->GetPropertyValue_InContainer(AnimNode); - if (!GroupValue.IsNone()) - { - SyncGroupNames.Add(GroupValue.ToString()); - } - } - } - } - } - } - - TArray> GroupsArr; - for (const FString& Group : SyncGroupNames) - { - GroupsArr.Add(MakeShared(Group)); - } - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blueprint"), BlueprintName); - Result->SetArrayField(TEXT("syncGroups"), GroupsArr); - Result->SetNumberField(TEXT("count"), GroupsArr.Num()); -} - -// ============================================================ -// HandleCreateBlendSpace — create a new 2D Blend Space asset -// ============================================================ - -void FBlueprintMCPServer::HandleCreateBlendSpace(const FJsonObject* Json, FJsonObject* Result) -{ - 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 MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton")); - } - - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); - } - - // Check if asset already exists - FString FullAssetPath = PackagePath / Name; - if (UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), Name)) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf( - TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."), - *Name)); - } - - // Resolve skeleton - USkeleton* Skeleton = UMCPAssetFinder::LoadAsset(SkeletonName, Result); - if (!Skeleton) return; - - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blend Space '%s' in '%s' with skeleton '%s'"), - *Name, *PackagePath, *Skeleton->GetName()); - - // Create the package - FString FullPackagePath = PackagePath / Name; - UPackage* Package = CreatePackage(*FullPackagePath); - if (!Package) - { - return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); - } - - // Create the Blend Space - UBlendSpace* NewBS = NewObject(Package, FName(*Name), RF_Public | RF_Standalone); - if (!NewBS) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create Blend Space object")); - } - - // Set skeleton - NewBS->SetSkeleton(Skeleton); - - // Mark dirty and save - NewBS->MarkPackageDirty(); - bool bSaved = MCPUtils::SaveGenericPackage(NewBS); - - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"), - *Name, bSaved ? TEXT("true") : TEXT("false")); - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("assetPath"), FullAssetPath); - Result->SetStringField(TEXT("skeleton"), Skeleton->GetName()); - Result->SetBoolField(TEXT("saved"), bSaved); -} - -// ============================================================ -// HandleSetBlendSpaceSamples — add animation samples to a Blend Space -// ============================================================ - -void FBlueprintMCPServer::HandleSetBlendSpaceSamples(const FJsonObject* Json, FJsonObject* Result) -{ - FString BlendSpaceName = Json->GetStringField(TEXT("blendSpace")); - if (BlendSpaceName.IsEmpty()) - { - return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blendSpace")); - } - - // Load the blend space - UBlendSpace* BS = UMCPAssetFinder::LoadAsset(BlendSpaceName, Result); - if (!BS) return; - - // Set axis parameters - BS->PreEditChange(nullptr); - - FString AxisXName = Json->GetStringField(TEXT("axisXName")); - FString AxisYName = Json->GetStringField(TEXT("axisYName")); - - const FBlendParameter& ParamX = BS->GetBlendParameter(0); - const FBlendParameter& ParamY = BS->GetBlendParameter(1); - - // We need to modify BlendParameters directly — use const_cast since there's no setter API - FBlendParameter& MutableParamX = const_cast(ParamX); - FBlendParameter& MutableParamY = const_cast(ParamY); - - if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName; - if (Json->HasField(TEXT("axisXMin"))) MutableParamX.Min = (float)Json->GetNumberField(TEXT("axisXMin")); - if (Json->HasField(TEXT("axisXMax"))) MutableParamX.Max = (float)Json->GetNumberField(TEXT("axisXMax")); - - if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName; - if (Json->HasField(TEXT("axisYMin"))) MutableParamY.Min = (float)Json->GetNumberField(TEXT("axisYMin")); - if (Json->HasField(TEXT("axisYMax"))) MutableParamY.Max = (float)Json->GetNumberField(TEXT("axisYMax")); - - // Clear existing samples (delete from end to start) - int32 NumExisting = BS->GetNumberOfBlendSamples(); - for (int32 i = NumExisting - 1; i >= 0; --i) - { - BS->DeleteSample(i); - } - - // Add new samples - const TArray>* SamplesArray = nullptr; - int32 SamplesSet = 0; - - if (Json->TryGetArrayField(TEXT("samples"), SamplesArray) && SamplesArray) - { - for (const TSharedPtr& SampleVal : *SamplesArray) - { - const TSharedPtr& SampleObj = SampleVal->AsObject(); - if (!SampleObj.IsValid()) continue; - - FString AnimAssetName = SampleObj->GetStringField(TEXT("animationAsset")); - float X = (float)SampleObj->GetNumberField(TEXT("x")); - float Y = (float)SampleObj->GetNumberField(TEXT("y")); - - UAnimSequence* AnimSeq = nullptr; - if (!AnimAssetName.IsEmpty()) - { - FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName); - if (FoundAnimAsset) - { - AnimSeq = Cast(FoundAnimAsset->GetAsset()); - } - } - - FVector SampleValue(X, Y, 0.0f); - if (AnimSeq) - { - BS->AddSample(AnimSeq, SampleValue); - } - else - { - BS->AddSample(SampleValue); - } - SamplesSet++; - } - } - - BS->ValidateSampleData(); - BS->PostEditChange(); - - // Save - BS->MarkPackageDirty(); - 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")); - - Result->SetBoolField(TEXT("success"), true); - Result->SetStringField(TEXT("blendSpace"), BS->GetPathName()); - Result->SetNumberField(TEXT("samplesSet"), SamplesSet); - Result->SetBoolField(TEXT("saved"), bSaved); -} - diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h new file mode 100644 index 00000000..9225b694 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AnimMutation.h @@ -0,0 +1,496 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPAssetFinder.h" +#include "MCPUtils.h" +#include "Engine/Blueprint.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphNode.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Dom/JsonValue.h" +#include "Animation/AnimBlueprint.h" +#include "Animation/AnimBlueprintGeneratedClass.h" +#include "Animation/Skeleton.h" +#include "AnimGraphNode_Base.h" +#include "Animation/AnimSequence.h" +#include "Animation/BlendSpace.h" +#include "MCPHandlers_AnimMutation.generated.h" + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="create_anim_blueprint_asset")) +class UMCPHandler_CreateAnimBlueprint : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Name for the new Animation Blueprint asset")) + FString Name; + + UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) + FString PackagePath; + + UPROPERTY(meta=(Description="Name or path of the skeleton asset to use")) + FString Skeleton; + + UPROPERTY(meta=(Optional, Description="Parent class name (default: AnimInstance)")) + FString ParentClass; + + virtual FString GetDescription() const override + { + return TEXT("Create a new Animation Blueprint asset with a specified skeleton."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + + if (Name.IsEmpty() || PackagePath.IsEmpty() || Skeleton.IsEmpty()) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton")); + } + + if (!PackagePath.StartsWith(TEXT("/Game"))) + { + return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); + } + + // Check if asset already exists + FString FullAssetPath = PackagePath / Name; + if (UMCPAssetFinder::FindAsset(UBlueprint::StaticClass(), Name)) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf( + TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."), + *Name)); + } + + // Resolve skeleton + USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset(Skeleton, Result); + if (!SkeletonObj) return; + + // Resolve parent class (default: UAnimInstance) + UClass* ParentClassObj = UAnimInstance::StaticClass(); + if (!ParentClass.IsEmpty() && ParentClass != TEXT("AnimInstance")) + { + for (TObjectIterator It; It; ++It) + { + if (It->GetName() == ParentClass && It->IsChildOf(UAnimInstance::StaticClass())) + { + ParentClassObj = *It; + break; + } + } + } + + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating AnimBlueprint '%s' in '%s' with skeleton '%s'"), + *Name, *PackagePath, *SkeletonObj->GetName()); + + // Create the package + FString FullPackagePath = PackagePath / Name; + UPackage* Package = CreatePackage(*FullPackagePath); + if (!Package) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); + } + + // Create the Animation Blueprint + UAnimBlueprint* NewAnimBP = CastChecked( + FKismetEditorUtilities::CreateBlueprint( + ParentClassObj, + Package, + FName(*Name), + BPTYPE_Normal, + UAnimBlueprint::StaticClass(), + UAnimBlueprintGeneratedClass::StaticClass() + )); + + if (!NewAnimBP) + { + return MCPUtils::MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null for AnimBlueprint")); + } + + // Set target skeleton + NewAnimBP->TargetSkeleton = SkeletonObj; + + // Compile + FKismetEditorUtilities::CompileBlueprint(NewAnimBP); + + // Save + bool bSaved = MCPUtils::SaveBlueprintPackage(NewAnimBP); + + + // Collect graph names + TArray> GraphNames; + TArray AllGraphs; + NewAnimBP->GetAllGraphs(AllGraphs); + for (UEdGraph* Graph : AllGraphs) + { + if (Graph) + { + GraphNames.Add(MakeShared(Graph->GetName())); + } + } + + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"), + *Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); + + Result->SetBoolField(TEXT("success"), true); + Result->SetStringField(TEXT("blueprintName"), Name); + Result->SetStringField(TEXT("packagePath"), PackagePath); + Result->SetStringField(TEXT("assetPath"), FullAssetPath); + Result->SetStringField(TEXT("targetSkeleton"), SkeletonObj->GetName()); + Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName()); + Result->SetBoolField(TEXT("isAnimBlueprint"), true); + Result->SetBoolField(TEXT("saved"), bSaved); + Result->SetArrayField(TEXT("graphs"), GraphNames); + } +}; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="list_anim_slot_names")) +class UMCPHandler_ListAnimSlots : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Animation Blueprint name or package path")) + FString Blueprint; + + virtual FString GetDescription() const override + { + return TEXT("List all animation slot names used in an Animation Blueprint."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + if (Blueprint.IsEmpty()) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); + } + + UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(Blueprint, Result); + if (!AnimBP) return; + + // Walk all anim nodes to collect slot names + TSet SlotNames; + TArray AllGraphs; + AnimBP->GetAllGraphs(AllGraphs); + for (UEdGraph* Graph : AllGraphs) + { + for (UEdGraphNode* Node : Graph->Nodes) + { + if (UAnimGraphNode_Base* AnimNode = Cast(Node)) + { + // Check for SlotName property via reflection + for (TFieldIterator PropIt(AnimNode->GetClass()); PropIt; ++PropIt) + { + if (PropIt->GetName().Contains(TEXT("SlotName")) || PropIt->GetName().Contains(TEXT("Slot"))) + { + FName SlotValue = PropIt->GetPropertyValue_InContainer(AnimNode); + if (!SlotValue.IsNone()) + { + SlotNames.Add(SlotValue.ToString()); + } + } + } + } + } + } + + TArray> SlotsArr; + for (const FString& Slot : SlotNames) + { + SlotsArr.Add(MakeShared(Slot)); + } + + Result->SetBoolField(TEXT("success"), true); + Result->SetStringField(TEXT("blueprint"), Blueprint); + Result->SetArrayField(TEXT("slots"), SlotsArr); + Result->SetNumberField(TEXT("count"), SlotsArr.Num()); + } +}; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="list_anim_sync_groups")) +class UMCPHandler_ListSyncGroups : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Animation Blueprint name or package path")) + FString Blueprint; + + virtual FString GetDescription() const override + { + return TEXT("List all sync group names used in an Animation Blueprint."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + if (Blueprint.IsEmpty()) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blueprint")); + } + + UAnimBlueprint* AnimBP = UMCPAssetFinder::LoadAsset(Blueprint, Result); + if (!AnimBP) return; + + // Walk all anim nodes to collect sync group names + TSet SyncGroupNames; + TArray AllGraphs; + AnimBP->GetAllGraphs(AllGraphs); + for (UEdGraph* Graph : AllGraphs) + { + for (UEdGraphNode* Node : Graph->Nodes) + { + if (UAnimGraphNode_Base* AnimNode = Cast(Node)) + { + // Check for SyncGroup/GroupName property via reflection + for (TFieldIterator PropIt(AnimNode->GetClass()); PropIt; ++PropIt) + { + if (PropIt->GetName().Contains(TEXT("SyncGroup")) || PropIt->GetName().Contains(TEXT("GroupName"))) + { + FName GroupValue = PropIt->GetPropertyValue_InContainer(AnimNode); + if (!GroupValue.IsNone()) + { + SyncGroupNames.Add(GroupValue.ToString()); + } + } + } + } + } + } + + TArray> GroupsArr; + for (const FString& Group : SyncGroupNames) + { + GroupsArr.Add(MakeShared(Group)); + } + + Result->SetBoolField(TEXT("success"), true); + Result->SetStringField(TEXT("blueprint"), Blueprint); + Result->SetArrayField(TEXT("syncGroups"), GroupsArr); + Result->SetNumberField(TEXT("count"), GroupsArr.Num()); + } +}; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="create_blend_space_asset")) +class UMCPHandler_CreateBlendSpace : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Name for the new Blend Space asset")) + FString Name; + + UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) + FString PackagePath; + + UPROPERTY(meta=(Description="Name or path of the skeleton asset to use")) + FString Skeleton; + + virtual FString GetDescription() const override + { + return TEXT("Create a new 2D Blend Space asset with a specified skeleton."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + + if (Name.IsEmpty() || PackagePath.IsEmpty() || Skeleton.IsEmpty()) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, skeleton")); + } + + if (!PackagePath.StartsWith(TEXT("/Game"))) + { + return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); + } + + // Check if asset already exists + FString FullAssetPath = PackagePath / Name; + if (UMCPAssetFinder::FindAsset(UBlendSpace::StaticClass(), Name)) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf( + TEXT("Blend Space '%s' already exists. Use a different name or delete the existing asset first."), + *Name)); + } + + // Resolve skeleton + USkeleton* SkeletonObj = UMCPAssetFinder::LoadAsset(Skeleton, Result); + if (!SkeletonObj) return; + + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blend Space '%s' in '%s' with skeleton '%s'"), + *Name, *PackagePath, *SkeletonObj->GetName()); + + // Create the package + FString FullPackagePath = PackagePath / Name; + UPackage* Package = CreatePackage(*FullPackagePath); + if (!Package) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); + } + + // Create the Blend Space + UBlendSpace* NewBS = NewObject(Package, FName(*Name), RF_Public | RF_Standalone); + if (!NewBS) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create Blend Space object")); + } + + // Set skeleton + NewBS->SetSkeleton(SkeletonObj); + + // Mark dirty and save + NewBS->MarkPackageDirty(); + bool bSaved = MCPUtils::SaveGenericPackage(NewBS); + + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blend Space '%s' (saved: %s)"), + *Name, bSaved ? TEXT("true") : TEXT("false")); + + Result->SetBoolField(TEXT("success"), true); + Result->SetStringField(TEXT("assetPath"), FullAssetPath); + Result->SetStringField(TEXT("skeleton"), SkeletonObj->GetName()); + Result->SetBoolField(TEXT("saved"), bSaved); + } +}; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="set_blend_space_sample_points")) +class UMCPHandler_SetBlendSpaceSamples : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Blend Space asset name or package path")) + FString BlendSpace; + + UPROPERTY(meta=(Optional, Description="Display name for the X axis")) + FString AxisXName; + + UPROPERTY(meta=(Optional, Description="Display name for the Y axis")) + FString AxisYName; + + UPROPERTY(meta=(Optional, Description="Minimum value for X axis")) + float AxisXMin = 0.0f; + + UPROPERTY(meta=(Optional, Description="Maximum value for X axis")) + float AxisXMax = 0.0f; + + UPROPERTY(meta=(Optional, Description="Minimum value for Y axis")) + float AxisYMin = 0.0f; + + UPROPERTY(meta=(Optional, Description="Maximum value for Y axis")) + float AxisYMax = 0.0f; + + UPROPERTY(meta=(Optional, Description="Array of sample points, each with animationAsset, x, y")) + FMCPJsonArray Samples; + + virtual FString GetDescription() const override + { + return TEXT("Set axis parameters and animation sample points on a Blend Space. " + "Replaces all existing samples."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + if (BlendSpace.IsEmpty()) + { + return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: blendSpace")); + } + + // Load the blend space + UBlendSpace* BS = UMCPAssetFinder::LoadAsset(BlendSpace, Result); + if (!BS) return; + + // Set axis parameters + BS->PreEditChange(nullptr); + + const FBlendParameter& ParamX = BS->GetBlendParameter(0); + const FBlendParameter& ParamY = BS->GetBlendParameter(1); + + // We need to modify BlendParameters directly — use const_cast since there's no setter API + FBlendParameter& MutableParamX = const_cast(ParamX); + FBlendParameter& MutableParamY = const_cast(ParamY); + + if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName; + if (Json->HasField(TEXT("axisXMin"))) MutableParamX.Min = AxisXMin; + if (Json->HasField(TEXT("axisXMax"))) MutableParamX.Max = AxisXMax; + + if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName; + if (Json->HasField(TEXT("axisYMin"))) MutableParamY.Min = AxisYMin; + if (Json->HasField(TEXT("axisYMax"))) MutableParamY.Max = AxisYMax; + + // Clear existing samples (delete from end to start) + int32 NumExisting = BS->GetNumberOfBlendSamples(); + for (int32 i = NumExisting - 1; i >= 0; --i) + { + BS->DeleteSample(i); + } + + // Add new samples + int32 SamplesSet = 0; + + for (const TSharedPtr& SampleVal : Samples.Array) + { + const TSharedPtr& SampleObj = SampleVal->AsObject(); + if (!SampleObj.IsValid()) continue; + + FString AnimAssetName = SampleObj->GetStringField(TEXT("animationAsset")); + float X = (float)SampleObj->GetNumberField(TEXT("x")); + float Y = (float)SampleObj->GetNumberField(TEXT("y")); + + UAnimSequence* AnimSeq = nullptr; + if (!AnimAssetName.IsEmpty()) + { + FAssetData* FoundAnimAsset = UMCPAssetFinder::FindAsset(UAnimSequence::StaticClass(), AnimAssetName); + if (FoundAnimAsset) + { + AnimSeq = Cast(FoundAnimAsset->GetAsset()); + } + } + + FVector SampleValue(X, Y, 0.0f); + if (AnimSeq) + { + BS->AddSample(AnimSeq, SampleValue); + } + else + { + BS->AddSample(SampleValue); + } + SamplesSet++; + } + + BS->ValidateSampleData(); + BS->PostEditChange(); + + // Save + BS->MarkPackageDirty(); + 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")); + + Result->SetBoolField(TEXT("success"), true); + Result->SetStringField(TEXT("blendSpace"), BS->GetPathName()); + Result->SetNumberField(TEXT("samplesSet"), SamplesSet); + Result->SetBoolField(TEXT("saved"), bSaved); + } +}; + diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h index cf66562a..0a47baaf 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_Mutation.h @@ -411,7 +411,7 @@ public: EntryResult->SetStringField(TEXT("actionName"), Entry.ActionName); // Find the spawner by exact full name - TArray Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true); + TArray Matches = MCPUtils::SearchNodeSpawners(Entry.ActionName, 0, /*ExactMatch=*/true, TargetGraph); if (Matches.Num() == 0) { EntryResult->SetStringField(TEXT("error"), FString::Printf( @@ -1491,17 +1491,48 @@ public: UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50, max 500)")) int32 MaxResults = 50; + UPROPERTY(meta=(Optional, Description="Blueprint name or path. If specified with graph, only returns nodes compatible with that graph.")) + FString Blueprint; + + UPROPERTY(meta=(Optional, Description="Graph name to filter by compatibility. Requires blueprint.")) + FString Graph; + virtual FString GetDescription() const override { return TEXT("Search the Blueprint action database for node spawners matching a query. " - "Returns full action names for use with spawn_node."); + "Returns full action names for use with spawn_node. " + "Optionally filter by blueprint+graph to only show compatible node types."); } virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override { int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500); - TArray Spawners = MCPUtils::SearchNodeSpawners(Query, ClampedMax); + // Optionally resolve a graph to filter by compatibility + UEdGraph* GraphFilter = nullptr; + if (!Blueprint.IsEmpty() && !Graph.IsEmpty()) + { + UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Blueprint, Result); + if (!BP) return; + + FString DecodedGraphName = MCPUtils::UrlDecode(Graph); + TArray AllGraphs; + BP->GetAllGraphs(AllGraphs); + for (UEdGraph* G : AllGraphs) + { + if (G && G->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase)) + { + GraphFilter = G; + break; + } + } + if (!GraphFilter) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); + } + } + + TArray Spawners = MCPUtils::SearchNodeSpawners(Query, ClampedMax, /*ExactMatch=*/false, GraphFilter); TArray> ResultArray; for (UBlueprintNodeSpawner* Spawner : Spawners) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp index 8766b15f..c32c0c4c 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp @@ -876,13 +876,10 @@ void FMCPServer::RegisterHandlers() TEXT("reparent_material_instance"), TEXT("create_material_function_asset"), TEXT("restore_material_graph_from_snapshot"), - TEXT("create_anim_blueprint_asset"), TEXT("add_anim_state_to_machine"), TEXT("remove_anim_state_from_machine"), TEXT("add_anim_state_transition"), TEXT("set_anim_transition_rule"), - TEXT("add_anim_graph_node"), - TEXT("add_anim_state_machine"), TEXT("set_anim_state_animation"), }; @@ -951,18 +948,11 @@ void FMCPServer::RegisterHandlers() H(TEXT("diff_material_graph_vs_snapshot"), &FMCPServer::HandleDiffMaterialGraph); H(TEXT("restore_material_graph_from_snapshot"), &FMCPServer::HandleRestoreMaterialGraph); H(TEXT("compile_material"), &FMCPServer::HandleValidateMaterial); - H(TEXT("create_anim_blueprint_asset"), &FMCPServer::HandleCreateAnimBlueprint); H(TEXT("add_anim_state_to_machine"), &FMCPServer::HandleAddAnimState); H(TEXT("remove_anim_state_from_machine"), &FMCPServer::HandleRemoveAnimState); H(TEXT("add_anim_state_transition"), &FMCPServer::HandleAddAnimTransition); H(TEXT("set_anim_transition_rule"), &FMCPServer::HandleSetTransitionRule); - H(TEXT("add_anim_graph_node"), &FMCPServer::HandleAddAnimNode); - H(TEXT("add_anim_state_machine"), &FMCPServer::HandleAddStateMachine); H(TEXT("set_anim_state_animation"), &FMCPServer::HandleSetStateAnimation); - H(TEXT("list_anim_slot_names"), &FMCPServer::HandleListAnimSlots); - H(TEXT("list_anim_sync_groups"), &FMCPServer::HandleListSyncGroups); - H(TEXT("create_blend_space_asset"), &FMCPServer::HandleCreateBlendSpace); - H(TEXT("set_blend_space_sample_points"), &FMCPServer::HandleSetBlendSpaceSamples); H(TEXT("set_anim_state_blend_space"), &FMCPServer::HandleSetStateBlendSpace); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp index 262948da..f02c236d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp @@ -1188,53 +1188,54 @@ FString MCPUtils::NodeSpawnerFullName(UBlueprintNodeSpawner* Spawner) return Category + TEXT("|") + MenuName; } -TArray MCPUtils::AllNodeSpawners() +TArray MCPUtils::SearchNodeSpawners(const FString& Query, int32 MaxResults, bool ExactMatch, UEdGraph* GraphFilter) { + FString QueryLower = Query.ToLower(); TArray Result; + for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions()) { for (UBlueprintNodeSpawner* Spawner : Pair.Value) { if (!Spawner) continue; if (Spawner->PrimeDefaultUiSpec().MenuName.IsEmpty()) continue; + + // Filter by graph compatibility if a graph was provided + if (GraphFilter && Spawner->NodeClass) + { + UEdGraphNode* NodeCDO = CastChecked(Spawner->NodeClass->ClassDefaultObject); + if (!NodeCDO->IsCompatibleWithGraph(GraphFilter)) + { + continue; + } + } + + FString FullName = NodeSpawnerFullName(Spawner); + + if (ExactMatch) + { + if (FullName.ToLower() != QueryLower) + { + continue; + } + } + else + { + FString Keywords = Spawner->PrimeDefaultUiSpec().Keywords.ToString(); + if (!FullName.ToLower().Contains(QueryLower) + && !Keywords.ToLower().Contains(QueryLower)) + { + continue; + } + } + Result.Add(Spawner); - } - } - return Result; -} -TArray MCPUtils::SearchNodeSpawners(const FString& Query, int32 MaxResults, bool ExactMatch) -{ - FString QueryLower = Query.ToLower(); - TArray Result; - - for (UBlueprintNodeSpawner* Spawner : AllNodeSpawners()) - { - FString FullName = NodeSpawnerFullName(Spawner); - - if (ExactMatch) - { - if (FullName.ToLower() != QueryLower) + if ((MaxResults > 0) && (Result.Num() >= MaxResults)) { - continue; + break; } } - else - { - FString Keywords = Spawner->PrimeDefaultUiSpec().Keywords.ToString(); - if (!FullName.ToLower().Contains(QueryLower) - && !Keywords.ToLower().Contains(QueryLower)) - { - continue; - } - } - - Result.Add(Spawner); - - if ((MaxResults > 0) && (Result.Num() >= MaxResults)) - { - break; - } } return Result; } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h index 4dee4699..5222a654 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h @@ -203,19 +203,12 @@ private: void HandleDiffMaterialGraph(const FJsonObject* Json, FJsonObject* Result); void HandleRestoreMaterialGraph(const FJsonObject* Json, FJsonObject* Result); - // ----- Animation Blueprint handlers ----- - void HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result); + // ----- Animation Blueprint handlers (state machine, still old-style) ----- 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); public: diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index e53b396f..6378f316 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -157,8 +157,7 @@ public: // ----- Node spawners ----- static FString NodeSpawnerFullName(UBlueprintNodeSpawner* Spawner); - static TArray AllNodeSpawners(); - static TArray SearchNodeSpawners(const FString& Query, int32 MaxResults = 0, bool ExactMatch = false); + static TArray SearchNodeSpawners(const FString& Query, int32 MaxResults = 0, bool ExactMatch = false, UEdGraph* GraphFilter = nullptr); // ----- Property population ----- static FString PropertyNameToJsonKey(const FString& PropName);