diff --git a/.gitignore b/.gitignore index 35fe075c..5aab8ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,10 @@ Config/** Saved/** Platforms/** Intermediate/** +Plugins/*/Intermediate/ DerivedDataCache/** Binaries/** +Plugins/*/Binaries/ User/** .vs/** diff --git a/Content/Widgets/WB_Hotkeys.uasset b/Content/Widgets/WB_Hotkeys.uasset index 3304d09c..5c0b82b0 100644 --- a/Content/Widgets/WB_Hotkeys.uasset +++ b/Content/Widgets/WB_Hotkeys.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8183d15f65901aebd326b7d744c5c57a49112a51443a1878537338c2839b5da -size 259136 +oid sha256:d2655e6680b2bf823b001b58e682de1130d1c28ddb01f27baf1b38328182482d +size 324586 diff --git a/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.debug b/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.debug deleted file mode 100644 index b93d2316..00000000 Binary files a/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.debug and /dev/null differ diff --git a/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.sym b/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.sym deleted file mode 100644 index d3a4e90e..00000000 --- a/Plugins/BlueprintMCP/Binaries/Linux/libUnrealEditor-BlueprintMCP-Linux-DebugGame.sym +++ /dev/null @@ -1,5 +0,0 @@ -DummySyms -DummySyms -DummySyms -DummySyms -DummySyms diff --git a/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/libUnrealEditor-BlueprintMCP-Linux-DebugGame.so.rsp b/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/libUnrealEditor-BlueprintMCP-Linux-DebugGame.so.rsp deleted file mode 100644 index da8daef6..00000000 --- a/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/libUnrealEditor-BlueprintMCP-Linux-DebugGame.so.rsp +++ /dev/null @@ -1,30 +0,0 @@ -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Variables.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPServer.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_MaterialInstance.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Read.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Validation.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_MaterialRead.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPModule.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Components.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPEditorSubsystem.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Params.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_UserTypes.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_DiffBlueprints.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_MaterialMutation.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPCommandlet.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Dispatchers.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_AnimMutation.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Discovery.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Mutation.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Snapshot.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Interfaces.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPHandlers_Graphs.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/PerModuleInline.gen.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCP.init.gen.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPCommandlet.gen.cpp.o" -"/home/jyelon/integration/Plugins/BlueprintMCP/Intermediate/Build/Linux/x64/UnrealEditor/DebugGame/BlueprintMCP/BlueprintMCPEditorSubsystem.gen.cpp.o" - -soname="libUnrealEditor-BlueprintMCP-Linux-DebugGame.so" - -rpath-link="/home/jyelon/integration/Plugins/BlueprintMCP/Binaries/Linux" - -L"/home/jyelon/integration.UE/Engine/Binaries/Linux" - --start-group - --end-group \ No newline at end of file diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_AnimMutation.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_AnimMutation.cpp index bf8153a0..0e8b7907 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_AnimMutation.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_AnimMutation.cpp @@ -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 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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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> 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 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(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 Result = MakeShared(); 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 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(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(Slot)); } - TSharedRef Result = MakeShared(); 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 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(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(Group)); } - TSharedRef Result = MakeShared(); 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 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(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 Result = MakeShared(); 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 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 Result = MakeShared(); 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 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(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 Result = MakeShared(); 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); -} \ No newline at end of file +} diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Components.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Components.cpp index 01677432..03bf89b8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Components.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Components.cpp @@ -13,31 +13,25 @@ // HandleListComponents — list all components in a Blueprint's SCS // ============================================================ -FString FBlueprintMCPServer::HandleListComponents(const FString& Body) +void FBlueprintMCPServer::HandleListComponents(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"), *BlueprintName)); } @@ -95,32 +89,24 @@ FString FBlueprintMCPServer::HandleListComponents(const FString& Body) ComponentsArr.Add(MakeShared(CompObj)); } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("count"), ComponentsArr.Num()); Result->SetArrayField(TEXT("components"), ComponentsArr); - return JsonToString(Result); } // ============================================================ // HandleAddComponent — add a component to a Blueprint's SCS // ============================================================ -FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) +void FBlueprintMCPServer::HandleAddComponent(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString ComponentClassName = Json->GetStringField(TEXT("componentClass")); FString ComponentName = Json->GetStringField(TEXT("name")); if (BlueprintName.IsEmpty() || ComponentClassName.IsEmpty() || ComponentName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, componentClass, name")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, componentClass, name")); } FString ParentComponentName; @@ -133,13 +119,13 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"), *BlueprintName)); } @@ -150,7 +136,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) { if (Existing && Existing->GetVariableName().ToString().Equals(ComponentName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A component named '%s' already exists in Blueprint '%s'"), *ComponentName, *BlueprintName)); } @@ -197,7 +183,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) if (!ComponentClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Component class '%s' not found or is not a subclass of UActorComponent. " "Common classes: StaticMeshComponent, SkeletalMeshComponent, AudioComponent, " "SceneComponent, BoxCollisionComponent, SphereCollisionComponent, CapsuleComponent, " @@ -221,7 +207,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) if (!ParentSCSNode) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Parent component '%s' not found in Blueprint '%s'"), *ParentComponentName, *BlueprintName)); } @@ -234,7 +220,7 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) USCS_Node* NewNode = SCS->CreateNode(ComponentClass, FName(*ComponentName)); if (!NewNode) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Failed to create SCS node for component '%s' with class '%s'"), *ComponentName, *ComponentClass->GetName())); } @@ -257,7 +243,6 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) ParentSCSNode ? *ParentComponentName : TEXT("(root)"), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("name"), NewNode->GetVariableName().ToString()); @@ -267,40 +252,33 @@ FString FBlueprintMCPServer::HandleAddComponent(const FString& Body) Result->SetStringField(TEXT("parentComponent"), ParentSCSNode->GetVariableName().ToString()); } Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRemoveComponent — remove a component from a Blueprint's SCS // ============================================================ -FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body) +void FBlueprintMCPServer::HandleRemoveComponent(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString ComponentName = Json->GetStringField(TEXT("name")); if (BlueprintName.IsEmpty() || ComponentName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, name")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, name")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } USimpleConstructionScript* SCS = BP->SimpleConstructionScript; if (!SCS) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)"), *BlueprintName)); } @@ -329,19 +307,18 @@ FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body) } } - TSharedRef ErrorResult = MakeShared(); - ErrorResult->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Component '%s' not found in Blueprint '%s'"), *ComponentName, *BlueprintName)); - ErrorResult->SetArrayField(TEXT("existingComponents"), CompList); - return JsonToString(ErrorResult); + Result->SetArrayField(TEXT("existingComponents"), CompList); + return; } // Prevent removing the root scene component if it has children const TArray& RootNodes = SCS->GetRootNodes(); if (RootNodes.Contains(NodeToRemove) && NodeToRemove->GetChildNodes().Num() > 0) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot remove component '%s' because it is a root component with %d child(ren). " "Remove or re-parent the children first."), *ComponentName, NodeToRemove->GetChildNodes().Num())); @@ -359,10 +336,8 @@ FString FBlueprintMCPServer::HandleRemoveComponent(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed component '%s' from '%s' (saved: %s)"), *ComponentName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("name"), ComponentName); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_DiffBlueprints.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_DiffBlueprints.cpp index 8e14554a..bda9b624 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_DiffBlueprints.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_DiffBlueprints.cpp @@ -17,30 +17,24 @@ // HandleDiffBlueprints — structural diff between two Blueprints // ============================================================ -FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body) +void FBlueprintMCPServer::HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintA = Json->GetStringField(TEXT("blueprintA")); FString BlueprintB = Json->GetStringField(TEXT("blueprintB")); FString GraphFilter = Json->GetStringField(TEXT("graph")); if (BlueprintA.IsEmpty() || BlueprintB.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprintA, blueprintB")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprintA, blueprintB")); } // Load both blueprints FString LoadErrorA, LoadErrorB; UBlueprint* BPA = LoadBlueprintByName(BlueprintA, LoadErrorA); - if (!BPA) return MakeErrorJson(FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); + if (!BPA) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintA: %s"), *LoadErrorA)); return; } UBlueprint* BPB = LoadBlueprintByName(BlueprintB, LoadErrorB); - if (!BPB) return MakeErrorJson(FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); + if (!BPB) { MakeErrorJson(Result, FString::Printf(TEXT("blueprintB: %s"), *LoadErrorB)); return; } // Helper to gather graphs from a Blueprint auto GatherGraphs = [&GraphFilter](UBlueprint* BP) -> TArray @@ -245,7 +239,6 @@ FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprintA"), BlueprintA); Result->SetStringField(TEXT("blueprintB"), BlueprintB); @@ -264,6 +257,4 @@ FString FBlueprintMCPServer::HandleDiffBlueprints(const FString& Body) } TotalDiffs += VarsOnlyInA.Num() + VarsOnlyInB.Num(); Result->SetNumberField(TEXT("totalDifferences"), TotalDiffs); - - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Discovery.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Discovery.cpp index fb66d299..0add4bf7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Discovery.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Discovery.cpp @@ -15,35 +15,29 @@ // HandleGetPinInfo — detailed information about a specific pin // ============================================================ -FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body) +void FBlueprintMCPServer::HandleGetPinInfo(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); FString PinName = Json->GetStringField(TEXT("pinName")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || PinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId, pinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, pinName")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UEdGraph* Graph = nullptr; UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } UEdGraphPin* Pin = Node->FindPin(FName(*PinName)); @@ -62,13 +56,11 @@ FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body) AvailPins.Add(MakeShared(PinObj)); } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); - E->SetArrayField(TEXT("availablePins"), AvailPins); - return JsonToString(E); + MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); + Result->SetArrayField(TEXT("availablePins"), AvailPins); + return; } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); @@ -119,22 +111,14 @@ FString FBlueprintMCPServer::HandleGetPinInfo(const FString& Body) } Result->SetArrayField(TEXT("connectedTo"), Conns); } - - return JsonToString(Result); } // ============================================================ // HandleCheckPinCompatibility — pre-flight check for connect_pins // ============================================================ -FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body) +void FBlueprintMCPServer::HandleCheckPinCompatibility(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString SourceNodeId = Json->GetStringField(TEXT("sourceNodeId")); FString SourcePinName = Json->GetStringField(TEXT("sourcePinName")); @@ -144,51 +128,50 @@ FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body) if (BlueprintName.IsEmpty() || SourceNodeId.IsEmpty() || SourcePinName.IsEmpty() || TargetNodeId.IsEmpty() || TargetPinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UEdGraph* SourceGraph = nullptr; UEdGraphNode* SourceNode = FindNodeByGuid(BP, SourceNodeId, &SourceGraph); if (!SourceNode) { - return MakeErrorJson(FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId)); } UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId); if (!TargetNode) { - return MakeErrorJson(FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); } UEdGraphPin* SourcePin = SourceNode->FindPin(FName(*SourcePinName)); if (!SourcePin) { - return MakeErrorJson(FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId)); } UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName)); if (!TargetPin) { - return MakeErrorJson(FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId)); } const UEdGraphSchema* Schema = SourceGraph ? SourceGraph->GetSchema() : nullptr; if (!Schema) { - return MakeErrorJson(TEXT("Graph schema not found")); + return MakeErrorJson(Result, TEXT("Graph schema not found")); } // Check compatibility using the schema const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); @@ -236,22 +219,14 @@ FString FBlueprintMCPServer::HandleCheckPinCompatibility(const FString& Body) Result->SetStringField(TEXT("targetPinType"), TargetPin->PinType.PinCategory.ToString()); if (TargetPin->PinType.PinSubCategoryObject.IsValid()) Result->SetStringField(TEXT("targetPinSubtype"), TargetPin->PinType.PinSubCategoryObject->GetName()); - - return JsonToString(Result); } // ============================================================ // HandleListClasses — discover available UClasses // ============================================================ -FString FBlueprintMCPServer::HandleListClasses(const FString& Body) +void FBlueprintMCPServer::HandleListClasses(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString Filter = Json->GetStringField(TEXT("filter")); FString ParentClassName = Json->GetStringField(TEXT("parentClass")); int32 Limit = 100; @@ -273,7 +248,7 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body) } if (!ParentClass) { - return MakeErrorJson(FString::Printf(TEXT("Parent class '%s' not found"), *ParentClassName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Parent class '%s' not found"), *ParentClassName)); } } @@ -336,7 +311,6 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body) ClassList.Add(MakeShared(ClassObj)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("count"), ClassList.Num()); Result->SetNumberField(TEXT("totalMatched"), TotalMatched); @@ -346,27 +320,20 @@ FString FBlueprintMCPServer::HandleListClasses(const FString& Body) Result->SetNumberField(TEXT("limit"), Limit); } Result->SetArrayField(TEXT("classes"), ClassList); - return JsonToString(Result); } // ============================================================ // HandleListFunctions — list Blueprint-callable functions on a class // ============================================================ -FString FBlueprintMCPServer::HandleListFunctions(const FString& Body) +void FBlueprintMCPServer::HandleListFunctions(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString ClassName = Json->GetStringField(TEXT("className")); FString Filter = Json->GetStringField(TEXT("filter")); if (ClassName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: className")); + return MakeErrorJson(Result, TEXT("Missing required field: className")); } // Find the class @@ -381,7 +348,7 @@ FString FBlueprintMCPServer::HandleListFunctions(const FString& Body) } if (!FoundClass) { - return MakeErrorJson(FString::Printf(TEXT("Class '%s' not found"), *ClassName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Class '%s' not found"), *ClassName)); } TArray> FuncList; @@ -453,32 +420,24 @@ FString FBlueprintMCPServer::HandleListFunctions(const FString& Body) FuncList.Add(MakeShared(FuncObj)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetNumberField(TEXT("count"), FuncList.Num()); Result->SetArrayField(TEXT("functions"), FuncList); - return JsonToString(Result); } // ============================================================ // HandleListProperties — list properties on a class // ============================================================ -FString FBlueprintMCPServer::HandleListProperties(const FString& Body) +void FBlueprintMCPServer::HandleListProperties(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString ClassName = Json->GetStringField(TEXT("className")); FString Filter = Json->GetStringField(TEXT("filter")); if (ClassName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: className")); + return MakeErrorJson(Result, TEXT("Missing required field: className")); } // Find the class @@ -493,7 +452,7 @@ FString FBlueprintMCPServer::HandleListProperties(const FString& Body) } if (!FoundClass) { - return MakeErrorJson(FString::Printf(TEXT("Class '%s' not found"), *ClassName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Class '%s' not found"), *ClassName)); } TArray> PropList; @@ -540,10 +499,8 @@ FString FBlueprintMCPServer::HandleListProperties(const FString& Body) PropList.Add(MakeShared(PropObj)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("className"), FoundClass->GetName()); Result->SetNumberField(TEXT("count"), PropList.Num()); Result->SetArrayField(TEXT("properties"), PropList); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Dispatchers.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Dispatchers.cpp index fc86cd90..a1227f11 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Dispatchers.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Dispatchers.cpp @@ -14,20 +14,14 @@ // HandleAddEventDispatcher — create a multicast delegate on a Blueprint // ============================================================ -FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) +void FBlueprintMCPServer::HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString DispatcherName = Json->GetStringField(TEXT("dispatcherName")); if (BlueprintName.IsEmpty() || DispatcherName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, dispatcherName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, dispatcherName")); } // Load Blueprint @@ -35,7 +29,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } FName DispatcherFName(*DispatcherName); @@ -45,7 +39,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) { if (Var.VarName == DispatcherFName) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A variable or dispatcher named '%s' already exists in Blueprint '%s'"), *DispatcherName, *BlueprintName)); } @@ -58,7 +52,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) { if (Existing && Existing->GetName().Equals(DispatcherName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A graph named '%s' already exists in Blueprint '%s'"), *DispatcherName, *BlueprintName)); } @@ -73,7 +67,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) bool bVarAdded = FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType); if (!bVarAdded) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Failed to add delegate variable for '%s'"), *DispatcherName)); } @@ -84,7 +78,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!SigGraph) { - return MakeErrorJson(TEXT("Failed to create delegate signature graph")); + return MakeErrorJson(Result, TEXT("Failed to create delegate signature graph")); } K2Schema->CreateDefaultNodesForGraph(*SigGraph); @@ -121,7 +115,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) // Still save what we have FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); SaveBlueprintPackage(BP); - return MakeErrorJson(TEXT("Event dispatcher created but entry node not found — parameters could not be added")); + return MakeErrorJson(Result, TEXT("Event dispatcher created but entry node not found — parameters could not be added")); } for (const TSharedPtr& ParamVal : ParamsArr) @@ -138,7 +132,7 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) FString TypeError; if (!ResolveTypeFromString(ParamType, PinType, TypeError)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Parameter '%s': %s"), *ParamName, *TypeError)); } @@ -157,38 +151,30 @@ FString FBlueprintMCPServer::HandleAddEventDispatcher(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added event dispatcher '%s' to '%s' with %d params (saved: %s)"), *DispatcherName, *BlueprintName, AddedParamsJson.Num(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("dispatcherName"), DispatcherName); Result->SetArrayField(TEXT("parameters"), AddedParamsJson); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleListEventDispatchers — list all event dispatchers on a Blueprint // ============================================================ -FString FBlueprintMCPServer::HandleListEventDispatchers(const FString& Body) +void FBlueprintMCPServer::HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } TSet DelegateNameSet; @@ -237,9 +223,7 @@ FString FBlueprintMCPServer::HandleListEventDispatchers(const FString& Body) DispatchersArr.Add(MakeShared(DispObj)); } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("count"), DispatchersArr.Num()); Result->SetArrayField(TEXT("dispatchers"), DispatchersArr); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Graphs.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Graphs.cpp index 22b2378b..dbeac1c6 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Graphs.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Graphs.cpp @@ -19,20 +19,14 @@ // HandleReparentBlueprint — change a Blueprint's parent class // ============================================================ -FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body) +void FBlueprintMCPServer::HandleReparentBlueprint(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NewParentName = Json->GetStringField(TEXT("newParentClass")); if (BlueprintName.IsEmpty() || NewParentName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, newParentClass")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, newParentClass")); } // Load Blueprint @@ -40,7 +34,7 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None"); @@ -72,7 +66,7 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body) if (!NewParentClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Could not find class '%s'. Provide a C++ class name (e.g. 'WebUIHUD') or Blueprint name."), *NewParentName)); } @@ -107,27 +101,19 @@ FString FBlueprintMCPServer::HandleReparentBlueprint(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("oldParentClass"), OldParentName); Result->SetStringField(TEXT("newParentClass"), NewParentActualName); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleCreateBlueprint — create a new Blueprint asset // ============================================================ -FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) +void FBlueprintMCPServer::HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprintName")); FString PackagePath = Json->GetStringField(TEXT("packagePath")); FString ParentClassName = Json->GetStringField(TEXT("parentClass")); @@ -135,20 +121,20 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) if (BlueprintName.IsEmpty() || PackagePath.IsEmpty() || ParentClassName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprintName, packagePath, parentClass")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprintName, packagePath, parentClass")); } // Validate packagePath starts with /Game if (!PackagePath.StartsWith(TEXT("/Game"))) { - return MakeErrorJson(TEXT("packagePath must start with '/Game'")); + return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); } // Check if asset already exists FString FullAssetPath = PackagePath / BlueprintName; if (FindBlueprintAsset(BlueprintName) || FindBlueprintAsset(FullAssetPath)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Blueprint '%s' already exists. Use a different name or delete the existing asset first."), *BlueprintName)); } @@ -177,7 +163,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) if (!ParentClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Could not find parent class '%s'. Provide a C++ class name (e.g. 'Actor', 'Pawn') or Blueprint name."), *ParentClassName)); } @@ -200,7 +186,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) } else if (BlueprintTypeStr != TEXT("Normal")) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid blueprintType '%s'. Valid values: Normal, Interface, FunctionLibrary, MacroLibrary"), *BlueprintTypeStr)); } @@ -221,7 +207,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) UPackage* Package = CreatePackage(*FullPackagePath); if (!Package) { - return MakeErrorJson(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); } // Create the Blueprint @@ -236,7 +222,7 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) if (!NewBP) { - return MakeErrorJson(TEXT("FKismetEditorUtilities::CreateBlueprint returned null")); + return MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null")); } // Compile @@ -268,7 +254,6 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"), *BlueprintName, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprintName"), BlueprintName); Result->SetStringField(TEXT("packagePath"), PackagePath); @@ -277,33 +262,26 @@ FString FBlueprintMCPServer::HandleCreateBlueprint(const FString& Body) Result->SetStringField(TEXT("blueprintType"), BlueprintTypeStr.IsEmpty() ? TEXT("Normal") : BlueprintTypeStr); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetArrayField(TEXT("graphs"), GraphNames); - return JsonToString(Result); } // ============================================================ // HandleCreateGraph — create a new function, macro, or custom event graph // ============================================================ -FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) +void FBlueprintMCPServer::HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString GraphName = Json->GetStringField(TEXT("graphName")); FString GraphType = Json->GetStringField(TEXT("graphType")); if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || GraphType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName, graphType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName, graphType")); } if (GraphType != TEXT("function") && GraphType != TEXT("macro") && GraphType != TEXT("customEvent")) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid graphType '%s'. Valid values: function, macro, customEvent"), *GraphType)); } @@ -312,7 +290,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Check graph name uniqueness @@ -322,7 +300,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) { if (Existing && Existing->GetName().Equals(GraphName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A graph named '%s' already exists in Blueprint '%s'"), *GraphName, *BlueprintName)); } } @@ -339,7 +317,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) { if (CE->CustomFunctionName == FName(*GraphName)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A custom event named '%s' already exists in Blueprint '%s'"), *GraphName, *BlueprintName)); } } @@ -358,7 +336,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!NewGraph) { - return MakeErrorJson(TEXT("Failed to create function graph")); + return MakeErrorJson(Result, TEXT("Failed to create function graph")); } FBlueprintEditorUtils::AddFunctionGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromObject=*/static_cast(nullptr)); } @@ -368,7 +346,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!NewGraph) { - return MakeErrorJson(TEXT("Failed to create macro graph")); + return MakeErrorJson(Result, TEXT("Failed to create macro graph")); } FBlueprintEditorUtils::AddMacroGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromClass=*/nullptr); } @@ -382,7 +360,7 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) } if (!EventGraph) { - return MakeErrorJson(TEXT("Blueprint has no EventGraph to add a custom event to")); + return MakeErrorJson(Result, TEXT("Blueprint has no EventGraph to add a custom event to")); } // Create a custom event node in the EventGraph @@ -402,7 +380,6 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"), *GraphType, *GraphName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("graphName"), GraphName); @@ -412,27 +389,28 @@ FString FBlueprintMCPServer::HandleCreateGraph(const FString& Body) { Result->SetStringField(TEXT("nodeId"), CreatedNodeId); } - return JsonToString(Result); } // ============================================================ // HandleDeleteGraph — delete a function or macro graph // ============================================================ -FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body) +void FBlueprintMCPServer::HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) return MakeErrorJson(TEXT("Invalid JSON body")); - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString GraphName = Json->GetStringField(TEXT("graphName")); if (BlueprintName.IsEmpty() || GraphName.IsEmpty()) - return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName")); + { + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName")); + } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); - if (!BP) return MakeErrorJson(LoadError); + if (!BP) + { + return MakeErrorJson(Result, LoadError); + } // Find the graph UEdGraph* TargetGraph = nullptr; @@ -467,12 +445,12 @@ FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body) { if (Graph && Graph->GetName().Equals(GraphName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."), *GraphName)); } } - return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting %s graph '%s' from Blueprint '%s'"), @@ -490,42 +468,42 @@ FString FBlueprintMCPServer::HandleDeleteGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"), *GraphName, NodeCount, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("graphName"), GraphName); Result->SetStringField(TEXT("graphType"), GraphType); Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRenameGraph — rename a function or macro graph // ============================================================ -FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body) +void FBlueprintMCPServer::HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) return MakeErrorJson(TEXT("Invalid JSON body")); - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString GraphName = Json->GetStringField(TEXT("graphName")); FString NewName = Json->GetStringField(TEXT("newName")); if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || NewName.IsEmpty()) - return MakeErrorJson(TEXT("Missing required fields: blueprint, graphName, newName")); + { + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graphName, newName")); + } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); - if (!BP) return MakeErrorJson(LoadError); + if (!BP) + { + return MakeErrorJson(Result, LoadError); + } // Check if it's an UbergraphPage — disallow rename for (UEdGraph* Graph : BP->UbergraphPages) { if (Graph && Graph->GetName().Equals(GraphName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."), *GraphName)); } @@ -558,7 +536,9 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body) } if (!TargetGraph) - return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName)); + { + return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *GraphName, *BlueprintName)); + } // Check for name collision TArray AllGraphs; @@ -567,7 +547,7 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body) { if (Existing && Existing != TargetGraph && Existing->GetName().Equals(NewName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("A graph named '%s' already exists in Blueprint '%s'"), *NewName, *BlueprintName)); } } @@ -583,12 +563,10 @@ FString FBlueprintMCPServer::HandleRenameGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"), *GraphName, *NewName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("oldName"), GraphName); Result->SetStringField(TEXT("newName"), TargetGraph->GetName()); Result->SetStringField(TEXT("graphType"), GraphType); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Interfaces.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Interfaces.cpp index f5ce559d..302c99d7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Interfaces.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Interfaces.cpp @@ -12,25 +12,19 @@ // HandleListInterfaces — list implemented interfaces on a Blueprint // ============================================================ -FString FBlueprintMCPServer::HandleListInterfaces(const FString& Body) +void FBlueprintMCPServer::HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } TArray> InterfacesArr; @@ -59,38 +53,30 @@ FString FBlueprintMCPServer::HandleListInterfaces(const FString& Body) InterfacesArr.Add(MakeShared(IfaceObj)); } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("count"), InterfacesArr.Num()); Result->SetArrayField(TEXT("interfaces"), InterfacesArr); - return JsonToString(Result); } // ============================================================ // HandleAddInterface — add a Blueprint Interface implementation // ============================================================ -FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) +void FBlueprintMCPServer::HandleAddInterface(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString InterfaceName = Json->GetStringField(TEXT("interfaceName")); if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, interfaceName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Resolve the interface class @@ -137,7 +123,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) if (!InterfaceClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Interface '%s' not found. Provide a Blueprint Interface asset name (e.g. 'BPI_MyInterface') or a native UInterface class name."), *InterfaceName)); } @@ -147,7 +133,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) { if (IfaceDesc.Interface == InterfaceClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Interface '%s' is already implemented by Blueprint '%s'"), *InterfaceName, *BlueprintName)); } @@ -162,7 +148,7 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath); if (!bAdded) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"), *InterfaceName, *BlueprintName)); } @@ -190,7 +176,6 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs, saved: %s)"), *InterfaceClass->GetName(), *BlueprintName, AddedFunctions.Num(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName()); @@ -203,27 +188,20 @@ FString FBlueprintMCPServer::HandleAddInterface(const FString& Body) } Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRemoveInterface — remove a Blueprint Interface implementation // ============================================================ -FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body) +void FBlueprintMCPServer::HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString InterfaceName = Json->GetStringField(TEXT("interfaceName")); if (BlueprintName.IsEmpty() || InterfaceName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, interfaceName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, interfaceName")); } bool bPreserveFunctions = false; @@ -236,7 +214,7 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the interface in ImplementedInterfaces by name (case-insensitive) @@ -279,12 +257,11 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body) } } - TSharedRef ErrorResult = MakeShared(); - ErrorResult->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Interface '%s' is not implemented by Blueprint '%s'"), *InterfaceName, *BlueprintName)); - ErrorResult->SetArrayField(TEXT("implementedInterfaces"), IfaceList); - return JsonToString(ErrorResult); + Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList); + return; } FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); @@ -300,11 +277,9 @@ FString FBlueprintMCPServer::HandleRemoveInterface(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s' (saved: %s)"), *FoundInterface->GetName(), *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName()); Result->SetBoolField(TEXT("preservedFunctions"), bPreserveFunctions); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialInstance.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialInstance.cpp index 9cbb6caa..effe98e3 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialInstance.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialInstance.cpp @@ -20,34 +20,28 @@ // HandleCreateMaterialInstance — create a new Material Instance Constant // ============================================================ -FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body) +void FBlueprintMCPServer::HandleCreateMaterialInstance(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString Name = Json->GetStringField(TEXT("name")); FString PackagePath = Json->GetStringField(TEXT("packagePath")); FString ParentMaterialName = Json->GetStringField(TEXT("parentMaterial")); if (Name.IsEmpty() || PackagePath.IsEmpty() || ParentMaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: name, packagePath, parentMaterial")); + return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath, parentMaterial")); } // Validate packagePath starts with /Game if (!PackagePath.StartsWith(TEXT("/Game"))) { - return MakeErrorJson(TEXT("packagePath must start with '/Game'")); + return MakeErrorJson(Result, TEXT("packagePath must start with '/Game'")); } // Check if asset already exists FString FullAssetPath = PackagePath / Name; if (FindMaterialInstanceAsset(Name) || FindMaterialInstanceAsset(FullAssetPath)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Material Instance '%s' already exists. Use a different name or delete the existing asset first."), *Name)); } @@ -80,7 +74,7 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body) if (!ParentMaterial) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Parent material '%s' not found. Provide a Material or Material Instance name/path."), *ParentMaterialName)); } @@ -95,13 +89,13 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body) UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialInstanceConstant::StaticClass(), Factory); if (!NewAsset) { - return MakeErrorJson(FString::Printf(TEXT("Failed to create Material Instance asset '%s' in '%s'"), *Name, *PackagePath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create Material Instance asset '%s' in '%s'"), *Name, *PackagePath)); } UMaterialInstanceConstant* MI = Cast(NewAsset); if (!MI) { - return MakeErrorJson(TEXT("Created asset is not a UMaterialInstanceConstant")); + return MakeErrorJson(Result, TEXT("Created asset is not a UMaterialInstanceConstant")); } // Set parent @@ -120,38 +114,30 @@ FString FBlueprintMCPServer::HandleCreateMaterialInstance(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Instance '%s' with parent '%s' (saved: %s)"), *Name, *ParentMaterial->GetName(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), MI->GetPathName()); Result->SetStringField(TEXT("parent"), ParentMaterial->GetPathName()); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleSetMaterialInstanceParameter — set a parameter override on an MI // ============================================================ -FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& Body) +void FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MIName = Json->GetStringField(TEXT("materialInstance")); FString ParamName = Json->GetStringField(TEXT("parameterName")); if (MIName.IsEmpty() || ParamName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: materialInstance, parameterName")); + return MakeErrorJson(Result, TEXT("Missing required fields: materialInstance, parameterName")); } if (!Json->HasField(TEXT("value"))) { - return MakeErrorJson(TEXT("Missing required field: value")); + return MakeErrorJson(Result, TEXT("Missing required field: value")); } bool bDryRun = false; @@ -165,7 +151,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(MIName, LoadError); if (!MI) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Determine the parameter type — explicit or auto-detect from parent @@ -237,7 +223,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B if (TypeStr.IsEmpty()) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Could not determine parameter type for '%s'. Specify the 'type' field explicitly (scalar, vector, texture, staticSwitch)."), *ParamName)); } @@ -266,7 +252,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B const TSharedPtr* ValueObj = nullptr; if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid()) { - return MakeErrorJson(TEXT("For vector parameters, 'value' must be an object with r, g, b (and optional a) fields.")); + return MakeErrorJson(Result, TEXT("For vector parameters, 'value' must be an object with r, g, b (and optional a) fields.")); } double R = (*ValueObj)->GetNumberField(TEXT("r")); @@ -288,13 +274,13 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B FString TexturePath = Json->GetStringField(TEXT("value")); if (TexturePath.IsEmpty()) { - return MakeErrorJson(TEXT("For texture parameters, 'value' must be a texture asset path string.")); + return MakeErrorJson(Result, TEXT("For texture parameters, 'value' must be a texture asset path string.")); } UTexture* TextureObj = LoadObject(nullptr, *TexturePath); if (!TextureObj) { - return MakeErrorJson(FString::Printf(TEXT("Could not load texture at path '%s'"), *TexturePath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Could not load texture at path '%s'"), *TexturePath)); } if (!bDryRun) @@ -342,7 +328,7 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Unknown parameter type '%s'. Valid types: scalar, vector, texture, staticSwitch"), *TypeStr)); } @@ -359,7 +345,6 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B bDryRun ? TEXT("[DRY RUN] Would set") : TEXT("Set"), *ParamName, *NewValueDescription, *MIName); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("materialInstance"), MIName); Result->SetStringField(TEXT("parameterName"), ParamName); @@ -369,29 +354,27 @@ FString FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FString& B { Result->SetBoolField(TEXT("dryRun"), true); } - return JsonToString(Result); } // ============================================================ // HandleGetMaterialInstanceParameters — list all parameters on an MI // ============================================================ -FString FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const TMap& Params) +void FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const FJsonObject* Json, FJsonObject* Result) { - const FString* NameParam = Params.Find(TEXT("name")); - if (!NameParam || NameParam->IsEmpty()) + FString NameParam = Json->GetStringField(TEXT("name")); + if (NameParam.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required query parameter: name")); + return MakeErrorJson(Result, TEXT("Missing required query parameter: name")); } FString LoadError; - UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(*NameParam, LoadError); + UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(NameParam, LoadError); if (!MI) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("name"), MI->GetName()); Result->SetStringField(TEXT("path"), MI->GetPathName()); @@ -610,28 +593,20 @@ FString FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const TMapSetArrayField(TEXT("vectorParameters"), VectorArr); Result->SetArrayField(TEXT("textureParameters"), TextureArr); Result->SetArrayField(TEXT("staticSwitchParameters"), StaticSwitchArr); - - return JsonToString(Result); } // ============================================================ // HandleReparentMaterialInstance — change parent of an MI // ============================================================ -FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) +void FBlueprintMCPServer::HandleReparentMaterialInstance(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MIName = Json->GetStringField(TEXT("materialInstance")); FString NewParentName = Json->GetStringField(TEXT("newParent")); if (MIName.IsEmpty() || NewParentName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: materialInstance, newParent")); + return MakeErrorJson(Result, TEXT("Missing required fields: materialInstance, newParent")); } bool bDryRun = false; @@ -645,7 +620,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) UMaterialInstanceConstant* MI = LoadMaterialInstanceByName(MIName, LoadError); if (!MI) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Capture old parent @@ -679,7 +654,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) if (!NewParent) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("New parent material '%s' not found. Provide a Material or Material Instance name/path."), *NewParentName)); } @@ -691,7 +666,7 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) { if (Check == MI) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot reparent '%s' to '%s' — this would create a circular parent chain."), *MIName, *NewParentName)); } @@ -723,7 +698,6 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) *MIName, bSaved ? TEXT("true") : TEXT("false")); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("materialInstance"), MIName); Result->SetStringField(TEXT("oldParent"), OldParentPath); @@ -732,5 +706,4 @@ FString FBlueprintMCPServer::HandleReparentMaterialInstance(const FString& Body) { Result->SetBoolField(TEXT("dryRun"), true); } - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialMutation.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialMutation.cpp index 631eb1af..0532f976 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialMutation.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialMutation.cpp @@ -56,32 +56,26 @@ extern int32 TryAddMaterialExpressionSEH( // HandleCreateMaterial — create a new UMaterial asset // ============================================================ -FString FBlueprintMCPServer::HandleCreateMaterial(const FString& Body) +void FBlueprintMCPServer::HandleCreateMaterial(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString Name = Json->GetStringField(TEXT("name")); FString PackagePath = Json->GetStringField(TEXT("packagePath")); if (Name.IsEmpty() || PackagePath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: name, packagePath")); + return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath")); } 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 (FindMaterialAsset(Name) || FindMaterialAsset(FullAssetPath)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Material '%s' already exists. Use a different name or delete the existing asset first."), *Name)); } @@ -95,13 +89,13 @@ FString FBlueprintMCPServer::HandleCreateMaterial(const FString& Body) if (!NewAsset) { - return MakeErrorJson(FString::Printf(TEXT("Failed to create Material '%s' in '%s'"), *Name, *PackagePath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create Material '%s' in '%s'"), *Name, *PackagePath)); } UMaterial* Material = Cast(NewAsset); if (!Material) { - return MakeErrorJson(TEXT("Created asset is not a UMaterial")); + return MakeErrorJson(Result, TEXT("Created asset is not a UMaterial")); } // Apply optional properties @@ -194,7 +188,6 @@ FString FBlueprintMCPServer::HandleCreateMaterial(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material '%s' (saved: %s)"), *Name, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), Material->GetPathName()); @@ -202,32 +195,25 @@ FString FBlueprintMCPServer::HandleCreateMaterial(const FString& Body) Result->SetStringField(TEXT("blendMode"), BlendModeToString(Material->BlendMode)); Result->SetBoolField(TEXT("twoSided"), Material->TwoSided != 0); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleSetMaterialProperty — set a top-level material property // ============================================================ -FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) +void FBlueprintMCPServer::HandleSetMaterialProperty(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString Property = Json->GetStringField(TEXT("property")); if (MaterialName.IsEmpty() || Property.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: material, property")); + return MakeErrorJson(Result, TEXT("Missing required fields: material, property")); } if (!Json->HasField(TEXT("value"))) { - return MakeErrorJson(TEXT("Missing required field: value")); + return MakeErrorJson(Result, TEXT("Missing required field: value")); } bool bDryRun = false; @@ -238,7 +224,7 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } FString OldValue; @@ -304,7 +290,7 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) else if (ValueStr == TEXT("UI")) NewDomain = MD_UI; else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid domain '%s'. Valid values: Surface, DeferredDecal, LightFunction, Volume, PostProcess, UI"), *ValueStr)); } @@ -331,7 +317,7 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) else if (ValueStr == TEXT("Modulate")) NewBlend = BLEND_Modulate; else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid blendMode '%s'. Valid values: Opaque, Masked, Translucent, Additive, Modulate"), *ValueStr)); } @@ -376,7 +362,7 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) else if (ValueStr == TEXT("Eye")) NewModel = MSM_Eye; else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid shadingModel '%s'. Valid values: Unlit, DefaultLit, Subsurface, PreintegratedSkin, ClearCoat, SubsurfaceProfile, TwoSidedFoliage, Hair, Cloth, Eye"), *ValueStr)); } @@ -470,7 +456,7 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Unknown property '%s'. Valid properties: domain, blendMode, twoSided, shadingModel, opacity, " "opacityMaskClipValue, bUsedWithSkeletalMesh, bUsedWithMorphTargets, bUsedWithNiagaraSprites, " "ditheredLODTransition, bAllowNegativeEmissiveColor"), @@ -488,7 +474,6 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) bDryRun ? TEXT("[DRY RUN] ") : TEXT(""), *Property, *MaterialName, *OldValue, *NewValue); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetStringField(TEXT("property"), Property); @@ -499,31 +484,24 @@ FString FBlueprintMCPServer::HandleSetMaterialProperty(const FString& Body) { Result->SetBoolField(TEXT("saved"), bSaved); } - return JsonToString(Result); } // ============================================================ // HandleAddMaterialExpression — add a new expression to a material // ============================================================ -FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) +void FBlueprintMCPServer::HandleAddMaterialExpression(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString ExpressionClassName = Json->GetStringField(TEXT("expressionClass")); if (MaterialName.IsEmpty() && !Json->HasField(TEXT("materialFunction"))) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (ExpressionClassName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: expressionClass")); + return MakeErrorJson(Result, TEXT("Missing required field: expressionClass")); } int32 PosX = 0, PosY = 0; @@ -562,14 +540,14 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) if (!ExprClass) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Unknown expression class '%s'. Use the UMaterialExpression subclass name without the 'MaterialExpression' prefix " "(e.g. 'Constant', 'ScalarParameter', 'Add', 'Multiply', 'Lerp', 'Subtract', 'Fresnel', 'Comment', etc.)"), *ExpressionClassName)); } if (ExprClass->HasAnyClassFlags(CLASS_Abstract)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Expression class '%s' is abstract and cannot be instantiated."), *ExpressionClassName)); } @@ -584,11 +562,11 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) { if (!MaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Specify either 'material' or 'materialFunction', not both")); + return MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both")); } FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } Owner = MatFunc; AssetDisplayName = MatFunc->GetName(); } @@ -596,7 +574,7 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } Owner = Material; AssetDisplayName = Material->GetName(); } @@ -606,14 +584,13 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would add expression '%s' to '%s' at (%d, %d)"), *ExpressionClassName, *AssetDisplayName, PosX, PosY); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("expressionClass"), ExpressionClassName); Result->SetNumberField(TEXT("posX"), PosX); Result->SetNumberField(TEXT("posY"), PosY); - return JsonToString(Result); + return; } // Ensure the MaterialGraph exists (commandlet mode doesn't auto-create it) @@ -627,7 +604,7 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) int32 CreateResult = TryAddMaterialExpressionSEH(Owner, ExprClass, Material, MatFunc, PosX, PosY, &NewExpr); if (CreateResult != 0 || !NewExpr) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Expression class '%s' cannot be instantiated (may be abstract or have internal errors)."), *ExpressionClassName)); } @@ -635,7 +612,7 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) NewExpr = NewObject(Owner, ExprClass); if (!NewExpr) { - return MakeErrorJson(TEXT("Failed to create material expression object")); + return MakeErrorJson(Result, TEXT("Failed to create material expression object")); } NewExpr->MaterialExpressionEditorX = PosX; NewExpr->MaterialExpressionEditorY = PosY; @@ -683,7 +660,6 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) // Serialize the expression details TSharedPtr ExprDetails = SerializeMaterialExpression(NewExpr); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("expressionClass"), ExpressionClassName); @@ -695,32 +671,25 @@ FString FBlueprintMCPServer::HandleAddMaterialExpression(const FString& Body) Result->SetObjectField(TEXT("expression"), ExprDetails); } Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleDeleteMaterialExpression — remove an expression from a material // ============================================================ -FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) +void FBlueprintMCPServer::HandleDeleteMaterialExpression(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString MaterialFunctionName = Json->GetStringField(TEXT("materialFunction")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (MaterialName.IsEmpty() && MaterialFunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: nodeId")); + return MakeErrorJson(Result, TEXT("Missing required field: nodeId")); } bool bDryRun = false; @@ -735,14 +704,14 @@ FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) { FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = MatFunc->GetName(); } else { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = Material->GetName(); } @@ -751,7 +720,7 @@ FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) UEdGraph* Graph = Material ? (UEdGraph*)Material->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr); if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); + return MakeErrorJson(Result, FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); } // Find the node by GUID @@ -768,12 +737,12 @@ FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) if (!TargetMatNode) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); } if (!TargetMatNode->MaterialExpression) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' has no associated material expression"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' has no associated material expression"), *NodeId)); } // Capture info before deletion @@ -785,14 +754,13 @@ FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would delete expression '%s' (nodeId: %s) from '%s'"), *DeletedExprClass, *NodeId, *AssetDisplayName); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("deletedNode"), NodeId); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); - return JsonToString(Result); + return; } // Remove the expression @@ -821,28 +789,20 @@ FString FBlueprintMCPServer::HandleDeleteMaterialExpression(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted expression '%s' (nodeId: %s) from '%s' (saved: %s)"), *DeletedExprClass, *NodeId, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("deletedNode"), NodeId); Result->SetStringField(TEXT("deletedNodeTitle"), DeletedNodeTitle); Result->SetStringField(TEXT("deletedExpressionClass"), DeletedExprClass); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleConnectMaterialPins — connect two pins in a material graph // ============================================================ -FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) +void FBlueprintMCPServer::HandleConnectMaterialPins(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString MaterialFunctionName = Json->GetStringField(TEXT("materialFunction")); FString SourceNodeId = Json->GetStringField(TEXT("sourceNodeId")); @@ -852,11 +812,11 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) if (MaterialName.IsEmpty() && MaterialFunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (SourceNodeId.IsEmpty() || SourcePinName.IsEmpty() || TargetNodeId.IsEmpty() || TargetPinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: sourceNodeId, sourcePinName, targetNodeId, targetPinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: sourceNodeId, sourcePinName, targetNodeId, targetPinName")); } bool bDryRun = false; @@ -871,14 +831,14 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) { FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = MatFunc->GetName(); } else { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = Material->GetName(); } @@ -886,7 +846,7 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) UEdGraph* Graph = Material ? (UEdGraph*)Material->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr); if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); + return MakeErrorJson(Result, FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); } // Find source and target nodes by GUID @@ -906,11 +866,11 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) if (!SourceNode) { - return MakeErrorJson(FString::Printf(TEXT("Source node '%s' not found in material graph"), *SourceNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Source node '%s' not found in material graph"), *SourceNodeId)); } if (!TargetNode) { - return MakeErrorJson(FString::Printf(TEXT("Target node '%s' not found in material graph"), *TargetNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found in material graph"), *TargetNodeId)); } // Find pins @@ -925,11 +885,10 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")))); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), + MakeErrorJson(Result, FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId)); - E->SetArrayField(TEXT("availablePins"), PinNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availablePins"), PinNames); + return; } UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName)); @@ -942,11 +901,10 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")))); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), + MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId)); - E->SetArrayField(TEXT("availablePins"), PinNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availablePins"), PinNames); + return; } if (bDryRun) @@ -954,12 +912,11 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would connect %s.%s -> %s.%s in '%s'"), *SourceNodeId, *SourcePinName, *TargetNodeId, *TargetPinName, *AssetDisplayName); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetBoolField(TEXT("connected"), false); Result->SetStringField(TEXT("material"), AssetDisplayName); - return JsonToString(Result); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Connecting %s.%s -> %s.%s in '%s'"), @@ -969,22 +926,20 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) const UEdGraphSchema* Schema = Graph->GetSchema(); if (!Schema) { - return MakeErrorJson(TEXT("Material graph schema not found")); + return MakeErrorJson(Result, TEXT("Material graph schema not found")); } bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), bConnected); Result->SetBoolField(TEXT("connected"), bConnected); Result->SetStringField(TEXT("material"), AssetDisplayName); if (!bConnected) { - Result->SetStringField(TEXT("error"), FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot connect %s.%s to %s.%s — types may be incompatible"), *SourceNodeId, *SourcePinName, *TargetNodeId, *TargetPinName)); - return JsonToString(Result); } // Save @@ -993,22 +948,14 @@ FString FBlueprintMCPServer::HandleConnectMaterialPins(const FString& Body) Asset->PostEditChange(); bool bSaved = Material ? SaveMaterialPackage(Material) : SaveGenericPackage(MatFunc); Result->SetBoolField(TEXT("saved"), bSaved); - - return JsonToString(Result); } // ============================================================ // HandleDisconnectMaterialPin — break connections on a pin in a material graph // ============================================================ -FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) +void FBlueprintMCPServer::HandleDisconnectMaterialPin(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString MaterialFunctionName = Json->GetStringField(TEXT("materialFunction")); FString NodeId = Json->GetStringField(TEXT("nodeId")); @@ -1016,11 +963,11 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) if (MaterialName.IsEmpty() && MaterialFunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (NodeId.IsEmpty() || PinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: nodeId, pinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: nodeId, pinName")); } bool bDryRun = false; @@ -1035,14 +982,14 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) { FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = MatFunc->GetName(); } else { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = Material->GetName(); } @@ -1050,7 +997,7 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) UEdGraph* Graph = Material ? (UEdGraph*)Material->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr); if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); + return MakeErrorJson(Result, FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); } // Find node by GUID @@ -1067,7 +1014,7 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) if (!TargetNode) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); } // Find pin @@ -1081,11 +1028,10 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")))); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Pin '%s' not found on node '%s'"), + MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); - E->SetArrayField(TEXT("availablePins"), PinNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availablePins"), PinNames); + return; } int32 BrokenCount = Pin->LinkedTo.Num(); @@ -1095,14 +1041,13 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would disconnect pin '%s' on node '%s' in '%s' (%d links)"), *PinName, *NodeId, *AssetDisplayName, BrokenCount); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetStringField(TEXT("pinName"), PinName); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); - return JsonToString(Result); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Disconnecting pin '%s' on node '%s' in '%s' (%d links)"), @@ -1118,44 +1063,36 @@ FString FBlueprintMCPServer::HandleDisconnectMaterialPin(const FString& Body) // Save bool bSaved = Material ? SaveMaterialPackage(Material) : SaveGenericPackage(MatFunc); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetStringField(TEXT("pinName"), PinName); Result->SetNumberField(TEXT("brokenLinkCount"), BrokenCount); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleSetExpressionValue — set value on a material expression // ============================================================ -FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) +void FBlueprintMCPServer::HandleSetExpressionValue(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString MaterialFunctionName = Json->GetStringField(TEXT("materialFunction")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (MaterialName.IsEmpty() && MaterialFunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: nodeId")); + return MakeErrorJson(Result, TEXT("Missing required field: nodeId")); } if (!Json->HasField(TEXT("value"))) { - return MakeErrorJson(TEXT("Missing required field: value")); + return MakeErrorJson(Result, TEXT("Missing required field: value")); } // Load material or material function @@ -1167,14 +1104,14 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) { FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = MatFunc->GetName(); } else { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = Material->GetName(); } @@ -1182,7 +1119,7 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) UEdGraph* Graph = Material ? (UEdGraph*)Material->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr); if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); + return MakeErrorJson(Result, FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); } // Find the node by GUID @@ -1199,13 +1136,13 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) if (!TargetMatNode) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); } UMaterialExpression* Expr = TargetMatNode->MaterialExpression; if (!Expr) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' has no associated material expression"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' has no associated material expression"), *NodeId)); } FString ExprType; @@ -1238,7 +1175,7 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) else { Asset->PostEditChange(); - return MakeErrorJson(TEXT("Constant3Vector requires value as object {r, g, b}")); + return MakeErrorJson(Result, TEXT("Constant3Vector requires value as object {r, g, b}")); } } else if (UMaterialExpressionConstant4Vector* C4Expr = Cast(Expr)) @@ -1258,7 +1195,7 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) else { Asset->PostEditChange(); - return MakeErrorJson(TEXT("Constant4Vector requires value as object {r, g, b, a}")); + return MakeErrorJson(Result, TEXT("Constant4Vector requires value as object {r, g, b, a}")); } } else if (UMaterialExpressionScalarParameter* SPExpr = Cast(Expr)) @@ -1291,7 +1228,7 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) else { Asset->PostEditChange(); - return MakeErrorJson(TEXT("VectorParameter requires value as object {r, g, b, a}")); + return MakeErrorJson(Result, TEXT("VectorParameter requires value as object {r, g, b, a}")); } FString ParamName; @@ -1318,7 +1255,7 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) else { Asset->PostEditChange(); - return MakeErrorJson(TEXT("TextureCoordinate requires value as object {coordinateIndex, uTiling, vTiling}")); + return MakeErrorJson(Result, TEXT("TextureCoordinate requires value as object {coordinateIndex, uTiling, vTiling}")); } } else if (UMaterialExpressionCustom* CustomExpr = Cast(Expr)) @@ -1375,13 +1312,13 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) else { Asset->PostEditChange(); - return MakeErrorJson(TEXT("ComponentMask requires value as object {r, g, b, a} (booleans)")); + return MakeErrorJson(Result, TEXT("ComponentMask requires value as object {r, g, b, a} (booleans)")); } } else { Asset->PostEditChange(); - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Expression type '%s' does not support direct value setting. Supported types: Constant, " "Constant3Vector, Constant4Vector, ScalarParameter, VectorParameter, TextureCoordinate, " "Custom, ComponentMask"), @@ -1397,44 +1334,36 @@ FString FBlueprintMCPServer::HandleSetExpressionValue(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set expression value on node '%s' (%s) in '%s': %s"), *NodeId, *ExprType, *AssetDisplayName, *NewValueStr); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetStringField(TEXT("expressionType"), ExprType); Result->SetStringField(TEXT("newValue"), NewValueStr); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleMoveMaterialExpression — reposition a material graph node // ============================================================ -FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) +void FBlueprintMCPServer::HandleMoveMaterialExpression(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString MaterialFunctionName = Json->GetStringField(TEXT("materialFunction")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (MaterialName.IsEmpty() && MaterialFunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: 'material' or 'materialFunction'")); + return MakeErrorJson(Result, TEXT("Missing required field: 'material' or 'materialFunction'")); } if (NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: nodeId")); + return MakeErrorJson(Result, TEXT("Missing required field: nodeId")); } if (!Json->HasField(TEXT("posX")) || !Json->HasField(TEXT("posY"))) { - return MakeErrorJson(TEXT("Missing required fields: posX, posY")); + return MakeErrorJson(Result, TEXT("Missing required fields: posX, posY")); } int32 PosX = (int32)Json->GetNumberField(TEXT("posX")); @@ -1452,14 +1381,14 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) { FString LoadError; MatFunc = LoadMaterialFunctionByName(MaterialFunctionName, LoadError); - if (!MatFunc) return MakeErrorJson(LoadError); + if (!MatFunc) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = MatFunc->GetName(); } else { FString LoadError; Material = LoadMaterialByName(MaterialName, LoadError); - if (!Material) return MakeErrorJson(LoadError); + if (!Material) { MakeErrorJson(Result, LoadError); return; } AssetDisplayName = Material->GetName(); } @@ -1467,7 +1396,7 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) UEdGraph* Graph = Material ? (UEdGraph*)Material->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr); if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); + return MakeErrorJson(Result, FString::Printf(TEXT("'%s' has no material graph"), *AssetDisplayName)); } // Find node by GUID @@ -1484,7 +1413,7 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) if (!TargetMatNode) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found in material graph"), *NodeId)); } if (bDryRun) @@ -1492,14 +1421,13 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: [DRY RUN] Would move node '%s' to (%d, %d) in '%s'"), *NodeId, PosX, PosY, *AssetDisplayName); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetNumberField(TEXT("posX"), PosX); Result->SetNumberField(TEXT("posY"), PosY); - return JsonToString(Result); + return; } // Set position on the graph node @@ -1523,14 +1451,12 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Moved node '%s' to (%d, %d) in '%s' (saved: %s)"), *NodeId, PosX, PosY, *AssetDisplayName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), AssetDisplayName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetNumberField(TEXT("posX"), PosX); Result->SetNumberField(TEXT("posY"), PosY); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ @@ -1541,32 +1467,26 @@ FString FBlueprintMCPServer::HandleMoveMaterialExpression(const FString& Body) // HandleCreateMaterialFunction — create a new UMaterialFunction asset // ============================================================ -FString FBlueprintMCPServer::HandleCreateMaterialFunction(const FString& Body) +void FBlueprintMCPServer::HandleCreateMaterialFunction(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString Name = Json->GetStringField(TEXT("name")); FString PackagePath = Json->GetStringField(TEXT("packagePath")); if (Name.IsEmpty() || PackagePath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: name, packagePath")); + return MakeErrorJson(Result, TEXT("Missing required fields: name, packagePath")); } 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 (FindMaterialFunctionAsset(Name) || FindMaterialFunctionAsset(FullAssetPath)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Material Function '%s' already exists. Use a different name or delete the existing asset first."), *Name)); } @@ -1583,13 +1503,13 @@ FString FBlueprintMCPServer::HandleCreateMaterialFunction(const FString& Body) if (!NewAsset) { - return MakeErrorJson(FString::Printf(TEXT("Failed to create Material Function '%s' in '%s'"), *Name, *PackagePath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to create Material Function '%s' in '%s'"), *Name, *PackagePath)); } UMaterialFunction* MF = Cast(NewAsset); if (!MF) { - return MakeErrorJson(TEXT("Created asset is not a UMaterialFunction")); + return MakeErrorJson(Result, TEXT("Created asset is not a UMaterialFunction")); } // Set optional description @@ -1609,7 +1529,6 @@ FString FBlueprintMCPServer::HandleCreateMaterialFunction(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material Function '%s' (saved: %s)"), *Name, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("name"), Name); Result->SetStringField(TEXT("path"), MF->GetPathName()); @@ -1618,7 +1537,6 @@ FString FBlueprintMCPServer::HandleCreateMaterialFunction(const FString& Body) Result->SetStringField(TEXT("description"), Description); } Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ @@ -1629,18 +1547,12 @@ FString FBlueprintMCPServer::HandleCreateMaterialFunction(const FString& Body) // HandleSnapshotMaterialGraph — snapshot current material graph state // ============================================================ -FString FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FString& Body) +void FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); if (MaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: material")); + return MakeErrorJson(Result, TEXT("Missing required field: material")); } // Load material @@ -1648,13 +1560,13 @@ FString FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FString& Body) UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } EnsureMaterialGraph(Material); if (!Material->MaterialGraph) { - return MakeErrorJson(FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating snapshot for material '%s'"), *MaterialName); @@ -1709,33 +1621,25 @@ FString FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Material snapshot '%s' created with %d nodes, %d connections"), *Snapshot.SnapshotId, NodeCount, ConnectionCount); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetStringField(TEXT("snapshotId"), Snapshot.SnapshotId); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetNumberField(TEXT("nodeCount"), NodeCount); Result->SetNumberField(TEXT("connectionCount"), ConnectionCount); - return JsonToString(Result); } // ============================================================ // HandleDiffMaterialGraph — diff current material graph against snapshot // ============================================================ -FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) +void FBlueprintMCPServer::HandleDiffMaterialGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString SnapshotId = Json->GetStringField(TEXT("snapshotId")); if (MaterialName.IsEmpty() || SnapshotId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: material, snapshotId")); + return MakeErrorJson(Result, TEXT("Missing required fields: material, snapshotId")); } // Load snapshot from material snapshots (memory or disk) @@ -1745,7 +1649,7 @@ FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) { if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot)) { - return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); } SnapshotPtr = &LoadedSnapshot; } @@ -1755,13 +1659,13 @@ FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } EnsureMaterialGraph(Material); if (!Material->MaterialGraph) { - return MakeErrorJson(FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Diffing material '%s' against snapshot '%s'"), *MaterialName, *SnapshotId); @@ -1782,7 +1686,7 @@ FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) const FGraphSnapshotData* SnapDataPtr = SnapshotPtr->Graphs.Find(TEXT("MaterialGraph")); if (!SnapDataPtr) { - return MakeErrorJson(TEXT("Snapshot does not contain a MaterialGraph")); + return MakeErrorJson(Result, TEXT("Snapshot does not contain a MaterialGraph")); } const FGraphSnapshotData& SnapData = *SnapDataPtr; @@ -1870,7 +1774,6 @@ FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) } // Build result - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetStringField(TEXT("snapshotId"), SnapshotId); @@ -1886,28 +1789,20 @@ FString FBlueprintMCPServer::HandleDiffMaterialGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Material diff complete — %d severed, %d new, %d missing nodes"), SeveredArr.Num(), NewConnsArr.Num(), MissingNodesArr.Num()); - - return JsonToString(Result); } // ============================================================ // HandleRestoreMaterialGraph — restore material graph connections from snapshot // ============================================================ -FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) +void FBlueprintMCPServer::HandleRestoreMaterialGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); FString SnapshotId = Json->GetStringField(TEXT("snapshotId")); if (MaterialName.IsEmpty() || SnapshotId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: material, snapshotId")); + return MakeErrorJson(Result, TEXT("Missing required fields: material, snapshotId")); } bool bDryRun = false; @@ -1920,7 +1815,7 @@ FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) { if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot)) { - return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); } SnapshotPtr = &LoadedSnapshot; } @@ -1930,13 +1825,13 @@ FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } EnsureMaterialGraph(Material); if (!Material->MaterialGraph) { - return MakeErrorJson(FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' has no material graph"), *MaterialName)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Restoring material connections from snapshot '%s' for material '%s' (dryRun=%s)"), @@ -1970,7 +1865,7 @@ FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) const FGraphSnapshotData* SnapDataPtr = SnapshotPtr->Graphs.Find(TEXT("MaterialGraph")); if (!SnapDataPtr) { - return MakeErrorJson(TEXT("Snapshot does not contain a MaterialGraph")); + return MakeErrorJson(Result, TEXT("Snapshot does not contain a MaterialGraph")); } int32 Reconnected = 0; @@ -2082,7 +1977,6 @@ FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Material restore complete — %d reconnected, %d failed, saved=%s"), Reconnected, Failed, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetStringField(TEXT("snapshotId"), SnapshotId); @@ -2091,5 +1985,4 @@ FString FBlueprintMCPServer::HandleRestoreMaterialGraph(const FString& Body) Result->SetArrayField(TEXT("details"), DetailsArr); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("dryRun"), bDryRun); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialRead.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialRead.cpp index 283bd7b2..928ea8fd 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialRead.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_MaterialRead.cpp @@ -34,13 +34,13 @@ // HandleListMaterials — list Material and MaterialInstance assets // ============================================================ -FString FBlueprintMCPServer::HandleListMaterials(const TMap& Params) +void FBlueprintMCPServer::HandleListMaterials(const FJsonObject* Json, FJsonObject* Result) { - const FString* Filter = Params.Find(TEXT("filter")); - const FString* TypeFilter = Params.Find(TEXT("type")); + FString Filter = Json->GetStringField(TEXT("filter")); + FString TypeFilter = Json->GetStringField(TEXT("type")); - bool bIncludeMaterials = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("material"); - bool bIncludeInstances = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("instance"); + bool bIncludeMaterials = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("material"); + bool bIncludeInstances = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("instance"); TArray> Entries; @@ -51,10 +51,10 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap& P FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); - if (Filter && !Filter->IsEmpty()) + if (!Filter.IsEmpty()) { - if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) && - !Path.Contains(*Filter, ESearchCase::IgnoreCase)) + if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && + !Path.Contains(Filter, ESearchCase::IgnoreCase)) { continue; } @@ -75,10 +75,10 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap& P FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); - if (Filter && !Filter->IsEmpty()) + if (!Filter.IsEmpty()) { - if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) && - !Path.Contains(*Filter, ESearchCase::IgnoreCase)) + if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && + !Path.Contains(Filter, ESearchCase::IgnoreCase)) { continue; } @@ -94,26 +94,24 @@ FString FBlueprintMCPServer::HandleListMaterials(const TMap& P int32 Total = AllMaterialAssets.Num() + AllMaterialInstanceAssets.Num(); - TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("count"), Entries.Num()); Result->SetNumberField(TEXT("total"), Total); Result->SetArrayField(TEXT("materials"), Entries); - return JsonToString(Result); } // ============================================================ // HandleGetMaterial — detailed info about a material or instance // ============================================================ -FString FBlueprintMCPServer::HandleGetMaterial(const TMap& Params) +void FBlueprintMCPServer::HandleGetMaterial(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - if (!Name || Name->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + if (Name.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' parameter")); } - FString DecodedName = UrlDecode(*Name); + FString DecodedName = UrlDecode(Name); // Try loading as UMaterial first FString LoadError; @@ -122,7 +120,6 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap& Par { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *Material->GetName()); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("name"), Material->GetName()); Result->SetStringField(TEXT("path"), Material->GetPathName()); Result->SetStringField(TEXT("type"), TEXT("Material")); @@ -268,7 +265,7 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap& Par } Result->SetNumberField(TEXT("textureSampleCount"), TextureSampleCount); - return JsonToString(Result); + return; } // Try loading as MaterialInstance @@ -278,7 +275,6 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap& Par { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName()); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("name"), MI->GetName()); Result->SetStringField(TEXT("path"), MI->GetPathName()); Result->SetStringField(TEXT("type"), TEXT("MaterialInstance")); @@ -343,31 +339,31 @@ FString FBlueprintMCPServer::HandleGetMaterial(const TMap& Par Result->SetArrayField(TEXT("overriddenParameters"), OverriddenParams); - return JsonToString(Result); + return; } - return MakeErrorJson(FString::Printf(TEXT("Material or MaterialInstance '%s' not found. Use list_materials to see available assets."), *DecodedName)); + MakeErrorJson(Result, FString::Printf(TEXT("Material or MaterialInstance '%s' not found. Use list_materials to see available assets."), *DecodedName)); } // ============================================================ // HandleGetMaterialGraph — serialized graph for a material // ============================================================ -FString FBlueprintMCPServer::HandleGetMaterialGraph(const TMap& Params) +void FBlueprintMCPServer::HandleGetMaterialGraph(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - if (!Name || Name->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + if (Name.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' parameter")); } - FString DecodedName = UrlDecode(*Name); + FString DecodedName = UrlDecode(Name); FString LoadError; UMaterial* Material = LoadMaterialByName(DecodedName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialGraph — material '%s'"), *Material->GetName()); @@ -385,45 +381,39 @@ FString FBlueprintMCPServer::HandleGetMaterialGraph(const TMap if (!Material->MaterialGraph) { - return MakeErrorJson(TEXT("Could not build MaterialGraph for this material")); + return MakeErrorJson(Result, TEXT("Could not build MaterialGraph for this material")); } TSharedPtr GraphJson = SerializeGraph(Material->MaterialGraph); if (!GraphJson.IsValid()) { - return MakeErrorJson(TEXT("Failed to serialize material graph")); + return MakeErrorJson(Result, TEXT("Failed to serialize material graph")); } - // Add material name context - GraphJson->SetStringField(TEXT("material"), Material->GetName()); - GraphJson->SetStringField(TEXT("materialPath"), Material->GetPathName()); + CopyJsonFields(GraphJson.Get(), Result); - return JsonToString(GraphJson.ToSharedRef()); + // Add material name context + Result->SetStringField(TEXT("material"), Material->GetName()); + Result->SetStringField(TEXT("materialPath"), Material->GetPathName()); } // ============================================================ // HandleDescribeMaterial — human-readable material description // ============================================================ -FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body) +void FBlueprintMCPServer::HandleDescribeMaterial(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); if (MaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: material")); + return MakeErrorJson(Result, TEXT("Missing required field: material")); } FString LoadError; UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *Material->GetName()); @@ -439,7 +429,7 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body) if (!Material->MaterialGraph) { - return MakeErrorJson(TEXT("Could not build MaterialGraph for this material")); + return MakeErrorJson(Result, TEXT("Could not build MaterialGraph for this material")); } // Recursive helper: trace backwards from a pin and build a description string @@ -559,7 +549,7 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body) if (!RootNode) { - return MakeErrorJson(TEXT("Could not find root node in material graph")); + return MakeErrorJson(Result, TEXT("Could not find root node in material graph")); } for (UEdGraphPin* Pin : RootNode->Pins) @@ -585,7 +575,6 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body) InputDescriptions.Add(MakeShared(InputObj)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetStringField(TEXT("materialPath"), Material->GetPathName()); @@ -609,28 +598,26 @@ FString FBlueprintMCPServer::HandleDescribeMaterial(const FString& Body) { Result->SetStringField(TEXT("description"), TextDesc); } - - return JsonToString(Result); } // ============================================================ // HandleSearchMaterials — search expressions and parameters // ============================================================ -FString FBlueprintMCPServer::HandleSearchMaterials(const TMap& Params) +void FBlueprintMCPServer::HandleSearchMaterials(const FJsonObject* Json, FJsonObject* Result) { - const FString* Query = Params.Find(TEXT("query")); - if (!Query || Query->IsEmpty()) + FString Query = Json->GetStringField(TEXT("query")); + if (Query.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'query' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'query' parameter")); } - FString DecodedQuery = UrlDecode(*Query); + FString DecodedQuery = UrlDecode(Query); int32 MaxResults = 50; - if (const FString* M = Params.Find(TEXT("maxResults"))) + if (Json->HasField(TEXT("maxResults"))) { - MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 200); + MaxResults = FMath::Clamp((int32)Json->GetNumberField(TEXT("maxResults")), 1, 200); } TArray> Results; @@ -698,29 +685,21 @@ FString FBlueprintMCPServer::HandleSearchMaterials(const TMap& } } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("query"), DecodedQuery); Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetArrayField(TEXT("results"), Results); - return JsonToString(Result); } // ============================================================ // HandleFindMaterialReferences — find assets referencing a material // ============================================================ -FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body) +void FBlueprintMCPServer::HandleFindMaterialReferences(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); if (MaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: material")); + return MakeErrorJson(Result, TEXT("Missing required field: material")); } // Try to find the material's package path @@ -742,7 +721,7 @@ FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body) if (PackagePath.IsEmpty()) { - return MakeErrorJson(FString::Printf(TEXT("Material '%s' not found. Use list_materials to see available assets."), *MaterialName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Material '%s' not found. Use list_materials to see available assets."), *MaterialName)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindMaterialReferences — '%s' (package: %s)"), *MaterialName, *PackagePath); @@ -761,22 +740,20 @@ FString FBlueprintMCPServer::HandleFindMaterialReferences(const FString& Body) RefArray.Add(MakeShared(RefStr)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("material"), MaterialName); Result->SetStringField(TEXT("packagePath"), PackagePath); Result->SetNumberField(TEXT("totalReferencers"), RefArray.Num()); Result->SetArrayField(TEXT("referencers"), RefArray); - return JsonToString(Result); } // ============================================================ // HandleListMaterialFunctions — list MaterialFunction assets // ============================================================ -FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMap& Params) +void FBlueprintMCPServer::HandleListMaterialFunctions(const FJsonObject* Json, FJsonObject* Result) { - const FString* Filter = Params.Find(TEXT("filter")); + FString Filter = Json->GetStringField(TEXT("filter")); TArray> Entries; @@ -785,10 +762,10 @@ FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMapIsEmpty()) + if (!Filter.IsEmpty()) { - if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) && - !Path.Contains(*Filter, ESearchCase::IgnoreCase)) + if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && + !Path.Contains(Filter, ESearchCase::IgnoreCase)) { continue; } @@ -800,37 +777,34 @@ FString FBlueprintMCPServer::HandleListMaterialFunctions(const TMap(Entry)); } - TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("count"), Entries.Num()); Result->SetNumberField(TEXT("total"), AllMaterialFunctionAssets.Num()); Result->SetArrayField(TEXT("functions"), Entries); - return JsonToString(Result); } // ============================================================ // HandleGetMaterialFunction — detailed info about a material function // ============================================================ -FString FBlueprintMCPServer::HandleGetMaterialFunction(const TMap& Params) +void FBlueprintMCPServer::HandleGetMaterialFunction(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - if (!Name || Name->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + if (Name.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' parameter")); } - FString DecodedName = UrlDecode(*Name); + FString DecodedName = UrlDecode(Name); FString LoadError; UMaterialFunction* MF = LoadMaterialFunctionByName(DecodedName, LoadError); if (!MF) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName()); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("name"), MF->GetName()); Result->SetStringField(TEXT("path"), MF->GetPathName()); Result->SetStringField(TEXT("description"), MF->GetDescription()); @@ -891,26 +865,18 @@ FString FBlueprintMCPServer::HandleGetMaterialFunction(const TMapSetObjectField(TEXT("graph"), GraphJson); } } - - return JsonToString(Result); } // ============================================================ // HandleValidateMaterial — force recompile and check for errors // ============================================================ -FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body) +void FBlueprintMCPServer::HandleValidateMaterial(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString MaterialName = Json->GetStringField(TEXT("material")); if (MaterialName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: material")); + return MakeErrorJson(Result, TEXT("Missing required field: material")); } // Load material @@ -918,7 +884,7 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body) UMaterial* Material = LoadMaterialByName(MaterialName, LoadError); if (!Material) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *Material->GetName()); @@ -962,7 +928,6 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("valid"), bValid); Result->SetStringField(TEXT("material"), Material->GetName()); Result->SetStringField(TEXT("materialPath"), Material->GetPathName()); @@ -973,6 +938,4 @@ FString FBlueprintMCPServer::HandleValidateMaterial(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Material '%s' validation %s (%d errors)"), *Material->GetName(), bValid ? TEXT("passed") : TEXT("failed"), ErrorArray.Num()); - - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Mutation.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Mutation.cpp index 89e27d0e..b60bbd65 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Mutation.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Mutation.cpp @@ -53,21 +53,15 @@ extern int32 TryRefreshAllNodesSEH(UBlueprint* BP); // HandleReplaceFunctionCalls — redirect function call nodes // ============================================================ -FString FBlueprintMCPServer::HandleReplaceFunctionCalls(const FString& Body) +void FBlueprintMCPServer::HandleReplaceFunctionCalls(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString OldClassName = Json->GetStringField(TEXT("oldClass")); FString NewClassName = Json->GetStringField(TEXT("newClass")); if (BlueprintName.IsEmpty() || OldClassName.IsEmpty() || NewClassName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, oldClass, newClass")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, oldClass, newClass")); } // Load Blueprint @@ -75,7 +69,7 @@ FString FBlueprintMCPServer::HandleReplaceFunctionCalls(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the new class — try several search strategies @@ -110,7 +104,7 @@ FString FBlueprintMCPServer::HandleReplaceFunctionCalls(const FString& Body) if (!NewClass) { - return MakeErrorJson(FString::Printf(TEXT("Could not find class '%s'"), *NewClassName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Could not find class '%s'"), *NewClassName)); } // Check for dry run @@ -280,13 +274,12 @@ FString FBlueprintMCPServer::HandleReplaceFunctionCalls(const FString& Body) if (bDryRun) { - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("wouldReplaceCount"), ReplacedCount); Result->SetNumberField(TEXT("connectionsAtRisk"), BrokenConnections.Num()); Result->SetArrayField(TEXT("connectionsAtRisk"), BrokenConnections); - return JsonToString(Result); + return; } // Save — guard flags and SEH protection are handled inside SaveBlueprintPackage @@ -296,39 +289,30 @@ FString FBlueprintMCPServer::HandleReplaceFunctionCalls(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Replaced %d function call(s), save %s"), ReplacedCount, bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("replacedCount"), ReplacedCount); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetNumberField(TEXT("brokenConnectionCount"), BrokenConnections.Num()); Result->SetArrayField(TEXT("brokenConnections"), BrokenConnections); - return JsonToString(Result); + return; } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("replacedCount"), 0); Result->SetStringField(TEXT("message"), FString::Printf( TEXT("No function call nodes found targeting class '%s'"), *OldClassName)); - return JsonToString(Result); } // ============================================================ // HandleDeleteAsset — delete a .uasset after verifying no references // ============================================================ -FString FBlueprintMCPServer::HandleDeleteAsset(const FString& Body) +void FBlueprintMCPServer::HandleDeleteAsset(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); if (AssetPath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: assetPath")); + return MakeErrorJson(Result, TEXT("Missing required field: assetPath")); } bool bForce = false; @@ -344,7 +328,7 @@ FString FBlueprintMCPServer::HandleDeleteAsset(const FString& Body) if (!IFileManager::Get().FileExists(*PackageFilename)) { - return MakeErrorJson(FString::Printf(TEXT("Asset file not found on disk: %s"), *PackageFilename)); + return MakeErrorJson(Result, FString::Printf(TEXT("Asset file not found on disk: %s"), *PackageFilename)); } // Check references @@ -376,8 +360,7 @@ FString FBlueprintMCPServer::HandleDeleteAsset(const FString& Body) } } - TSharedRef Result = MakeShared(); - Result->SetStringField(TEXT("error"), TEXT("Asset is still referenced. Remove all references first.")); + MakeErrorJson(Result, TEXT("Asset is still referenced. Remove all references first.")); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetNumberField(TEXT("referencerCount"), Referencers.Num()); Result->SetNumberField(TEXT("liveReferencerCount"), LiveRefs.Num()); @@ -388,7 +371,7 @@ FString FBlueprintMCPServer::HandleDeleteAsset(const FString& Body) StaleRefs.Num() > 0 ? TEXT("Some references may be stale. Consider force=true to skip the reference check, or use change_variable_type to migrate references first.") : TEXT("All references are live. Migrate with change_variable_type or replace_function_calls before deleting.")); - return JsonToString(Result); + return; } // Force delete: unload the package from memory first @@ -457,34 +440,26 @@ FString FBlueprintMCPServer::HandleDeleteAsset(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), bDeleted); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("filename"), PackageFilename); Result->SetBoolField(TEXT("forced"), bForce); if (!bDeleted) { - Result->SetStringField(TEXT("error"), TEXT("Failed to delete file from disk")); + MakeErrorJson(Result, TEXT("Failed to delete file from disk")); } if (RefWarnings.Num() > 0) { Result->SetArrayField(TEXT("warnings"), RefWarnings); } - return JsonToString(Result); } // ============================================================ // HandleConnectPins — wire two pins together // ============================================================ -FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) +void FBlueprintMCPServer::HandleConnectPins(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString SourceNodeId = Json->GetStringField(TEXT("sourceNodeId")); FString SourcePinName = Json->GetStringField(TEXT("sourcePinName")); @@ -494,7 +469,7 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) if (BlueprintName.IsEmpty() || SourceNodeId.IsEmpty() || SourcePinName.IsEmpty() || TargetNodeId.IsEmpty() || TargetPinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, sourceNodeId, sourcePinName, targetNodeId, targetPinName")); } // Load Blueprint @@ -502,7 +477,7 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find source node @@ -510,14 +485,14 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) UEdGraphNode* SourceNode = FindNodeByGuid(BP, SourceNodeId, &SourceGraph); if (!SourceNode) { - return MakeErrorJson(FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Source node '%s' not found"), *SourceNodeId)); } // Find target node UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId); if (!TargetNode) { - return MakeErrorJson(FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); } // Find source pin @@ -532,11 +507,10 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")))); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), + MakeErrorJson(Result, FString::Printf(TEXT("Source pin '%s' not found on node '%s'"), *SourcePinName, *SourceNodeId)); - E->SetArrayField(TEXT("availablePins"), PinNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availablePins"), PinNames); + return; } // Find target pin @@ -551,11 +525,10 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) FString::Printf(TEXT("%s (%s)"), *P->PinName.ToString(), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output")))); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), + MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId)); - E->SetArrayField(TEXT("availablePins"), PinNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availablePins"), PinNames); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Connecting %s.%s -> %s.%s"), @@ -566,11 +539,10 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) const UEdGraphSchema* Schema = SourceGraph->GetSchema(); if (!Schema) { - return MakeErrorJson(TEXT("Graph schema not found")); + return MakeErrorJson(Result, TEXT("Graph schema not found")); } bool bConnected = Schema->TryCreateConnection(SourcePin, TargetPin); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), bConnected); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("sourcePinType"), SourcePin->PinType.PinCategory.ToString()); @@ -586,8 +558,7 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) FString Reason = FString::Printf(TEXT("Cannot connect %s (%s) to %s (%s) — types are incompatible"), *SourcePinName, *SourcePin->PinType.PinCategory.ToString(), *TargetPinName, *TargetPin->PinType.PinCategory.ToString()); - Result->SetStringField(TEXT("error"), Reason); - return JsonToString(Result); + return MakeErrorJson(Result, Reason); } // Save @@ -605,29 +576,21 @@ FString FBlueprintMCPServer::HandleConnectPins(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Connection %s, save %s"), bConnected ? TEXT("succeeded") : TEXT("failed"), bSaved ? TEXT("succeeded") : TEXT("failed")); - - return JsonToString(Result); } // ============================================================ // HandleDisconnectPin — break connections on a pin // ============================================================ -FString FBlueprintMCPServer::HandleDisconnectPin(const FString& Body) +void FBlueprintMCPServer::HandleDisconnectPin(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); FString PinName = Json->GetStringField(TEXT("pinName")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || PinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId, pinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, pinName")); } // Optional: specific target to disconnect from @@ -639,21 +602,21 @@ FString FBlueprintMCPServer::HandleDisconnectPin(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find source node UEdGraphNode* Node = FindNodeByGuid(BP, NodeId); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } // Find pin UEdGraphPin* Pin = Node->FindPin(FName(*PinName)); if (!Pin) { - return MakeErrorJson(FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); } int32 DisconnectedCount = 0; @@ -664,13 +627,13 @@ FString FBlueprintMCPServer::HandleDisconnectPin(const FString& Body) UEdGraphNode* TargetNode = FindNodeByGuid(BP, TargetNodeId); if (!TargetNode) { - return MakeErrorJson(FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Target node '%s' not found"), *TargetNodeId)); } UEdGraphPin* TargetPin = TargetNode->FindPin(FName(*TargetPinName)); if (!TargetPin) { - return MakeErrorJson(FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), + return MakeErrorJson(Result, FString::Printf(TEXT("Target pin '%s' not found on node '%s'"), *TargetPinName, *TargetNodeId)); } @@ -681,7 +644,7 @@ FString FBlueprintMCPServer::HandleDisconnectPin(const FString& Body) } else { - return MakeErrorJson(TEXT("The specified pins are not connected to each other")); + return MakeErrorJson(Result, TEXT("The specified pins are not connected to each other")); } } else @@ -704,30 +667,22 @@ FString FBlueprintMCPServer::HandleDisconnectPin(const FString& Body) bSaved = SaveBlueprintPackage(BP); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("disconnectedCount"), DisconnectedCount); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRefreshAllNodes — refresh all nodes and recompile // ============================================================ -FString FBlueprintMCPServer::HandleRefreshAllNodes(const FString& Body) +void FBlueprintMCPServer::HandleRefreshAllNodes(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } // Load Blueprint @@ -735,7 +690,7 @@ FString FBlueprintMCPServer::HandleRefreshAllNodes(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Count graphs and nodes before refresh @@ -833,7 +788,6 @@ FString FBlueprintMCPServer::HandleRefreshAllNodes(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), !bRefreshCrashed); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("graphCount"), GraphCount); @@ -846,21 +800,14 @@ FString FBlueprintMCPServer::HandleRefreshAllNodes(const FString& Body) { WarningsArr.Add(MakeShared(TEXT("RefreshAllNodes crashed (SEH caught), save was still attempted"))); } - return JsonToString(Result); } // ============================================================ // HandleSetPinDefault — set the default value of a pin on a node // ============================================================ -FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) +void FBlueprintMCPServer::HandleSetPinDefault(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - // Check for batch mode const TArray>* BatchArray = nullptr; if (Json->TryGetArrayField(TEXT("batch"), BatchArray) && BatchArray && BatchArray->Num() > 0) @@ -966,13 +913,12 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Batch SetPinDefault — %d/%d succeeded, save %s"), SuccessCount, BatchArray->Num(), bAllSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("successCount"), SuccessCount); Result->SetNumberField(TEXT("totalCount"), BatchArray->Num()); Result->SetArrayField(TEXT("results"), Results); Result->SetBoolField(TEXT("saved"), bAllSaved); - return JsonToString(Result); + return; } // Single-pin mode (existing logic) @@ -983,7 +929,7 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || PinName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId, pinName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, pinName")); } // Load Blueprint @@ -991,7 +937,7 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find node @@ -999,20 +945,20 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } // Find pin UEdGraphPin* Pin = Node->FindPin(FName(*PinName)); if (!Pin) { - return MakeErrorJson(FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *NodeId)); } // Only allow setting defaults on input pins if (Pin->Direction != EGPD_Input) { - return MakeErrorJson(FString::Printf(TEXT("Pin '%s' is an output pin — can only set defaults on input pins"), *PinName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' is an output pin — can only set defaults on input pins"), *PinName)); } // Store old value for reporting @@ -1040,7 +986,6 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) // Save bool bSaved = SaveBlueprintPackage(BP); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); @@ -1048,28 +993,21 @@ FString FBlueprintMCPServer::HandleSetPinDefault(const FString& Body) Result->SetStringField(TEXT("oldValue"), OldValue); Result->SetStringField(TEXT("newValue"), Pin->DefaultValue); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleChangeStructNodeType — change the struct type on a Break/Make node // ============================================================ -FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) +void FBlueprintMCPServer::HandleChangeStructNodeType(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); FString NewType = Json->GetStringField(TEXT("newType")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty() || NewType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId, newType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId, newType")); } // Load Blueprint @@ -1077,7 +1015,7 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find node @@ -1085,7 +1023,7 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } // Determine what kind of struct node this is @@ -1094,7 +1032,7 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) if (!BreakNode && !MakeNode) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' is not a BreakStruct or MakeStruct node (class: %s)"), + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' is not a BreakStruct or MakeStruct node (class: %s)"), *NodeId, *Node->GetClass()->GetName())); } @@ -1113,7 +1051,7 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) } if (!NewStruct) { - return MakeErrorJson(FString::Printf(TEXT("Struct type '%s' not found"), *NewType)); + return MakeErrorJson(Result, FString::Printf(TEXT("Struct type '%s' not found"), *NewType)); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Changing struct node '%s' to type '%s'"), @@ -1183,7 +1121,7 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) const UEdGraphSchema* Schema = Graph->GetSchema(); if (!Schema) { - return MakeErrorJson(TEXT("Graph schema not found")); + return MakeErrorJson(Result, TEXT("Graph schema not found")); } // Reconstruct to rebuild pins for the new struct type (use schema version for MinimalAPI compat) @@ -1262,7 +1200,6 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) // Return updated node state TSharedPtr UpdatedNodeState = SerializeNode(Node); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); @@ -1276,27 +1213,20 @@ FString FBlueprintMCPServer::HandleChangeStructNodeType(const FString& Body) { Result->SetObjectField(TEXT("updatedNode"), UpdatedNodeState); } - return JsonToString(Result); } // ============================================================ // HandleDeleteNode — remove a node from a blueprint graph // ============================================================ -FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) +void FBlueprintMCPServer::HandleDeleteNode(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId")); } // Load Blueprint @@ -1304,7 +1234,7 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find node @@ -1312,11 +1242,11 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) UEdGraphNode* Node = FindNodeByGuid(BP, NodeId, &Graph); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } if (!Graph) { - return MakeErrorJson(FString::Printf(TEXT("Graph not found for node '%s'"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Graph not found for node '%s'"), *NodeId)); } FString NodeClass = Node->GetClass()->GetName(); @@ -1328,7 +1258,7 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) // without recreating the entire function/event. if (Cast(Node)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot delete FunctionEntry node '%s' in graph '%s'. ") TEXT("This is the root node of the function — removing it would leave an empty, uncompilable graph. ") TEXT("To remove the entire function, delete it from the Blueprint editor."), @@ -1336,14 +1266,14 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) } if (Cast(Node)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot delete event entry node '%s' in graph '%s'. ") TEXT("This is the root node of the event handler — removing it would leave an empty, uncompilable graph."), *NodeTitle, *GraphName)); } if (Cast(Node)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Cannot delete CustomEvent entry node '%s' in graph '%s'. ") TEXT("This is the root node of the custom event — removing it would leave an empty, uncompilable graph."), *NodeTitle, *GraphName)); @@ -1364,7 +1294,6 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Node deleted, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); @@ -1372,28 +1301,21 @@ FString FBlueprintMCPServer::HandleDeleteNode(const FString& Body) Result->SetStringField(TEXT("nodeTitle"), NodeTitle); Result->SetStringField(TEXT("graph"), GraphName); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleAddNode — create a new node in a blueprint graph // ============================================================ -FString FBlueprintMCPServer::HandleAddNode(const FString& Body) +void FBlueprintMCPServer::HandleAddNode(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr 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() || GraphName.IsEmpty() || NodeType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, nodeType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, nodeType")); } int32 PosX = 0, PosY = 0; @@ -1407,7 +1329,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the target graph (URL decode graph name) @@ -1432,10 +1354,9 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) { if (Graph) GraphNames.Add(MakeShared(Graph->GetName())); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); - E->SetArrayField(TEXT("availableGraphs"), GraphNames); - return JsonToString(E); + MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); + Result->SetArrayField(TEXT("availableGraphs"), GraphNames); + return; } UEdGraphNode* NewNode = nullptr; @@ -1445,7 +1366,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString TypeName = Json->GetStringField(TEXT("typeName")); if (TypeName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'typeName' for BreakStruct/MakeStruct")); + return MakeErrorJson(Result, TEXT("Missing required field 'typeName' for BreakStruct/MakeStruct")); } // Find the struct type @@ -1470,7 +1391,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) } if (!FoundStruct) { - return MakeErrorJson(FString::Printf(TEXT("Struct type '%s' not found"), *TypeName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Struct type '%s' not found"), *TypeName)); } if (NodeType == TEXT("BreakStruct")) @@ -1501,7 +1422,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) if (FunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'functionName' for CallFunction")); + return MakeErrorJson(Result, TEXT("Missing required field 'functionName' for CallFunction")); } // Find the function @@ -1541,7 +1462,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) if (!TargetFunc) { - return MakeErrorJson(FString::Printf(TEXT("Function '%s' not found%s"), + return MakeErrorJson(Result, FString::Printf(TEXT("Function '%s' not found%s"), *FunctionName, ClassName.IsEmpty() ? TEXT("") : *FString::Printf(TEXT(" in class '%s'"), *ClassName))); } @@ -1558,7 +1479,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString VariableName = Json->GetStringField(TEXT("variableName")); if (VariableName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'variableName' for VariableGet/VariableSet")); + return MakeErrorJson(Result, TEXT("Missing required field 'variableName' for VariableGet/VariableSet")); } // Verify the variable exists in the blueprint @@ -1586,7 +1507,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) if (!bVarFound) { - return MakeErrorJson(FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), + return MakeErrorJson(Result, FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName)); } @@ -1616,7 +1537,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString CastTarget = Json->GetStringField(TEXT("castTarget")); if (CastTarget.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'castTarget' for DynamicCast")); + return MakeErrorJson(Result, TEXT("Missing required field 'castTarget' for DynamicCast")); } // Find the target class (C++ or Blueprint) @@ -1632,7 +1553,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) } if (!TargetClass) { - return MakeErrorJson(FString::Printf(TEXT("Cast target class '%s' not found"), *CastTarget)); + return MakeErrorJson(Result, FString::Printf(TEXT("Cast target class '%s' not found"), *CastTarget)); } UK2Node_DynamicCast* CastNode = NewObject(TargetGraph); @@ -1648,18 +1569,18 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString FunctionName = Json->GetStringField(TEXT("functionName")); if (FunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'functionName' for OverrideEvent")); + return MakeErrorJson(Result, TEXT("Missing required field 'functionName' for OverrideEvent")); } if (!BP->ParentClass) { - return MakeErrorJson(TEXT("Blueprint has no parent class")); + return MakeErrorJson(Result, TEXT("Blueprint has no parent class")); } UFunction* Func = BP->ParentClass->FindFunctionByName(FName(*FunctionName)); if (!Func) { - return MakeErrorJson(FString::Printf(TEXT("Function '%s' not found on parent class '%s'"), + return MakeErrorJson(Result, FString::Printf(TEXT("Function '%s' not found on parent class '%s'"), *FunctionName, *BP->ParentClass->GetName())); } @@ -1673,7 +1594,6 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) { // Already exists — return it with alreadyExists flag TSharedPtr NodeState = SerializeNode(ExistingEvent); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetBoolField(TEXT("alreadyExists"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); @@ -1687,7 +1607,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: OverrideEvent '%s' already exists in '%s', returning existing node"), *FunctionName, *BlueprintName); - return JsonToString(Result); + return; } } } @@ -1706,18 +1626,18 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString FunctionName = Json->GetStringField(TEXT("functionName")); if (FunctionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'functionName' for CallParentFunction")); + return MakeErrorJson(Result, TEXT("Missing required field 'functionName' for CallParentFunction")); } if (!BP->ParentClass) { - return MakeErrorJson(TEXT("Blueprint has no parent class")); + return MakeErrorJson(Result, TEXT("Blueprint has no parent class")); } UFunction* Func = BP->ParentClass->FindFunctionByName(FName(*FunctionName)); if (!Func) { - return MakeErrorJson(FString::Printf(TEXT("Function '%s' not found on parent class '%s'"), + return MakeErrorJson(Result, FString::Printf(TEXT("Function '%s' not found on parent class '%s'"), *FunctionName, *BP->ParentClass->GetName())); } @@ -1752,7 +1672,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) FString EventName = Json->GetStringField(TEXT("eventName")); if (EventName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field 'eventName' for CustomEvent")); + return MakeErrorJson(Result, TEXT("Missing required field 'eventName' for CustomEvent")); } UK2Node_CustomEvent* EventNode = NewObject(TargetGraph); @@ -1792,7 +1712,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) if (!MacroGraph) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Standard macro '%s' not found. Ensure the engine standard macros are loaded."), *MacroName)); } @@ -1822,7 +1742,7 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) } if (!ActorClass) { - return MakeErrorJson(FString::Printf(TEXT("Actor class '%s' not found"), *ClassName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Actor class '%s' not found"), *ClassName)); } } @@ -1893,14 +1813,14 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Unsupported nodeType '%s'. Supported: BreakStruct, MakeStruct, CallFunction, VariableGet, VariableSet, DynamicCast, OverrideEvent, CallParentFunction, Branch, Sequence, CustomEvent, ForEachLoop, ForLoop, ForLoopWithBreak, WhileLoop, SpawnActorFromClass, Select, Comment, Reroute"), *NodeType)); } if (!NewNode) { - return MakeErrorJson(TEXT("Failed to create node")); + return MakeErrorJson(Result, TEXT("Failed to create node")); } // Ensure node has a valid GUID (PostInitProperties may skip it in some contexts) @@ -1922,7 +1842,6 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) // Serialize the new node (includes GUID and pin list) TSharedPtr NodeState = SerializeNode(NewNode); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("graph"), DecodedGraphName); @@ -1933,27 +1852,20 @@ FString FBlueprintMCPServer::HandleAddNode(const FString& Body) { Result->SetObjectField(TEXT("node"), NodeState); } - return JsonToString(Result); } // ============================================================ // HandleRenameAsset — rename or move an asset // ============================================================ -FString FBlueprintMCPServer::HandleRenameAsset(const FString& Body) +void FBlueprintMCPServer::HandleRenameAsset(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); FString NewPath = Json->GetStringField(TEXT("newPath")); if (AssetPath.IsEmpty() || NewPath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: assetPath, newPath")); + return MakeErrorJson(Result, TEXT("Missing required fields: assetPath, newPath")); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renaming asset '%s' -> '%s'"), *AssetPath, *NewPath); @@ -1969,13 +1881,13 @@ FString FBlueprintMCPServer::HandleRenameAsset(const FString& Body) FAssetData* FoundAsset = FindAnyAsset(AssetPath); if (!FoundAsset) { - return MakeErrorJson(FString::Printf(TEXT("Asset '%s' not found. Checked Blueprints, Materials, Material Instances, and Material Functions."), *AssetPath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Asset '%s' not found. Checked Blueprints, Materials, Material Instances, and Material Functions."), *AssetPath)); } UObject* AssetObj = FoundAsset->GetAsset(); if (!AssetObj) { - return MakeErrorJson(FString::Printf(TEXT("Failed to load asset '%s'"), *AssetPath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to load asset '%s'"), *AssetPath)); } // Parse new path into package path and asset name @@ -2019,7 +1931,6 @@ FString FBlueprintMCPServer::HandleRenameAsset(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Rename %s"), bSuccess ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), bSuccess); Result->SetStringField(TEXT("oldPath"), AssetPath); Result->SetStringField(TEXT("newPath"), NewPath); @@ -2027,30 +1938,23 @@ FString FBlueprintMCPServer::HandleRenameAsset(const FString& Body) Result->SetStringField(TEXT("newAssetName"), NewAssetName); if (!bSuccess) { - Result->SetStringField(TEXT("error"), TEXT("Asset rename failed. The target path may be invalid or a conflicting asset may exist.")); + MakeErrorJson(Result, TEXT("Asset rename failed. The target path may be invalid or a conflicting asset may exist.")); } - return JsonToString(Result); } // ============================================================ // Set Blueprint Default Property Value // ============================================================ -FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) +void FBlueprintMCPServer::HandleSetBlueprintDefault(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString PropertyName = Json->GetStringField(TEXT("property")); FString Value = Json->GetStringField(TEXT("value")); if (BlueprintName.IsEmpty() || PropertyName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, property")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, property")); } // Load Blueprint @@ -2058,24 +1962,24 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } if (!BP->GeneratedClass) { - return MakeErrorJson(TEXT("Blueprint has no GeneratedClass")); + return MakeErrorJson(Result, TEXT("Blueprint has no GeneratedClass")); } UObject* CDO = BP->GeneratedClass->GetDefaultObject(); if (!CDO) { - return MakeErrorJson(TEXT("Could not get Class Default Object")); + return MakeErrorJson(Result, TEXT("Could not get Class Default Object")); } FProperty* Prop = BP->GeneratedClass->FindPropertyByName(*PropertyName); if (!Prop) { - return MakeErrorJson(FString::Printf(TEXT("Property '%s' not found on '%s'"), *PropertyName, *BlueprintName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Property '%s' not found on '%s'"), *PropertyName, *BlueprintName)); } FString OldValue; @@ -2116,7 +2020,7 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) if (!ResolvedClass) { - return MakeErrorJson(FString::Printf(TEXT("Could not resolve '%s' to a class"), *Value)); + return MakeErrorJson(Result, FString::Printf(TEXT("Could not resolve '%s' to a class"), *Value)); } // Validate meta class compatibility @@ -2125,7 +2029,7 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) UClass* MetaClass = ClassProp->MetaClass; if (MetaClass && !ResolvedClass->IsChildOf(MetaClass)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("'%s' is not a subclass of '%s' (required by property '%s')"), *ResolvedClass->GetName(), *MetaClass->GetName(), *PropertyName)); } @@ -2155,7 +2059,7 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) if (!ResolvedObj) { - return MakeErrorJson(FString::Printf(TEXT("Could not resolve '%s' to an object"), *Value)); + return MakeErrorJson(Result, FString::Printf(TEXT("Could not resolve '%s' to an object"), *Value)); } ObjProp->SetPropertyValue_InContainer(CDO, ResolvedObj); @@ -2173,7 +2077,7 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Failed to set property '%s' to '%s' — value could not be parsed for type '%s'"), *PropertyName, *Value, *Prop->GetCPPType())); } @@ -2181,7 +2085,7 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) if (!bSuccess) { - return MakeErrorJson(TEXT("Failed to set property value")); + return MakeErrorJson(Result, TEXT("Failed to set property value")); } // Mark modified and save @@ -2194,7 +2098,6 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set '%s.%s' from '%s' to '%s' (saved: %s)"), *BlueprintName, *PropertyName, *OldValue, *ActualNewValue, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("property"), PropertyName); @@ -2202,25 +2105,18 @@ FString FBlueprintMCPServer::HandleSetBlueprintDefault(const FString& Body) Result->SetStringField(TEXT("newValue"), ActualNewValue); Result->SetStringField(TEXT("propertyType"), Prop->GetCPPType()); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleMoveNode — reposition one or more nodes in a blueprint graph // ============================================================ -FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) +void FBlueprintMCPServer::HandleMoveNode(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } // Load Blueprint @@ -2228,7 +2124,7 @@ FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Check for batch mode @@ -2279,26 +2175,25 @@ FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: MoveNode batch — %d/%d succeeded, save %s"), SuccessCount, NodesArray->Num(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetNumberField(TEXT("movedCount"), SuccessCount); Result->SetNumberField(TEXT("totalRequested"), NodesArray->Num()); Result->SetArrayField(TEXT("results"), Results); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); + return; } // Single node mode FString NodeId = Json->GetStringField(TEXT("nodeId")); if (NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: nodeId (or use 'nodes' array for batch mode)")); + return MakeErrorJson(Result, TEXT("Missing required field: nodeId (or use 'nodes' array for batch mode)")); } if (!Json->HasField(TEXT("x")) || !Json->HasField(TEXT("y"))) { - return MakeErrorJson(TEXT("Missing required fields: x, y")); + return MakeErrorJson(Result, TEXT("Missing required fields: x, y")); } int32 X = (int32)Json->GetNumberField(TEXT("x")); @@ -2307,7 +2202,7 @@ FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) UEdGraphNode* Node = FindNodeByGuid(BP, NodeId); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } int32 OldX = Node->NodePosX; @@ -2321,7 +2216,6 @@ FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) FBlueprintEditorUtils::MarkBlueprintAsModified(BP); bool bSaved = SaveBlueprintPackage(BP); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); @@ -2330,34 +2224,27 @@ FString FBlueprintMCPServer::HandleMoveNode(const FString& Body) Result->SetNumberField(TEXT("newX"), Node->NodePosX); Result->SetNumberField(TEXT("newY"), Node->NodePosY); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleDuplicateNodes — duplicate one or more nodes in a graph // ============================================================ -FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) +void FBlueprintMCPServer::HandleDuplicateNodes(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString GraphName = Json->GetStringField(TEXT("graph")); if (BlueprintName.IsEmpty() || GraphName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, graph")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph")); } // Get node IDs const TArray>* NodeIdsArray = nullptr; if (!Json->TryGetArrayField(TEXT("nodeIds"), NodeIdsArray) || !NodeIdsArray || NodeIdsArray->Num() == 0) { - return MakeErrorJson(TEXT("Missing required field: nodeIds (array of node GUIDs)")); + return MakeErrorJson(Result, TEXT("Missing required field: nodeIds (array of node GUIDs)")); } int32 OffsetX = 50; @@ -2372,7 +2259,7 @@ FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the target graph @@ -2392,7 +2279,7 @@ FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) if (!TargetGraph) { - return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); } // Find all source nodes @@ -2423,7 +2310,7 @@ FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) if (SourceNodes.Num() == 0) { - return MakeErrorJson(TEXT("No valid nodes found to duplicate")); + return MakeErrorJson(Result, TEXT("No valid nodes found to duplicate")); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Duplicating %d node(s) in graph '%s' of '%s'"), @@ -2482,7 +2369,6 @@ FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Duplicated %d node(s), save %s"), DuplicatedNodes.Num(), bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("graph"), DecodedGraphName); @@ -2499,75 +2385,59 @@ FString FBlueprintMCPServer::HandleDuplicateNodes(const FString& Body) } Result->SetArrayField(TEXT("notFound"), NotFoundArr); } - - return JsonToString(Result); } // ============================================================ // HandleGetNodeComment — read a node's comment text // ============================================================ -FString FBlueprintMCPServer::HandleGetNodeComment(const FString& Body) +void FBlueprintMCPServer::HandleGetNodeComment(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId")); } FString LoadError; UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UEdGraphNode* Node = FindNodeByGuid(BP, NodeId); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetStringField(TEXT("comment"), Node->NodeComment); Result->SetBoolField(TEXT("commentBubbleVisible"), Node->bCommentBubbleVisible); - return JsonToString(Result); } // ============================================================ // HandleSetNodeComment — set a node's comment text // ============================================================ -FString FBlueprintMCPServer::HandleSetNodeComment(const FString& Body) +void FBlueprintMCPServer::HandleSetNodeComment(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString NodeId = Json->GetStringField(TEXT("nodeId")); if (BlueprintName.IsEmpty() || NodeId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, nodeId")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, nodeId")); } if (!Json->HasField(TEXT("comment"))) { - return MakeErrorJson(TEXT("Missing required field: comment")); + return MakeErrorJson(Result, TEXT("Missing required field: comment")); } FString Comment = Json->GetStringField(TEXT("comment")); @@ -2576,13 +2446,13 @@ FString FBlueprintMCPServer::HandleSetNodeComment(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UEdGraphNode* Node = FindNodeByGuid(BP, NodeId); if (!Node) { - return MakeErrorJson(FString::Printf(TEXT("Node '%s' not found"), *NodeId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *NodeId)); } FString OldComment = Node->NodeComment; @@ -2601,14 +2471,12 @@ FString FBlueprintMCPServer::HandleSetNodeComment(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Set comment on node '%s' in '%s', save %s"), *NodeId, *BlueprintName, bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("nodeId"), NodeId); Result->SetStringField(TEXT("oldComment"), OldComment); Result->SetStringField(TEXT("newComment"), Comment); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ @@ -2693,18 +2561,12 @@ struct FNodeActionSearch // for spawners matching a query string (same pool as the right-click menu) // ============================================================ -FString FBlueprintMCPServer::HandleSearchNodeActions(const FString& Body) +void FBlueprintMCPServer::HandleSearchNodeActions(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString Query = Json->GetStringField(TEXT("query")); if (Query.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: query")); + return MakeErrorJson(Result, TEXT("Missing required field: query")); } int32 MaxResults = 50; @@ -2722,11 +2584,9 @@ FString FBlueprintMCPServer::HandleSearchNodeActions(const FString& Body) ResultArray.Add(MakeShared(Name)); } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetNumberField(TEXT("count"), ResultArray.Num()); Result->SetArrayField(TEXT("actions"), ResultArray); - return JsonToString(Result); } // ============================================================ @@ -2734,21 +2594,15 @@ FString FBlueprintMCPServer::HandleSearchNodeActions(const FString& Body) // Takes a full action name, finds the spawner, and calls Invoke(). // ============================================================ -FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) +void FBlueprintMCPServer::HandleSpawnNode(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr 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 ActionName = Json->GetStringField(TEXT("actionName")); if (BlueprintName.IsEmpty() || GraphName.IsEmpty() || ActionName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, graph, actionName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, graph, actionName")); } int32 PosX = 0, PosY = 0; @@ -2762,7 +2616,7 @@ FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the target graph @@ -2787,23 +2641,22 @@ FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) { if (Graph) GraphNames.Add(MakeShared(Graph->GetName())); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); - E->SetArrayField(TEXT("availableGraphs"), GraphNames); - return JsonToString(E); + MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); + Result->SetArrayField(TEXT("availableGraphs"), GraphNames); + return; } // Find the spawner by exact full name TArray Matches = FNodeActionSearch::FindSpawner(ActionName); if (Matches.Num() == 0) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("No action found matching '%s'. Use search_node_actions to find available actions."), *ActionName)); } if (Matches.Num() > 1) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Ambiguous: %d spawners match '%s'. Cannot determine which one to use."), Matches.Num(), *ActionName)); } @@ -2816,7 +2669,7 @@ FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) if (!NewNode) { - return MakeErrorJson(TEXT("Spawner Invoke() returned null — node creation failed.")); + return MakeErrorJson(Result, TEXT("Spawner Invoke() returned null — node creation failed.")); } // Ensure valid GUID @@ -2839,7 +2692,6 @@ FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) // Serialize result TSharedPtr NodeState = SerializeNode(NewNode); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("graph"), DecodedGraphName); @@ -2852,5 +2704,4 @@ FString FBlueprintMCPServer::HandleSpawnNode(const FString& Body) Result->SetObjectField(TEXT("node"), NodeState); } Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Params.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Params.cpp index 707fb2f3..16a27d7a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Params.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Params.cpp @@ -13,14 +13,8 @@ #include "Serialization/JsonSerializer.h" #include "UObject/UObjectIterator.h" -FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) +void FBlueprintMCPServer::HandleChangeFunctionParamType(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString FunctionName = Json->GetStringField(TEXT("functionName")); FString ParamName = Json->GetStringField(TEXT("paramName")); @@ -28,7 +22,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty() || NewTypeName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName, newType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName, newType")); } // Load Blueprint @@ -36,7 +30,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references) @@ -44,7 +38,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) FString TypeError; if (!ResolveTypeFromString(NewTypeName, NewPinType, TypeError)) { - return MakeErrorJson(TypeError); + return MakeErrorJson(Result, TypeError); } // Find the entry node: K2Node_FunctionEntry in a function graph, @@ -117,12 +111,11 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Function or custom event '%s' not found in Blueprint '%s'"), *FunctionName, *BlueprintName)); - E->SetArrayField(TEXT("availableFunctionsAndEvents"), Available); - return JsonToString(E); + Result->SetArrayField(TEXT("availableFunctionsAndEvents"), Available); + return; } // Find the UserDefinedPin matching paramName @@ -149,12 +142,11 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Parameter '%s' not found in %s '%s'"), *ParamName, *FoundNodeType, *FunctionName)); - E->SetArrayField(TEXT("availableParams"), ParamNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availableParams"), ParamNames); + return; } // Check for dry run @@ -189,7 +181,6 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("functionName"), FunctionName); @@ -199,7 +190,7 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetNumberField(TEXT("connectionsAtRisk"), AffectedPins.Num()); Result->SetArrayField(TEXT("affectedPins"), AffectedPins); - return JsonToString(Result); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Changing param '%s' in %s '%s' of '%s' to %s"), @@ -223,7 +214,6 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) // Serialize the updated entry node state TSharedPtr UpdatedNodeState = SerializeNode(EntryNode); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("functionName"), FunctionName); @@ -236,28 +226,21 @@ FString FBlueprintMCPServer::HandleChangeFunctionParamType(const FString& Body) { Result->SetObjectField(TEXT("updatedNode"), UpdatedNodeState); } - return JsonToString(Result); } // ============================================================ // HandleRemoveFunctionParameter // ============================================================ -FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) +void FBlueprintMCPServer::HandleRemoveFunctionParameter(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString FunctionName = Json->GetStringField(TEXT("functionName")); FString ParamName = Json->GetStringField(TEXT("paramName")); if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName")); } // Load Blueprint @@ -265,7 +248,7 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the entry node @@ -337,12 +320,11 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Function or custom event '%s' not found in Blueprint '%s'"), *FunctionName, *BlueprintName)); - E->SetArrayField(TEXT("availableFunctionsAndEvents"), Available); - return JsonToString(E); + Result->SetArrayField(TEXT("availableFunctionsAndEvents"), Available); + return; } // Find and remove the UserDefinedPin matching paramName @@ -369,12 +351,11 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Parameter '%s' not found in %s '%s'"), *ParamName, *FoundNodeType, *FunctionName)); - E->SetArrayField(TEXT("availableParams"), ParamNames); - return JsonToString(E); + Result->SetArrayField(TEXT("availableParams"), ParamNames); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing param '%s' from %s '%s' in '%s'"), @@ -398,7 +379,6 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Parameter removed, save %s"), bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("functionName"), FunctionName); @@ -406,21 +386,14 @@ FString FBlueprintMCPServer::HandleRemoveFunctionParameter(const FString& Body) Result->SetStringField(TEXT("nodeType"), FoundNodeType); Result->SetStringField(TEXT("nodeId"), EntryNode->NodeGuid.ToString()); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleAddFunctionParameter // ============================================================ -FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) +void FBlueprintMCPServer::HandleAddFunctionParameter(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString FunctionName = Json->GetStringField(TEXT("functionName")); FString ParamName = Json->GetStringField(TEXT("paramName")); @@ -428,7 +401,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) if (BlueprintName.IsEmpty() || FunctionName.IsEmpty() || ParamName.IsEmpty() || ParamType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, functionName, paramName, paramType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, functionName, paramName, paramType")); } // Load Blueprint @@ -436,7 +409,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Resolve param type @@ -444,7 +417,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) FString TypeError; if (!ResolveTypeFromString(ParamType, PinType, TypeError)) { - return MakeErrorJson(TypeError); + return MakeErrorJson(Result, TypeError); } // Find the entry node using 3 strategies @@ -559,12 +532,11 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) FString::Printf(TEXT("%s (event dispatcher)"), *DN.ToString()))); } - TSharedRef ErrorResult = MakeShared(); - ErrorResult->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Function, custom event, or event dispatcher '%s' not found in Blueprint '%s'"), *FunctionName, *BlueprintName)); - ErrorResult->SetArrayField(TEXT("availableFunctions"), AvailFuncs); - return JsonToString(ErrorResult); + Result->SetArrayField(TEXT("availableFunctions"), AvailFuncs); + return; } // Check for duplicate parameter name @@ -572,7 +544,7 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) { if (Existing.IsValid() && Existing->PinName.ToString().Equals(ParamName, ESearchCase::IgnoreCase)) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Parameter '%s' already exists on '%s'"), *ParamName, *FunctionName)); } } @@ -589,7 +561,6 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added parameter '%s' to '%s' in '%s' (saved: %s)"), *ParamName, *FunctionName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("functionName"), FunctionName); @@ -597,5 +568,4 @@ FString FBlueprintMCPServer::HandleAddFunctionParameter(const FString& Body) Result->SetStringField(TEXT("paramType"), ParamType); Result->SetStringField(TEXT("nodeType"), NodeType); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Read.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Read.cpp index def8790f..abacd4d9 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Read.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Read.cpp @@ -24,14 +24,14 @@ // Request handlers // ============================================================ -FString FBlueprintMCPServer::HandleList(const TMap& Params) +void FBlueprintMCPServer::HandleList(const FJsonObject* Json, FJsonObject* Result) { - const FString* Filter = Params.Find(TEXT("filter")); - const FString* ParentClassFilter = Params.Find(TEXT("parentClass")); - const FString* TypeFilter = Params.Find(TEXT("type")); + FString Filter = Json->GetStringField(TEXT("filter")); + FString ParentClassFilter = Json->GetStringField(TEXT("parentClass")); + FString TypeFilter = Json->GetStringField(TEXT("type")); // type: "all" (default), "regular", "level" - bool bIncludeRegular = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("regular"); - bool bIncludeLevel = !TypeFilter || TypeFilter->IsEmpty() || *TypeFilter == TEXT("all") || *TypeFilter == TEXT("level"); + bool bIncludeRegular = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("regular"); + bool bIncludeLevel = TypeFilter.IsEmpty() || TypeFilter == TEXT("all") || TypeFilter == TEXT("level"); TArray> Entries; if (bIncludeRegular) @@ -40,10 +40,10 @@ FString FBlueprintMCPServer::HandleList(const TMap& Params) FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); - if (Filter && !Filter->IsEmpty()) + if (!Filter.IsEmpty()) { - if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) && - !Path.Contains(*Filter, ESearchCase::IgnoreCase)) + if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && + !Path.Contains(Filter, ESearchCase::IgnoreCase)) { continue; } @@ -58,9 +58,9 @@ FString FBlueprintMCPServer::HandleList(const TMap& Params) ParentClass = ParentClass.Mid(DotIndex + 1); } - if (ParentClassFilter && !ParentClassFilter->IsEmpty()) + if (!ParentClassFilter.IsEmpty()) { - if (!ParentClass.Contains(*ParentClassFilter, ESearchCase::IgnoreCase)) + if (!ParentClass.Contains(ParentClassFilter, ESearchCase::IgnoreCase)) { continue; } @@ -80,19 +80,19 @@ FString FBlueprintMCPServer::HandleList(const TMap& Params) FString Name = Asset.AssetName.ToString(); FString Path = Asset.PackageName.ToString(); - if (Filter && !Filter->IsEmpty()) + if (!Filter.IsEmpty()) { - if (!Name.Contains(*Filter, ESearchCase::IgnoreCase) && - !Path.Contains(*Filter, ESearchCase::IgnoreCase)) + if (!Name.Contains(Filter, ESearchCase::IgnoreCase) && + !Path.Contains(Filter, ESearchCase::IgnoreCase)) { continue; } } // No parent class filter for level blueprints - if (ParentClassFilter && !ParentClassFilter->IsEmpty()) + if (!ParentClassFilter.IsEmpty()) { - if (!FString(TEXT("LevelScriptActor")).Contains(*ParentClassFilter, ESearchCase::IgnoreCase)) + if (!FString(TEXT("LevelScriptActor")).Contains(ParentClassFilter, ESearchCase::IgnoreCase)) { continue; } @@ -106,48 +106,47 @@ FString FBlueprintMCPServer::HandleList(const TMap& Params) Entries.Add(MakeShared(Entry)); } - TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("count"), Entries.Num()); Result->SetNumberField(TEXT("total"), AllBlueprintAssets.Num() + AllMapAssets.Num()); Result->SetArrayField(TEXT("blueprints"), Entries); - return JsonToString(Result); } -FString FBlueprintMCPServer::HandleGetBlueprint(const TMap& Params) +void FBlueprintMCPServer::HandleGetBlueprint(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - if (!Name || Name->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + if (Name.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' parameter")); } FString LoadError; - UBlueprint* BP = LoadBlueprintByName(*Name, LoadError); + UBlueprint* BP = LoadBlueprintByName(Name, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } - return JsonToString(SerializeBlueprint(BP)); + TSharedRef Tmp = SerializeBlueprint(BP); + CopyJsonFields(&*Tmp, Result); } -FString FBlueprintMCPServer::HandleGetGraph(const TMap& Params) +void FBlueprintMCPServer::HandleGetGraph(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - const FString* GraphName = Params.Find(TEXT("graph")); - if (!Name || Name->IsEmpty() || !GraphName || GraphName->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + FString GraphName = Json->GetStringField(TEXT("graph")); + if (Name.IsEmpty() || GraphName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' or 'graph' parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' or 'graph' parameter")); } // URL-decode graph name to handle spaces encoded as '+' or '%20' - FString DecodedGraphName = UrlDecode(*GraphName); + FString DecodedGraphName = UrlDecode(GraphName); FString LoadError; - UBlueprint* BP = LoadBlueprintByName(*Name, LoadError); + UBlueprint* BP = LoadBlueprintByName(Name, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } TArray AllGraphs; @@ -160,7 +159,8 @@ FString FBlueprintMCPServer::HandleGetGraph(const TMap& Params TSharedPtr GraphJson = SerializeGraph(Graph); if (GraphJson.IsValid()) { - return JsonToString(GraphJson.ToSharedRef()); + CopyJsonFields(GraphJson.Get(), Result); + return; } } } @@ -174,28 +174,24 @@ FString FBlueprintMCPServer::HandleGetGraph(const TMap& Params GraphNames.Add(MakeShared(Graph->GetName())); } } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); - E->SetArrayField(TEXT("availableGraphs"), GraphNames); - return JsonToString(E); + MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName)); + Result->SetArrayField(TEXT("availableGraphs"), GraphNames); } -FString FBlueprintMCPServer::HandleSearch(const TMap& Params) +void FBlueprintMCPServer::HandleSearch(const FJsonObject* Json, FJsonObject* Result) { - const FString* Query = Params.Find(TEXT("query")); - if (!Query || Query->IsEmpty()) + FString Query = Json->GetStringField(TEXT("query")); + if (Query.IsEmpty()) { - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), TEXT("Missing 'query' parameter")); - return JsonToString(E); + return MakeErrorJson(Result, TEXT("Missing 'query' parameter")); } - const FString* PathFilter = Params.Find(TEXT("path")); + FString PathFilter = Json->GetStringField(TEXT("path")); int32 MaxResults = 50; - if (const FString* M = Params.Find(TEXT("maxResults"))) + if (Json->HasField(TEXT("maxResults"))) { - MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 200); + MaxResults = FMath::Clamp(FCString::Atoi(*Json->GetStringField(TEXT("maxResults"))), 1, 200); } // Build a combined list of all searchable blueprints (regular + level) @@ -236,10 +232,10 @@ FString FBlueprintMCPServer::HandleSearch(const TMap& Params) VarName = VS->GetVarName().ToString(); } - bool bMatch = Title.Contains(*Query, ESearchCase::IgnoreCase) || - (!FuncName.IsEmpty() && FuncName.Contains(*Query, ESearchCase::IgnoreCase)) || - (!EventName.IsEmpty() && EventName.Contains(*Query, ESearchCase::IgnoreCase)) || - (!VarName.IsEmpty() && VarName.Contains(*Query, ESearchCase::IgnoreCase)); + bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) || + (!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) || + (!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) || + (!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase)); if (bMatch) { @@ -264,7 +260,7 @@ FString FBlueprintMCPServer::HandleSearch(const TMap& Params) if (Results.Num() >= MaxResults) break; FString Path = Asset.PackageName.ToString(); - if (PathFilter && !PathFilter->IsEmpty() && !Path.Contains(*PathFilter, ESearchCase::IgnoreCase)) + if (!PathFilter.IsEmpty() && !Path.Contains(PathFilter, ESearchCase::IgnoreCase)) { continue; } @@ -281,7 +277,7 @@ FString FBlueprintMCPServer::HandleSearch(const TMap& Params) if (Results.Num() >= MaxResults) break; FString Path = MapAsset.PackageName.ToString(); - if (PathFilter && !PathFilter->IsEmpty() && !Path.Contains(*PathFilter, ESearchCase::IgnoreCase)) + if (!PathFilter.IsEmpty() && !Path.Contains(PathFilter, ESearchCase::IgnoreCase)) { continue; } @@ -300,32 +296,30 @@ FString FBlueprintMCPServer::HandleSearch(const TMap& Params) } } - TSharedRef Result = MakeShared(); - Result->SetStringField(TEXT("query"), *Query); + Result->SetStringField(TEXT("query"), Query); Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetArrayField(TEXT("results"), Results); - return JsonToString(Result); } // ============================================================ // HandleTestSave — load a Blueprint and save it unmodified (diagnostic) // ============================================================ -FString FBlueprintMCPServer::HandleTestSave(const TMap& Params) +void FBlueprintMCPServer::HandleTestSave(const FJsonObject* Json, FJsonObject* Result) { - const FString* Name = Params.Find(TEXT("name")); - if (!Name || Name->IsEmpty()) + FString Name = Json->GetStringField(TEXT("name")); + if (Name.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'name' query parameter")); + return MakeErrorJson(Result, TEXT("Missing 'name' query parameter")); } - UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), **Name); + UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save requested for '%s'"), *Name); FString LoadError; - UBlueprint* BP = LoadBlueprintByName(*Name, LoadError); + UBlueprint* BP = LoadBlueprintByName(Name, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: test-save — loaded '%s', GeneratedClass=%s"), @@ -335,30 +329,28 @@ FString FBlueprintMCPServer::HandleTestSave(const TMap& Params // Attempt save with NO modifications bool bSaved = SaveBlueprintPackage(BP); - TSharedRef Result = MakeShared(); - Result->SetStringField(TEXT("blueprint"), *Name); + Result->SetStringField(TEXT("blueprint"), Name); Result->SetStringField(TEXT("packagePath"), BP->GetPackage()->GetName()); Result->SetBoolField(TEXT("hasGeneratedClass"), BP->GeneratedClass != nullptr); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleFindReferences — find all Blueprints referencing an asset // ============================================================ -FString FBlueprintMCPServer::HandleFindReferences(const TMap& Params) +void FBlueprintMCPServer::HandleFindReferences(const FJsonObject* Json, FJsonObject* Result) { - const FString* AssetPath = Params.Find(TEXT("assetPath")); - if (!AssetPath || AssetPath->IsEmpty()) + FString AssetPath = Json->GetStringField(TEXT("assetPath")); + if (AssetPath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'assetPath' query parameter")); + return MakeErrorJson(Result, TEXT("Missing 'assetPath' query parameter")); } IAssetRegistry& Registry = *IAssetRegistry::Get(); TArray Referencers; - Registry.GetReferencers(FName(**AssetPath), Referencers); + Registry.GetReferencers(FName(*AssetPath), Referencers); // Build set of known Blueprint package names for filtering TSet BlueprintPackages; @@ -382,36 +374,34 @@ FString FBlueprintMCPServer::HandleFindReferences(const TMap& } } - TSharedRef Result = MakeShared(); - Result->SetStringField(TEXT("assetPath"), **AssetPath); + Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetNumberField(TEXT("totalReferencers"), Referencers.Num()); Result->SetNumberField(TEXT("blueprintReferencerCount"), BPRefs.Num()); Result->SetArrayField(TEXT("blueprintReferencers"), BPRefs); Result->SetNumberField(TEXT("otherReferencerCount"), OtherRefs.Num()); Result->SetArrayField(TEXT("otherReferencers"), OtherRefs); - return JsonToString(Result); } // ============================================================ // HandleSearchByType — find all usages of a type across blueprints // ============================================================ -FString FBlueprintMCPServer::HandleSearchByType(const TMap& Params) +void FBlueprintMCPServer::HandleSearchByType(const FJsonObject* Json, FJsonObject* Result) { - const FString* TypeNamePtr = Params.Find(TEXT("typeName")); - if (!TypeNamePtr || TypeNamePtr->IsEmpty()) + FString TypeNameRaw = Json->GetStringField(TEXT("typeName")); + if (TypeNameRaw.IsEmpty()) { - return MakeErrorJson(TEXT("Missing 'typeName' query parameter")); + return MakeErrorJson(Result, TEXT("Missing 'typeName' query parameter")); } - FString TypeName = UrlDecode(*TypeNamePtr); - const FString* Filter = Params.Find(TEXT("filter")); - FString FilterStr = Filter ? UrlDecode(*Filter) : FString(); + FString TypeName = UrlDecode(TypeNameRaw); + FString FilterRaw = Json->GetStringField(TEXT("filter")); + FString FilterStr = FilterRaw.IsEmpty() ? FString() : UrlDecode(FilterRaw); int32 MaxResults = 200; - if (const FString* M = Params.Find(TEXT("maxResults"))) + if (Json->HasField(TEXT("maxResults"))) { - MaxResults = FMath::Clamp(FCString::Atoi(**M), 1, 500); + MaxResults = FMath::Clamp(FCString::Atoi(*Json->GetStringField(TEXT("maxResults"))), 1, 500); } // Strip F/E/U prefix for comparison @@ -636,9 +626,7 @@ FString FBlueprintMCPServer::HandleSearchByType(const TMap& Pa SearchOneBlueprint(MapName, Path, LevelBP, true); } - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("typeName"), TypeName); Result->SetNumberField(TEXT("resultCount"), Results.Num()); Result->SetArrayField(TEXT("results"), Results); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Snapshot.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Snapshot.cpp index bf570152..8b8b082b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Snapshot.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Snapshot.cpp @@ -254,18 +254,12 @@ bool FBlueprintMCPServer::LoadSnapshotFromDisk(const FString& SnapshotId, FGraph // HandleSnapshotGraph // ============================================================ -FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body) +void FBlueprintMCPServer::HandleSnapshotGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } FString GraphFilter; @@ -276,7 +270,7 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating snapshot for blueprint '%s'"), *BlueprintName); @@ -305,7 +299,7 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body) if (GraphsToCapture.Num() == 0 && !GraphFilter.IsEmpty()) { - return MakeErrorJson(FString::Printf(TEXT("Graph '%s' not found in blueprint '%s'"), *GraphFilter, *BlueprintName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in blueprint '%s'"), *GraphFilter, *BlueprintName)); } int32 TotalConnections = 0; @@ -336,32 +330,24 @@ FString FBlueprintMCPServer::HandleSnapshotGraph(const FString& Body) *Snapshot.SnapshotId, GraphsToCapture.Num(), TotalConnections); // Build response - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetStringField(TEXT("snapshotId"), Snapshot.SnapshotId); Result->SetStringField(TEXT("blueprint"), BP->GetName()); Result->SetArrayField(TEXT("graphs"), GraphSummaries); Result->SetNumberField(TEXT("totalConnections"), TotalConnections); - return JsonToString(Result); } // ============================================================ // HandleDiffGraph // ============================================================ -FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body) +void FBlueprintMCPServer::HandleDiffGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString SnapshotId = Json->GetStringField(TEXT("snapshotId")); if (BlueprintName.IsEmpty() || SnapshotId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, snapshotId")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, snapshotId")); } FString GraphFilter; @@ -374,7 +360,7 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body) { if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot)) { - return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); } SnapshotPtr = &LoadedSnapshot; } @@ -384,7 +370,7 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Diffing blueprint '%s' against snapshot '%s'"), *BlueprintName, *SnapshotId); @@ -545,7 +531,6 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body) } // Build result - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetStringField(TEXT("blueprint"), BP->GetName()); Result->SetStringField(TEXT("snapshotId"), SnapshotId); @@ -563,27 +548,19 @@ FString FBlueprintMCPServer::HandleDiffGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Diff complete — %d severed, %d new, %d type changes, %d missing nodes"), SeveredArr.Num(), NewConnsArr.Num(), TypeChangesArr.Num(), MissingNodesArr.Num()); - - return JsonToString(Result); } // ============================================================ // HandleRestoreGraph // ============================================================ -FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body) +void FBlueprintMCPServer::HandleRestoreGraph(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString SnapshotId = Json->GetStringField(TEXT("snapshotId")); if (BlueprintName.IsEmpty() || SnapshotId.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, snapshotId")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, snapshotId")); } FString GraphFilter; @@ -602,7 +579,7 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body) { if (!LoadSnapshotFromDisk(SnapshotId, LoadedSnapshot)) { - return MakeErrorJson(FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); + return MakeErrorJson(Result, FString::Printf(TEXT("Snapshot '%s' not found in memory or on disk"), *SnapshotId)); } SnapshotPtr = &LoadedSnapshot; } @@ -612,7 +589,7 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Restoring connections from snapshot '%s' for blueprint '%s' (dryRun=%s)"), @@ -775,28 +752,20 @@ FString FBlueprintMCPServer::HandleRestoreGraph(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Restore complete — %d reconnected, %d failed, saved=%s"), Reconnected, Failed, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("status"), TEXT("ok")); Result->SetNumberField(TEXT("reconnected"), Reconnected); Result->SetNumberField(TEXT("failed"), Failed); Result->SetArrayField(TEXT("details"), DetailsArr); Result->SetBoolField(TEXT("saved"), bSaved); Result->SetBoolField(TEXT("dryRun"), bDryRun); - return JsonToString(Result); } // ============================================================ // HandleFindDisconnectedPins // ============================================================ -FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body) +void FBlueprintMCPServer::HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName; Json->TryGetStringField(TEXT("blueprint"), BlueprintName); @@ -808,7 +777,7 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body) if (BlueprintName.IsEmpty() && PathFilter.IsEmpty() && SnapshotId.IsEmpty()) { - return MakeErrorJson(TEXT("Provide at least one of: blueprint, filter, or snapshotId")); + return MakeErrorJson(Result, TEXT("Provide at least one of: blueprint, filter, or snapshotId")); } // Optionally load snapshot for definite-break detection @@ -1086,7 +1055,6 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: FindDisconnectedPins complete — %d HIGH, %d MEDIUM, %d total across %d blueprints"), HighCount, MediumCount, ResultsArr.Num(), BlueprintsScanned); - TSharedRef Result = MakeShared(); Result->SetArrayField(TEXT("results"), ResultsArr); TSharedRef Summary = MakeShared(); @@ -1095,26 +1063,18 @@ FString FBlueprintMCPServer::HandleFindDisconnectedPins(const FString& Body) Summary->SetNumberField(TEXT("total"), ResultsArr.Num()); Summary->SetNumberField(TEXT("blueprintsScanned"), BlueprintsScanned); Result->SetObjectField(TEXT("summary"), Summary); - - return JsonToString(Result); } // ============================================================ // HandleAnalyzeRebuildImpact // ============================================================ -FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body) +void FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString ModuleName = Json->GetStringField(TEXT("moduleName")); if (ModuleName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: moduleName")); + return MakeErrorJson(Result, TEXT("Missing required field: moduleName")); } // Optional struct name filter @@ -1359,7 +1319,6 @@ FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body) AffectedBlueprints.Num(), TotalBreakMakeNodes, TotalConnectionsAtRisk); // Build response - TSharedRef Result = MakeShared(); Result->SetStringField(TEXT("moduleName"), ModuleName); Result->SetArrayField(TEXT("typesFound"), TypesFoundArr); @@ -1384,6 +1343,4 @@ FString FBlueprintMCPServer::HandleAnalyzeRebuildImpact(const FString& Body) Summary->SetNumberField(TEXT("totalBreakMakeNodes"), TotalBreakMakeNodes); Summary->SetNumberField(TEXT("totalConnectionsAtRisk"), TotalConnectionsAtRisk); Result->SetObjectField(TEXT("summary"), Summary); - - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_UserTypes.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_UserTypes.cpp index 83ce5403..cdac8ac1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_UserTypes.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_UserTypes.cpp @@ -20,18 +20,12 @@ // HandleCreateStruct — create a new UserDefinedStruct asset // ============================================================ -FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body) +void FBlueprintMCPServer::HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); if (AssetPath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/S_MyStruct')")); + return MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/S_MyStruct')")); } // Split path into package path and asset name @@ -44,12 +38,12 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body) } else { - return MakeErrorJson(TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')")); + return MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')")); } if (AssetName.IsEmpty()) { - return MakeErrorJson(TEXT("Invalid asset name in assetPath")); + return MakeErrorJson(Result, TEXT("Invalid asset name in assetPath")); } // Check if asset already exists @@ -57,7 +51,7 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body) FAssetData ExistingAsset = ARM.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath + TEXT(".") + AssetName)); if (ExistingAsset.IsValid()) { - return MakeErrorJson(FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath)); + return MakeErrorJson(Result, FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath)); } // Create the struct using the AssetTools factory @@ -69,13 +63,13 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body) if (!NewAsset) { - return MakeErrorJson(TEXT("Failed to create UserDefinedStruct asset")); + return MakeErrorJson(Result, TEXT("Failed to create UserDefinedStruct asset")); } UUserDefinedStruct* NewStruct = Cast(NewAsset); if (!NewStruct) { - return MakeErrorJson(TEXT("Created asset is not a UserDefinedStruct")); + return MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedStruct")); } // Add properties if specified @@ -139,31 +133,23 @@ FString FBlueprintMCPServer::HandleCreateStruct(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedStruct '%s' with %d properties, save %s"), *AssetPath, PropsAdded, bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("assetName"), AssetName); Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleCreateEnum — create a new UserDefinedEnum asset // ============================================================ -FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body) +void FBlueprintMCPServer::HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); if (AssetPath.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/E_MyEnum')")); + return MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/E_MyEnum')")); } // Split path @@ -176,19 +162,19 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body) } else { - return MakeErrorJson(TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')")); + return MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')")); } if (AssetName.IsEmpty()) { - return MakeErrorJson(TEXT("Invalid asset name in assetPath")); + return MakeErrorJson(Result, TEXT("Invalid asset name in assetPath")); } // Get values const TArray>* ValuesArray = nullptr; if (!Json->TryGetArrayField(TEXT("values"), ValuesArray) || !ValuesArray || ValuesArray->Num() == 0) { - return MakeErrorJson(TEXT("Missing or empty required field: values (array of strings)")); + return MakeErrorJson(Result, TEXT("Missing or empty required field: values (array of strings)")); } TArray EnumValues; @@ -199,7 +185,7 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body) } if (EnumValues.Num() == 0) { - return MakeErrorJson(TEXT("No valid enum values provided")); + return MakeErrorJson(Result, TEXT("No valid enum values provided")); } // Create the enum using AssetTools @@ -211,13 +197,13 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body) if (!NewAsset) { - return MakeErrorJson(TEXT("Failed to create UserDefinedEnum asset")); + return MakeErrorJson(Result, TEXT("Failed to create UserDefinedEnum asset")); } UUserDefinedEnum* NewEnum = Cast(NewAsset); if (!NewEnum) { - return MakeErrorJson(TEXT("Created asset is not a UserDefinedEnum")); + return MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedEnum")); } // Add enum values — UUserDefinedEnum starts with a MAX value. @@ -241,34 +227,26 @@ FString FBlueprintMCPServer::HandleCreateEnum(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedEnum '%s' with %d values, save %s"), *AssetPath, EnumValues.Num(), bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("assetName"), AssetName); Result->SetNumberField(TEXT("valueCount"), EnumValues.Num()); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleAddStructProperty — add a property to UserDefinedStruct // ============================================================ -FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body) +void FBlueprintMCPServer::HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); FString PropName = Json->GetStringField(TEXT("name")); FString PropType = Json->GetStringField(TEXT("type")); if (AssetPath.IsEmpty() || PropName.IsEmpty() || PropType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: assetPath, name, type")); + return MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name, type")); } // Find the struct @@ -281,7 +259,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body) } if (!Struct) { - return MakeErrorJson(FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath)); + return MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath)); } // Resolve type @@ -289,7 +267,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body) FString TypeError; if (!ResolveTypeFromString(PropType, PinType, TypeError)) { - return MakeErrorJson(FString::Printf(TEXT("Cannot resolve type '%s': %s"), *PropType, *TypeError)); + return MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *PropType, *TypeError)); } // Snapshot existing GUIDs so we can find the newly added one @@ -302,7 +280,7 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body) bool bAdded = FStructureEditorUtils::AddVariable(Struct, PinType); if (!bAdded) { - return MakeErrorJson(TEXT("Failed to add property to struct")); + return MakeErrorJson(Result, TEXT("Failed to add property to struct")); } // Find the new variable by diffing GUID sets and rename it @@ -325,33 +303,25 @@ FString FBlueprintMCPServer::HandleAddStructProperty(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added property '%s' (%s) to struct '%s', save %s"), *PropName, *PropType, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("propertyName"), PropName); Result->SetStringField(TEXT("propertyType"), PropType); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRemoveStructProperty — remove a property from UserDefinedStruct // ============================================================ -FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body) +void FBlueprintMCPServer::HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString AssetPath = Json->GetStringField(TEXT("assetPath")); FString PropName = Json->GetStringField(TEXT("name")); if (AssetPath.IsEmpty() || PropName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: assetPath, name")); + return MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name")); } // Find the struct @@ -363,7 +333,7 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body) } if (!Struct) { - return MakeErrorJson(FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath)); + return MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath)); } // Find the property GUID by name @@ -387,16 +357,15 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body) { AvailProps.Add(MakeShared(Var.FriendlyName)); } - TSharedRef E = MakeShared(); - E->SetStringField(TEXT("error"), FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *PropName, *AssetPath)); - E->SetArrayField(TEXT("availableProperties"), AvailProps); - return JsonToString(E); + MakeErrorJson(Result, FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *PropName, *AssetPath)); + Result->SetArrayField(TEXT("availableProperties"), AvailProps); + return; } bool bRemoved = FStructureEditorUtils::RemoveVariable(Struct, TargetGuid); if (!bRemoved) { - return MakeErrorJson(FString::Printf(TEXT("Failed to remove property '%s'"), *PropName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Failed to remove property '%s'"), *PropName)); } // Save @@ -409,10 +378,8 @@ FString FBlueprintMCPServer::HandleRemoveStructProperty(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed property '%s' from struct '%s', save %s"), *PropName, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("assetPath"), AssetPath); Result->SetStringField(TEXT("removedProperty"), PropName); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Validation.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Validation.cpp index 82e8acb5..53b1eac1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Validation.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Validation.cpp @@ -175,18 +175,12 @@ static TSharedRef ValidateSingleBlueprint(UBlueprint* BP, const FSt // HandleValidateBlueprint — compile without saving, report errors + captured log messages // ============================================================ -FString FBlueprintMCPServer::HandleValidateBlueprint(const FString& Body) +void FBlueprintMCPServer::HandleValidateBlueprint(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); if (BlueprintName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required field: blueprint")); + return MakeErrorJson(Result, TEXT("Missing required field: blueprint")); } // Load Blueprint @@ -194,36 +188,25 @@ FString FBlueprintMCPServer::HandleValidateBlueprint(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating blueprint '%s'"), *BlueprintName); - TSharedRef Result = ValidateSingleBlueprint(BP, BlueprintName); - return JsonToString(Result); + TSharedRef ValidationResult = ValidateSingleBlueprint(BP, BlueprintName); + CopyJsonFields(&*ValidationResult, Result); } // ============================================================ // HandleValidateAllBlueprints — bulk validation // ============================================================ -FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) +void FBlueprintMCPServer::HandleValidateAllBlueprints(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - // Body is optional — empty body means validate all - - FString Filter; - bool bCountOnly = false; - int32 Offset = 0; - int32 Limit = 0; - - if (Json.IsValid()) - { - Filter = Json->GetStringField(TEXT("filter")); - bCountOnly = Json->GetBoolField(TEXT("countOnly")); - Offset = (int32)Json->GetNumberField(TEXT("offset")); - Limit = (int32)Json->GetNumberField(TEXT("limit")); - } + FString Filter = Json->GetStringField(TEXT("filter")); + bool bCountOnly = Json->GetBoolField(TEXT("countOnly")); + int32 Offset = (int32)Json->GetNumberField(TEXT("offset")); + int32 Limit = (int32)Json->GetNumberField(TEXT("limit")); // First pass: collect matching asset indices (string comparisons only, no GetAsset()) TArray MatchingIndices; @@ -247,13 +230,12 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) // countOnly: return count without compiling anything if (bCountOnly) { - TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("totalMatching"), TotalMatching); if (!Filter.IsEmpty()) { Result->SetStringField(TEXT("filter"), Filter); } - return JsonToString(Result); + return; } // Compute range @@ -284,13 +266,13 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) TotalChecked++; - TSharedRef Result = ValidateSingleBlueprint(BP, AssetName); + TSharedRef ValidationResult = ValidateSingleBlueprint(BP, AssetName); - bool bValid = Result->GetBoolField(TEXT("isValid")); - int32 Errors = (int32)Result->GetNumberField(TEXT("errorCount")); - int32 Warnings = (int32)Result->GetNumberField(TEXT("warningCount")); + bool bValid = ValidationResult->GetBoolField(TEXT("isValid")); + int32 Errors = (int32)ValidationResult->GetNumberField(TEXT("errorCount")); + int32 Warnings = (int32)ValidationResult->GetNumberField(TEXT("warningCount")); - if (Result->HasField(TEXT("compileWarning"))) + if (ValidationResult->HasField(TEXT("compileWarning"))) { TotalCrashed++; } @@ -303,8 +285,8 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) { TotalFailed++; // Include path for context in bulk results - Result->SetStringField(TEXT("path"), PackagePath); - FailedArr.Add(MakeShared(Result)); + ValidationResult->SetStringField(TEXT("path"), PackagePath); + FailedArr.Add(MakeShared(ValidationResult)); } // Log progress every 50 blueprints @@ -318,7 +300,6 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Bulk validation complete — %d checked, %d passed, %d failed, %d crashed"), TotalChecked, TotalPassed, TotalFailed, TotalCrashed); - TSharedRef Result = MakeShared(); Result->SetNumberField(TEXT("totalMatching"), TotalMatching); Result->SetNumberField(TEXT("totalChecked"), TotalChecked); Result->SetNumberField(TEXT("totalPassed"), TotalPassed); @@ -332,5 +313,4 @@ FString FBlueprintMCPServer::HandleValidateAllBlueprints(const FString& Body) { Result->SetStringField(TEXT("filter"), Filter); } - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Variables.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Variables.cpp index 7ef5c39f..edf1d925 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Variables.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPHandlers_Variables.cpp @@ -15,14 +15,8 @@ // HandleChangeVariableType — change a Blueprint member variable's type // ============================================================ -FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) +void FBlueprintMCPServer::HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString VariableName = Json->GetStringField(TEXT("variable")); FString NewTypeName = Json->GetStringField(TEXT("newType")); @@ -34,7 +28,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || NewTypeName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, variable, newType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable, newType")); } // Load Blueprint @@ -42,7 +36,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Verify variable exists @@ -57,7 +51,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) } if (!bVarFound) { - return MakeErrorJson(FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName)); + return MakeErrorJson(Result, FString::Printf(TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName)); } // Build the new pin type using shared resolver @@ -75,7 +69,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) FString TypeError; if (!ResolveTypeFromString(ResolveInput, NewPinType, TypeError)) { - return MakeErrorJson(TypeError); + return MakeErrorJson(Result, TypeError); } // Derive typeCategory from the resolved pin type for the response @@ -170,7 +164,6 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) if (bDryRun) { - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("dryRun"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("variable"), VariableName); @@ -178,7 +171,7 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) Result->SetStringField(TEXT("typeCategory"), TypeCategory); Result->SetNumberField(TEXT("affectedNodeCount"), AffectedNodes.Num()); Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes); - return JsonToString(Result); + return; } // Directly modify the variable type in the description array. @@ -211,7 +204,6 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) } } - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("variable"), VariableName); @@ -220,28 +212,21 @@ FString FBlueprintMCPServer::HandleChangeVariableType(const FString& Body) Result->SetBoolField(TEXT("saved"), bSaved); Result->SetObjectField(TEXT("updatedVariable"), UpdatedVar); Result->SetArrayField(TEXT("affectedNodes"), AffectedNodes); - return JsonToString(Result); } // ============================================================ // HandleAddVariable — add a new member variable to a Blueprint // ============================================================ -FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) +void FBlueprintMCPServer::HandleAddVariable(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString VariableName = Json->GetStringField(TEXT("variableName")); FString VariableType = Json->GetStringField(TEXT("variableType")); if (BlueprintName.IsEmpty() || VariableName.IsEmpty() || VariableType.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, variableName, variableType")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName, variableType")); } FString Category; @@ -267,7 +252,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Check for duplicate variable name @@ -276,7 +261,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) { if (Var.VarName == VarFName) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Variable '%s' already exists in Blueprint '%s'"), *VariableName, *BlueprintName)); } } @@ -286,7 +271,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) FString TypeError; if (!ResolveTypeFromString(VariableType, PinType, TypeError)) { - return MakeErrorJson(TypeError); + return MakeErrorJson(Result, TypeError); } // Set container type for arrays @@ -302,7 +287,7 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) bool bSuccess = FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue); if (!bSuccess) { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("FBlueprintEditorUtils::AddMemberVariable failed for '%s'"), *VariableName)); } @@ -318,7 +303,6 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added variable '%s' to '%s' (saved: %s)"), *VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("variableName"), VariableName); @@ -329,27 +313,20 @@ FString FBlueprintMCPServer::HandleAddVariable(const FString& Body) } Result->SetBoolField(TEXT("isArray"), bIsArray); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleRemoveVariable — remove a member variable from a Blueprint // ============================================================ -FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body) +void FBlueprintMCPServer::HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString VariableName = Json->GetStringField(TEXT("variableName")); if (BlueprintName.IsEmpty() || VariableName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, variableName")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variableName")); } // Load Blueprint @@ -357,7 +334,7 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find variable by name (case-insensitive) @@ -382,11 +359,10 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body) AvailVars.Add(MakeShared(Var.VarName.ToString())); } - TSharedRef ErrorResult = MakeShared(); - ErrorResult->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName)); - ErrorResult->SetArrayField(TEXT("availableVariables"), AvailVars); - return JsonToString(ErrorResult); + Result->SetArrayField(TEXT("availableVariables"), AvailVars); + return; } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing variable '%s' from Blueprint '%s'"), @@ -401,32 +377,24 @@ FString FBlueprintMCPServer::HandleRemoveVariable(const FString& Body) UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed variable '%s' from '%s' (saved: %s)"), *VariableName, *BlueprintName, bSaved ? TEXT("true") : TEXT("false")); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("variableName"), VariableName); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } // ============================================================ // HandleSetVariableMetadata — set variable properties (category, tooltip, replication, etc.) // ============================================================ -FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) +void FBlueprintMCPServer::HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result) { - TSharedPtr Json = ParseBodyJson(Body); - if (!Json.IsValid()) - { - return MakeErrorJson(TEXT("Invalid JSON body")); - } - FString BlueprintName = Json->GetStringField(TEXT("blueprint")); FString VariableName = Json->GetStringField(TEXT("variable")); if (BlueprintName.IsEmpty() || VariableName.IsEmpty()) { - return MakeErrorJson(TEXT("Missing required fields: blueprint, variable")); + return MakeErrorJson(Result, TEXT("Missing required fields: blueprint, variable")); } // Load Blueprint @@ -434,7 +402,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) UBlueprint* BP = LoadBlueprintByName(BlueprintName, LoadError); if (!BP) { - return MakeErrorJson(LoadError); + return MakeErrorJson(Result, LoadError); } // Find the variable @@ -456,11 +424,10 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) { AvailableVars.Add(MakeShared(Var.VarName.ToString())); } - TSharedRef ErrResult = MakeShared(); - ErrResult->SetStringField(TEXT("error"), FString::Printf( + MakeErrorJson(Result, FString::Printf( TEXT("Variable '%s' not found in Blueprint '%s'"), *VariableName, *BlueprintName)); - ErrResult->SetArrayField(TEXT("availableVariables"), AvailableVars); - return JsonToString(ErrResult); + Result->SetArrayField(TEXT("availableVariables"), AvailableVars); + return; } TArray> Changes; @@ -521,7 +488,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid replication value '%s'. Valid: none, replicated, repNotify"), *ReplicationStr)); } @@ -590,7 +557,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) } else { - return MakeErrorJson(FString::Printf( + return MakeErrorJson(Result, FString::Printf( TEXT("Invalid editability value '%s'. Valid: editAnywhere, editDefaultsOnly, editInstanceOnly, none"), *Editability)); } @@ -603,7 +570,7 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) if (Changes.Num() == 0) { - return MakeErrorJson(TEXT("No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability")); + return MakeErrorJson(Result, TEXT("No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability")); } UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetVariableMetadata on '%s.%s' — %d field(s) changed"), @@ -612,11 +579,9 @@ FString FBlueprintMCPServer::HandleSetVariableMetadata(const FString& Body) FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); bool bSaved = SaveBlueprintPackage(BP); - TSharedRef Result = MakeShared(); Result->SetBoolField(TEXT("success"), true); Result->SetStringField(TEXT("blueprint"), BlueprintName); Result->SetStringField(TEXT("variable"), VariableName); Result->SetArrayField(TEXT("changes"), Changes); Result->SetBoolField(TEXT("saved"), bSaved); - return JsonToString(Result); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPServer.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPServer.cpp index acabba70..123acad8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPServer.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintMCPServer.cpp @@ -210,6 +210,20 @@ FString FBlueprintMCPServer::MakeErrorJson(const FString& Message) return JsonToString(E); } +void FBlueprintMCPServer::MakeErrorJson(FJsonObject* Result, const FString& Message) +{ + Result->Values.Empty(); + Result->SetStringField(TEXT("error"), Message); +} + +void FBlueprintMCPServer::CopyJsonFields(const FJsonObject* Source, FJsonObject* Dest) +{ + for (const auto& KV : Source->Values) + { + Dest->SetField(KV.Key, KV.Value); + } +} + FString FBlueprintMCPServer::UrlDecode(const FString& EncodedString) { FString Result; @@ -547,9 +561,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode) FHttpRequestHandler::CreateLambda( [this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete) { - FString Resp = HandleList(Request.QueryParams); + TSharedRef Params = MakeShared(); + for (const auto& KV : Request.QueryParams) + { + Params->SetStringField(KV.Key, KV.Value); + } + TSharedRef ListResult = MakeShared(); + HandleList(&*Params, &*ListResult); TUniquePtr R = FHttpServerResponse::Create( - Resp, TEXT("application/json")); + JsonToString(ListResult), TEXT("application/json")); OnComplete(MoveTemp(R)); return true; })); @@ -693,9 +713,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode) FHttpRequestHandler::CreateLambda( [this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete) { - FString Resp = HandleListMaterials(Request.QueryParams); + TSharedRef Params = MakeShared(); + for (const auto& KV : Request.QueryParams) + { + Params->SetStringField(KV.Key, KV.Value); + } + TSharedRef ListResult = MakeShared(); + HandleListMaterials(&*Params, &*ListResult); TUniquePtr R = FHttpServerResponse::Create( - Resp, TEXT("application/json")); + JsonToString(ListResult), TEXT("application/json")); OnComplete(MoveTemp(R)); return true; })); @@ -743,9 +769,15 @@ bool FBlueprintMCPServer::Start(int32 InPort, bool bEditorMode) FHttpRequestHandler::CreateLambda( [this](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete) { - FString Resp = HandleListMaterialFunctions(Request.QueryParams); + TSharedRef Params = MakeShared(); + for (const auto& KV : Request.QueryParams) + { + Params->SetStringField(KV.Key, KV.Value); + } + TSharedRef ListResult = MakeShared(); + HandleListMaterialFunctions(&*Params, &*ListResult); TUniquePtr R = FHttpServerResponse::Create( - Resp, TEXT("application/json")); + JsonToString(ListResult), TEXT("application/json")); OnComplete(MoveTemp(R)); return true; })); @@ -852,7 +884,23 @@ bool FBlueprintMCPServer::ProcessOneRequest() return false; } - FString Response; + // Build Params JSON: either from POST body or from GET query params + TSharedPtr Params; + if (!Req->Body.IsEmpty()) + { + Params = ParseBodyJson(Req->Body); + } + if (!Params.IsValid()) + { + Params = MakeShared(); + for (const auto& KV : Req->QueryParams) + { + Params->SetStringField(KV.Key, KV.Value); + } + } + + TSharedRef Result = MakeShared(); + if (FRequestHandler* Handler = HandlerMap.Find(Req->Endpoint)) { // Wrap mutation endpoints in an undo transaction so users can Ctrl+Z @@ -862,7 +910,7 @@ bool FBlueprintMCPServer::ProcessOneRequest() GEditor->BeginTransaction(FText::FromString(FString::Printf(TEXT("BlueprintMCP: %s"), *Req->Endpoint))); } - Response = (*Handler)(Req->QueryParams, Req->Body); + (*Handler)(Params.Get(), &*Result); if (bIsMutation && GEditor) { @@ -871,10 +919,11 @@ bool FBlueprintMCPServer::ProcessOneRequest() } else { - Response = MakeErrorJson(FString::Printf(TEXT("Unknown endpoint: %s"), *Req->Endpoint)); + MakeErrorJson(&*Result, FString::Printf(TEXT("Unknown endpoint: %s"), *Req->Endpoint)); } // Send the response back via the HTTP callback (non-blocking) + FString Response = JsonToString(Result); TUniquePtr HttpResp = FHttpServerResponse::Create( Response, TEXT("application/json")); Req->OnComplete(MoveTemp(HttpResp)); @@ -946,121 +995,116 @@ void FBlueprintMCPServer::RegisterHandlers() TEXT("setStateAnimation"), }; - // GET handlers (use QueryParams, ignore Body) - HandlerMap.Add(TEXT("blueprint"), [this](const TMap& P, const FString&) { return HandleGetBlueprint(P); }); - HandlerMap.Add(TEXT("graph"), [this](const TMap& P, const FString&) { return HandleGetGraph(P); }); - HandlerMap.Add(TEXT("search"), [this](const TMap& P, const FString&) { return HandleSearch(P); }); - HandlerMap.Add(TEXT("references"), [this](const TMap& P, const FString&) { return HandleFindReferences(P); }); - HandlerMap.Add(TEXT("testSave"), [this](const TMap& P, const FString&) { return HandleTestSave(P); }); - HandlerMap.Add(TEXT("searchByType"), [this](const TMap& P, const FString&) { return HandleSearchByType(P); }); + // All handlers have uniform signature: void(const FJsonObject&, FJsonObject&) + auto H = [this](const TCHAR* Name, void(FBlueprintMCPServer::*Fn)(const FJsonObject*, FJsonObject*)) + { + HandlerMap.Add(Name, [this, Fn](const FJsonObject* Json, FJsonObject* Result) { (this->*Fn)(Json, Result); }); + }; - // Rescan handler (game thread, no body needed) - HandlerMap.Add(TEXT("rescan"), [this](const TMap&, const FString&) { return HandleRescan(); }); - - // POST handlers (use Body, ignore QueryParams) - HandlerMap.Add(TEXT("replaceFunctionCalls"), [this](const TMap&, const FString& B) { return HandleReplaceFunctionCalls(B); }); - HandlerMap.Add(TEXT("changeVariableType"), [this](const TMap&, const FString& B) { return HandleChangeVariableType(B); }); - HandlerMap.Add(TEXT("changeFunctionParamType"), [this](const TMap&, const FString& B) { return HandleChangeFunctionParamType(B); }); - HandlerMap.Add(TEXT("removeFunctionParameter"), [this](const TMap&, const FString& B) { return HandleRemoveFunctionParameter(B); }); - HandlerMap.Add(TEXT("deleteAsset"), [this](const TMap&, const FString& B) { return HandleDeleteAsset(B); }); - HandlerMap.Add(TEXT("connectPins"), [this](const TMap&, const FString& B) { return HandleConnectPins(B); }); - HandlerMap.Add(TEXT("disconnectPin"), [this](const TMap&, const FString& B) { return HandleDisconnectPin(B); }); - HandlerMap.Add(TEXT("refreshAllNodes"), [this](const TMap&, const FString& B) { return HandleRefreshAllNodes(B); }); - HandlerMap.Add(TEXT("setPinDefault"), [this](const TMap&, const FString& B) { return HandleSetPinDefault(B); }); - HandlerMap.Add(TEXT("moveNode"), [this](const TMap&, const FString& B) { return HandleMoveNode(B); }); - HandlerMap.Add(TEXT("getNodeComment"), [this](const TMap&, const FString& B) { return HandleGetNodeComment(B); }); - HandlerMap.Add(TEXT("setNodeComment"), [this](const TMap&, const FString& B) { return HandleSetNodeComment(B); }); - HandlerMap.Add(TEXT("getPinInfo"), [this](const TMap&, const FString& B) { return HandleGetPinInfo(B); }); - HandlerMap.Add(TEXT("checkPinCompatibility"), [this](const TMap&, const FString& B) { return HandleCheckPinCompatibility(B); }); - HandlerMap.Add(TEXT("listClasses"), [this](const TMap&, const FString& B) { return HandleListClasses(B); }); - HandlerMap.Add(TEXT("listFunctions"), [this](const TMap&, const FString& B) { return HandleListFunctions(B); }); - HandlerMap.Add(TEXT("listProperties"), [this](const TMap&, const FString& B) { return HandleListProperties(B); }); - HandlerMap.Add(TEXT("changeStructNodeType"), [this](const TMap&, const FString& B) { return HandleChangeStructNodeType(B); }); - HandlerMap.Add(TEXT("deleteNode"), [this](const TMap&, const FString& B) { return HandleDeleteNode(B); }); - HandlerMap.Add(TEXT("duplicateNodes"), [this](const TMap&, const FString& B) { return HandleDuplicateNodes(B); }); - HandlerMap.Add(TEXT("validateBlueprint"), [this](const TMap&, const FString& B) { return HandleValidateBlueprint(B); }); - HandlerMap.Add(TEXT("validateAllBlueprints"), [this](const TMap&, const FString& B) { return HandleValidateAllBlueprints(B); }); - HandlerMap.Add(TEXT("addNode"), [this](const TMap&, const FString& B) { return HandleAddNode(B); }); - HandlerMap.Add(TEXT("searchNodeActions"), [this](const TMap&, const FString& B) { return HandleSearchNodeActions(B); }); - HandlerMap.Add(TEXT("spawnNode"), [this](const TMap&, const FString& B) { return HandleSpawnNode(B); }); - HandlerMap.Add(TEXT("renameAsset"), [this](const TMap&, const FString& B) { return HandleRenameAsset(B); }); - HandlerMap.Add(TEXT("reparentBlueprint"), [this](const TMap&, const FString& B) { return HandleReparentBlueprint(B); }); - HandlerMap.Add(TEXT("setBlueprintDefault"), [this](const TMap&, const FString& B) { return HandleSetBlueprintDefault(B); }); - HandlerMap.Add(TEXT("createBlueprint"), [this](const TMap&, const FString& B) { return HandleCreateBlueprint(B); }); - HandlerMap.Add(TEXT("createGraph"), [this](const TMap&, const FString& B) { return HandleCreateGraph(B); }); - HandlerMap.Add(TEXT("deleteGraph"), [this](const TMap&, const FString& B) { return HandleDeleteGraph(B); }); - HandlerMap.Add(TEXT("renameGraph"), [this](const TMap&, const FString& B) { return HandleRenameGraph(B); }); - HandlerMap.Add(TEXT("addVariable"), [this](const TMap&, const FString& B) { return HandleAddVariable(B); }); - HandlerMap.Add(TEXT("removeVariable"), [this](const TMap&, const FString& B) { return HandleRemoveVariable(B); }); - HandlerMap.Add(TEXT("setVariableMetadata"), [this](const TMap&, const FString& B) { return HandleSetVariableMetadata(B); }); - HandlerMap.Add(TEXT("addInterface"), [this](const TMap&, const FString& B) { return HandleAddInterface(B); }); - HandlerMap.Add(TEXT("removeInterface"), [this](const TMap&, const FString& B) { return HandleRemoveInterface(B); }); - HandlerMap.Add(TEXT("listInterfaces"), [this](const TMap&, const FString& B) { return HandleListInterfaces(B); }); - HandlerMap.Add(TEXT("addEventDispatcher"), [this](const TMap&, const FString& B) { return HandleAddEventDispatcher(B); }); - HandlerMap.Add(TEXT("listEventDispatchers"), [this](const TMap&, const FString& B) { return HandleListEventDispatchers(B); }); - HandlerMap.Add(TEXT("addFunctionParameter"), [this](const TMap&, const FString& B) { return HandleAddFunctionParameter(B); }); - HandlerMap.Add(TEXT("addComponent"), [this](const TMap&, const FString& B) { return HandleAddComponent(B); }); - HandlerMap.Add(TEXT("removeComponent"), [this](const TMap&, const FString& B) { return HandleRemoveComponent(B); }); - HandlerMap.Add(TEXT("listComponents"), [this](const TMap&, const FString& B) { return HandleListComponents(B); }); - HandlerMap.Add(TEXT("snapshotGraph"), [this](const TMap&, const FString& B) { return HandleSnapshotGraph(B); }); - HandlerMap.Add(TEXT("diffGraph"), [this](const TMap&, const FString& B) { return HandleDiffGraph(B); }); - HandlerMap.Add(TEXT("restoreGraph"), [this](const TMap&, const FString& B) { return HandleRestoreGraph(B); }); - HandlerMap.Add(TEXT("findDisconnectedPins"), [this](const TMap&, const FString& B) { return HandleFindDisconnectedPins(B); }); - HandlerMap.Add(TEXT("analyzeRebuildImpact"), [this](const TMap&, const FString& B) { return HandleAnalyzeRebuildImpact(B); }); - HandlerMap.Add(TEXT("diffBlueprints"), [this](const TMap&, const FString& B) { return HandleDiffBlueprints(B); }); - HandlerMap.Add(TEXT("createStruct"), [this](const TMap&, const FString& B) { return HandleCreateStruct(B); }); - HandlerMap.Add(TEXT("createEnum"), [this](const TMap&, const FString& B) { return HandleCreateEnum(B); }); - HandlerMap.Add(TEXT("addStructProperty"), [this](const TMap&, const FString& B) { return HandleAddStructProperty(B); }); - HandlerMap.Add(TEXT("removeStructProperty"), [this](const TMap&, const FString& B) { return HandleRemoveStructProperty(B); }); - - // Material GET handlers - HandlerMap.Add(TEXT("getMaterial"), [this](const TMap& P, const FString&) { return HandleGetMaterial(P); }); - HandlerMap.Add(TEXT("getMaterialGraph"), [this](const TMap& P, const FString&) { return HandleGetMaterialGraph(P); }); - HandlerMap.Add(TEXT("searchMaterials"), [this](const TMap& P, const FString&) { return HandleSearchMaterials(P); }); - HandlerMap.Add(TEXT("getMaterialInstanceParams"), [this](const TMap& P, const FString&) { return HandleGetMaterialInstanceParameters(P); }); - HandlerMap.Add(TEXT("getMaterialFunction"), [this](const TMap& P, const FString&) { return HandleGetMaterialFunction(P); }); - - // Material POST handlers - HandlerMap.Add(TEXT("describeMaterial"), [this](const TMap&, const FString& B) { return HandleDescribeMaterial(B); }); - HandlerMap.Add(TEXT("findMaterialReferences"), [this](const TMap&, const FString& B) { return HandleFindMaterialReferences(B); }); - HandlerMap.Add(TEXT("createMaterial"), [this](const TMap&, const FString& B) { return HandleCreateMaterial(B); }); - HandlerMap.Add(TEXT("setMaterialProperty"), [this](const TMap&, const FString& B) { return HandleSetMaterialProperty(B); }); - HandlerMap.Add(TEXT("addMaterialExpression"), [this](const TMap&, const FString& B) { return HandleAddMaterialExpression(B); }); - HandlerMap.Add(TEXT("deleteMaterialExpression"),[this](const TMap&, const FString& B) { return HandleDeleteMaterialExpression(B); }); - HandlerMap.Add(TEXT("connectMaterialPins"), [this](const TMap&, const FString& B) { return HandleConnectMaterialPins(B); }); - HandlerMap.Add(TEXT("disconnectMaterialPin"), [this](const TMap&, const FString& B) { return HandleDisconnectMaterialPin(B); }); - HandlerMap.Add(TEXT("setExpressionValue"), [this](const TMap&, const FString& B) { return HandleSetExpressionValue(B); }); - HandlerMap.Add(TEXT("moveMaterialExpression"), [this](const TMap&, const FString& B) { return HandleMoveMaterialExpression(B); }); - HandlerMap.Add(TEXT("createMaterialInstance"), [this](const TMap&, const FString& B) { return HandleCreateMaterialInstance(B); }); - HandlerMap.Add(TEXT("setMaterialInstanceParameter"), [this](const TMap&, const FString& B) { return HandleSetMaterialInstanceParameter(B); }); - HandlerMap.Add(TEXT("reparentMaterialInstance"),[this](const TMap&, const FString& B) { return HandleReparentMaterialInstance(B); }); - HandlerMap.Add(TEXT("createMaterialFunction"), [this](const TMap&, const FString& B) { return HandleCreateMaterialFunction(B); }); - HandlerMap.Add(TEXT("snapshotMaterialGraph"), [this](const TMap&, const FString& B) { return HandleSnapshotMaterialGraph(B); }); - HandlerMap.Add(TEXT("diffMaterialGraph"), [this](const TMap&, const FString& B) { return HandleDiffMaterialGraph(B); }); - HandlerMap.Add(TEXT("restoreMaterialGraph"), [this](const TMap&, const FString& B) { return HandleRestoreMaterialGraph(B); }); - HandlerMap.Add(TEXT("validateMaterial"), [this](const TMap&, const FString& B) { return HandleValidateMaterial(B); }); - - // Animation Blueprint handlers - HandlerMap.Add(TEXT("createAnimBlueprint"), [this](const TMap&, const FString& B) { return HandleCreateAnimBlueprint(B); }); - HandlerMap.Add(TEXT("addAnimState"), [this](const TMap&, const FString& B) { return HandleAddAnimState(B); }); - HandlerMap.Add(TEXT("removeAnimState"), [this](const TMap&, const FString& B) { return HandleRemoveAnimState(B); }); - HandlerMap.Add(TEXT("addAnimTransition"), [this](const TMap&, const FString& B) { return HandleAddAnimTransition(B); }); - HandlerMap.Add(TEXT("setTransitionRule"), [this](const TMap&, const FString& B) { return HandleSetTransitionRule(B); }); - HandlerMap.Add(TEXT("addAnimNode"), [this](const TMap&, const FString& B) { return HandleAddAnimNode(B); }); - HandlerMap.Add(TEXT("addStateMachine"), [this](const TMap&, const FString& B) { return HandleAddStateMachine(B); }); - HandlerMap.Add(TEXT("setStateAnimation"), [this](const TMap&, const FString& B) { return HandleSetStateAnimation(B); }); - HandlerMap.Add(TEXT("listAnimSlots"), [this](const TMap&, const FString& B) { return HandleListAnimSlots(B); }); - HandlerMap.Add(TEXT("listSyncGroups"), [this](const TMap&, const FString& B) { return HandleListSyncGroups(B); }); - HandlerMap.Add(TEXT("createBlendSpace"), [this](const TMap&, const FString& B) { return HandleCreateBlendSpace(B); }); - HandlerMap.Add(TEXT("setBlendSpaceSamples"), [this](const TMap&, const FString& B) { return HandleSetBlendSpaceSamples(B); }); - HandlerMap.Add(TEXT("setStateBlendSpace"), [this](const TMap&, const FString& B) { return HandleSetStateBlendSpace(B); }); + H(TEXT("rescan"), &FBlueprintMCPServer::HandleRescan); + H(TEXT("blueprint"), &FBlueprintMCPServer::HandleGetBlueprint); + H(TEXT("graph"), &FBlueprintMCPServer::HandleGetGraph); + H(TEXT("search"), &FBlueprintMCPServer::HandleSearch); + H(TEXT("references"), &FBlueprintMCPServer::HandleFindReferences); + H(TEXT("testSave"), &FBlueprintMCPServer::HandleTestSave); + H(TEXT("searchByType"), &FBlueprintMCPServer::HandleSearchByType); + H(TEXT("replaceFunctionCalls"), &FBlueprintMCPServer::HandleReplaceFunctionCalls); + H(TEXT("changeVariableType"), &FBlueprintMCPServer::HandleChangeVariableType); + H(TEXT("changeFunctionParamType"), &FBlueprintMCPServer::HandleChangeFunctionParamType); + H(TEXT("removeFunctionParameter"), &FBlueprintMCPServer::HandleRemoveFunctionParameter); + H(TEXT("deleteAsset"), &FBlueprintMCPServer::HandleDeleteAsset); + H(TEXT("connectPins"), &FBlueprintMCPServer::HandleConnectPins); + H(TEXT("disconnectPin"), &FBlueprintMCPServer::HandleDisconnectPin); + H(TEXT("refreshAllNodes"), &FBlueprintMCPServer::HandleRefreshAllNodes); + H(TEXT("setPinDefault"), &FBlueprintMCPServer::HandleSetPinDefault); + H(TEXT("moveNode"), &FBlueprintMCPServer::HandleMoveNode); + H(TEXT("getNodeComment"), &FBlueprintMCPServer::HandleGetNodeComment); + H(TEXT("setNodeComment"), &FBlueprintMCPServer::HandleSetNodeComment); + H(TEXT("getPinInfo"), &FBlueprintMCPServer::HandleGetPinInfo); + H(TEXT("checkPinCompatibility"), &FBlueprintMCPServer::HandleCheckPinCompatibility); + H(TEXT("listClasses"), &FBlueprintMCPServer::HandleListClasses); + H(TEXT("listFunctions"), &FBlueprintMCPServer::HandleListFunctions); + H(TEXT("listProperties"), &FBlueprintMCPServer::HandleListProperties); + H(TEXT("changeStructNodeType"), &FBlueprintMCPServer::HandleChangeStructNodeType); + H(TEXT("deleteNode"), &FBlueprintMCPServer::HandleDeleteNode); + H(TEXT("duplicateNodes"), &FBlueprintMCPServer::HandleDuplicateNodes); + H(TEXT("validateBlueprint"), &FBlueprintMCPServer::HandleValidateBlueprint); + H(TEXT("validateAllBlueprints"), &FBlueprintMCPServer::HandleValidateAllBlueprints); + H(TEXT("addNode"), &FBlueprintMCPServer::HandleAddNode); + H(TEXT("searchNodeActions"), &FBlueprintMCPServer::HandleSearchNodeActions); + H(TEXT("spawnNode"), &FBlueprintMCPServer::HandleSpawnNode); + H(TEXT("renameAsset"), &FBlueprintMCPServer::HandleRenameAsset); + H(TEXT("reparentBlueprint"), &FBlueprintMCPServer::HandleReparentBlueprint); + H(TEXT("setBlueprintDefault"), &FBlueprintMCPServer::HandleSetBlueprintDefault); + H(TEXT("createBlueprint"), &FBlueprintMCPServer::HandleCreateBlueprint); + H(TEXT("createGraph"), &FBlueprintMCPServer::HandleCreateGraph); + H(TEXT("deleteGraph"), &FBlueprintMCPServer::HandleDeleteGraph); + H(TEXT("renameGraph"), &FBlueprintMCPServer::HandleRenameGraph); + H(TEXT("addVariable"), &FBlueprintMCPServer::HandleAddVariable); + H(TEXT("removeVariable"), &FBlueprintMCPServer::HandleRemoveVariable); + H(TEXT("setVariableMetadata"), &FBlueprintMCPServer::HandleSetVariableMetadata); + H(TEXT("addInterface"), &FBlueprintMCPServer::HandleAddInterface); + H(TEXT("removeInterface"), &FBlueprintMCPServer::HandleRemoveInterface); + H(TEXT("listInterfaces"), &FBlueprintMCPServer::HandleListInterfaces); + H(TEXT("addEventDispatcher"), &FBlueprintMCPServer::HandleAddEventDispatcher); + H(TEXT("listEventDispatchers"), &FBlueprintMCPServer::HandleListEventDispatchers); + H(TEXT("addFunctionParameter"), &FBlueprintMCPServer::HandleAddFunctionParameter); + H(TEXT("addComponent"), &FBlueprintMCPServer::HandleAddComponent); + H(TEXT("removeComponent"), &FBlueprintMCPServer::HandleRemoveComponent); + H(TEXT("listComponents"), &FBlueprintMCPServer::HandleListComponents); + H(TEXT("snapshotGraph"), &FBlueprintMCPServer::HandleSnapshotGraph); + H(TEXT("diffGraph"), &FBlueprintMCPServer::HandleDiffGraph); + H(TEXT("restoreGraph"), &FBlueprintMCPServer::HandleRestoreGraph); + H(TEXT("findDisconnectedPins"), &FBlueprintMCPServer::HandleFindDisconnectedPins); + H(TEXT("analyzeRebuildImpact"), &FBlueprintMCPServer::HandleAnalyzeRebuildImpact); + H(TEXT("diffBlueprints"), &FBlueprintMCPServer::HandleDiffBlueprints); + H(TEXT("createStruct"), &FBlueprintMCPServer::HandleCreateStruct); + H(TEXT("createEnum"), &FBlueprintMCPServer::HandleCreateEnum); + H(TEXT("addStructProperty"), &FBlueprintMCPServer::HandleAddStructProperty); + H(TEXT("removeStructProperty"), &FBlueprintMCPServer::HandleRemoveStructProperty); + H(TEXT("getMaterial"), &FBlueprintMCPServer::HandleGetMaterial); + H(TEXT("getMaterialGraph"), &FBlueprintMCPServer::HandleGetMaterialGraph); + H(TEXT("searchMaterials"), &FBlueprintMCPServer::HandleSearchMaterials); + H(TEXT("getMaterialInstanceParams"),&FBlueprintMCPServer::HandleGetMaterialInstanceParameters); + H(TEXT("getMaterialFunction"), &FBlueprintMCPServer::HandleGetMaterialFunction); + H(TEXT("describeMaterial"), &FBlueprintMCPServer::HandleDescribeMaterial); + H(TEXT("findMaterialReferences"), &FBlueprintMCPServer::HandleFindMaterialReferences); + H(TEXT("createMaterial"), &FBlueprintMCPServer::HandleCreateMaterial); + H(TEXT("setMaterialProperty"), &FBlueprintMCPServer::HandleSetMaterialProperty); + H(TEXT("addMaterialExpression"), &FBlueprintMCPServer::HandleAddMaterialExpression); + H(TEXT("deleteMaterialExpression"), &FBlueprintMCPServer::HandleDeleteMaterialExpression); + H(TEXT("connectMaterialPins"), &FBlueprintMCPServer::HandleConnectMaterialPins); + H(TEXT("disconnectMaterialPin"), &FBlueprintMCPServer::HandleDisconnectMaterialPin); + H(TEXT("setExpressionValue"), &FBlueprintMCPServer::HandleSetExpressionValue); + H(TEXT("moveMaterialExpression"), &FBlueprintMCPServer::HandleMoveMaterialExpression); + H(TEXT("createMaterialInstance"), &FBlueprintMCPServer::HandleCreateMaterialInstance); + H(TEXT("setMaterialInstanceParameter"), &FBlueprintMCPServer::HandleSetMaterialInstanceParameter); + H(TEXT("reparentMaterialInstance"), &FBlueprintMCPServer::HandleReparentMaterialInstance); + H(TEXT("createMaterialFunction"), &FBlueprintMCPServer::HandleCreateMaterialFunction); + H(TEXT("snapshotMaterialGraph"), &FBlueprintMCPServer::HandleSnapshotMaterialGraph); + H(TEXT("diffMaterialGraph"), &FBlueprintMCPServer::HandleDiffMaterialGraph); + H(TEXT("restoreMaterialGraph"), &FBlueprintMCPServer::HandleRestoreMaterialGraph); + H(TEXT("validateMaterial"), &FBlueprintMCPServer::HandleValidateMaterial); + H(TEXT("createAnimBlueprint"), &FBlueprintMCPServer::HandleCreateAnimBlueprint); + H(TEXT("addAnimState"), &FBlueprintMCPServer::HandleAddAnimState); + H(TEXT("removeAnimState"), &FBlueprintMCPServer::HandleRemoveAnimState); + H(TEXT("addAnimTransition"), &FBlueprintMCPServer::HandleAddAnimTransition); + H(TEXT("setTransitionRule"), &FBlueprintMCPServer::HandleSetTransitionRule); + H(TEXT("addAnimNode"), &FBlueprintMCPServer::HandleAddAnimNode); + H(TEXT("addStateMachine"), &FBlueprintMCPServer::HandleAddStateMachine); + H(TEXT("setStateAnimation"), &FBlueprintMCPServer::HandleSetStateAnimation); + H(TEXT("listAnimSlots"), &FBlueprintMCPServer::HandleListAnimSlots); + H(TEXT("listSyncGroups"), &FBlueprintMCPServer::HandleListSyncGroups); + H(TEXT("createBlendSpace"), &FBlueprintMCPServer::HandleCreateBlendSpace); + H(TEXT("setBlendSpaceSamples"), &FBlueprintMCPServer::HandleSetBlendSpaceSamples); + H(TEXT("setStateBlendSpace"), &FBlueprintMCPServer::HandleSetStateBlendSpace); } // ============================================================ // HandleRescan — refresh cached asset lists from asset registry // ============================================================ -FString FBlueprintMCPServer::HandleRescan() +void FBlueprintMCPServer::HandleRescan(const FJsonObject* Json, FJsonObject* Result) { UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Rescanning asset registry...")); @@ -1092,13 +1136,12 @@ FString FBlueprintMCPServer::HandleRescan() OldMI, AllMaterialInstanceAssets.Num(), OldMF, AllMaterialFunctionAssets.Num()); - TSharedRef J = MakeShared(); - J->SetStringField(TEXT("status"), TEXT("ok")); - J->SetNumberField(TEXT("blueprintCount"), AllBlueprintAssets.Num()); - J->SetNumberField(TEXT("mapCount"), AllMapAssets.Num()); - J->SetNumberField(TEXT("materialCount"), AllMaterialAssets.Num()); - J->SetNumberField(TEXT("materialInstanceCount"), AllMaterialInstanceAssets.Num()); - J->SetNumberField(TEXT("materialFunctionCount"), AllMaterialFunctionAssets.Num()); + Result->SetStringField(TEXT("status"), TEXT("ok")); + Result->SetNumberField(TEXT("blueprintCount"), AllBlueprintAssets.Num()); + Result->SetNumberField(TEXT("mapCount"), AllMapAssets.Num()); + Result->SetNumberField(TEXT("materialCount"), AllMaterialAssets.Num()); + Result->SetNumberField(TEXT("materialInstanceCount"), AllMaterialInstanceAssets.Num()); + Result->SetNumberField(TEXT("materialFunctionCount"), AllMaterialFunctionAssets.Num()); TSharedRef Delta = MakeShared(); Delta->SetNumberField(TEXT("blueprints"), AllBlueprintAssets.Num() - OldBP); @@ -1106,9 +1149,7 @@ FString FBlueprintMCPServer::HandleRescan() Delta->SetNumberField(TEXT("materials"), AllMaterialAssets.Num() - OldMat); Delta->SetNumberField(TEXT("materialInstances"), AllMaterialInstanceAssets.Num() - OldMI); Delta->SetNumberField(TEXT("materialFunctions"), AllMaterialFunctionAssets.Num() - OldMF); - J->SetObjectField(TEXT("delta"), Delta); - - return JsonToString(J); + Result->SetObjectField(TEXT("delta"), Delta); } // ============================================================ diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintMCPServer.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintMCPServer.h index 8a06fc62..20203782 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintMCPServer.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintMCPServer.h @@ -94,8 +94,8 @@ public: int32 GetMaterialInstanceCount() const { return AllMaterialInstanceAssets.Num(); } private: - // ----- TMap-based request dispatch ----- - using FRequestHandler = TFunction&, const FString&)>; + // ----- Request dispatch ----- + using FRequestHandler = TFunction; TMap HandlerMap; TSet MutationEndpoints; void RegisterHandlers(); @@ -119,162 +119,162 @@ private: bool bIsEditor = false; // ----- Asset registry rescan ----- - FString HandleRescan(); + void HandleRescan(const FJsonObject* Json, FJsonObject* Result); // ----- Request handlers (read-only) ----- - FString HandleList(const TMap& Params); - FString HandleGetBlueprint(const TMap& Params); - FString HandleGetGraph(const TMap& Params); - FString HandleSearch(const TMap& Params); - FString HandleFindReferences(const TMap& Params); - FString HandleSearchByType(const TMap& Params); + void HandleList(const FJsonObject* Json, FJsonObject* Result); + void HandleGetBlueprint(const FJsonObject* Json, FJsonObject* Result); + void HandleGetGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleSearch(const FJsonObject* Json, FJsonObject* Result); + void HandleFindReferences(const FJsonObject* Json, FJsonObject* Result); + void HandleSearchByType(const FJsonObject* Json, FJsonObject* Result); // ----- Request handlers (write) ----- - FString HandleReplaceFunctionCalls(const FString& Body); - FString HandleChangeVariableType(const FString& Body); - FString HandleChangeFunctionParamType(const FString& Body); - FString HandleRemoveFunctionParameter(const FString& Body); - FString HandleDeleteAsset(const FString& Body); - FString HandleDeleteNode(const FString& Body); - FString HandleDuplicateNodes(const FString& Body); - FString HandleAddNode(const FString& Body); - FString HandleRenameAsset(const FString& Body); + void HandleReplaceFunctionCalls(const FJsonObject* Json, FJsonObject* Result); + void HandleChangeVariableType(const FJsonObject* Json, FJsonObject* Result); + void HandleChangeFunctionParamType(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveFunctionParameter(const FJsonObject* Json, FJsonObject* Result); + void HandleDeleteAsset(const FJsonObject* Json, FJsonObject* Result); + void HandleDeleteNode(const FJsonObject* Json, FJsonObject* Result); + void HandleDuplicateNodes(const FJsonObject* Json, FJsonObject* Result); + void HandleAddNode(const FJsonObject* Json, FJsonObject* Result); + void HandleRenameAsset(const FJsonObject* Json, FJsonObject* Result); // ----- Validation (read-only, no save) ----- - FString HandleValidateBlueprint(const FString& Body); - FString HandleValidateAllBlueprints(const FString& Body); + void HandleValidateBlueprint(const FJsonObject* Json, FJsonObject* Result); + void HandleValidateAllBlueprints(const FJsonObject* Json, FJsonObject* Result); // ----- Pin manipulation (write) ----- - FString HandleConnectPins(const FString& Body); - FString HandleDisconnectPin(const FString& Body); - FString HandleRefreshAllNodes(const FString& Body); - FString HandleSetPinDefault(const FString& Body); - FString HandleMoveNode(const FString& Body); - FString HandleGetNodeComment(const FString& Body); - FString HandleSetNodeComment(const FString& Body); + void HandleConnectPins(const FJsonObject* Json, FJsonObject* Result); + void HandleDisconnectPin(const FJsonObject* Json, FJsonObject* Result); + void HandleRefreshAllNodes(const FJsonObject* Json, FJsonObject* Result); + void HandleSetPinDefault(const FJsonObject* Json, FJsonObject* Result); + void HandleMoveNode(const FJsonObject* Json, FJsonObject* Result); + void HandleGetNodeComment(const FJsonObject* Json, FJsonObject* Result); + void HandleSetNodeComment(const FJsonObject* Json, FJsonObject* Result); // ----- Pin introspection (read-only) ----- - FString HandleGetPinInfo(const FString& Body); - FString HandleCheckPinCompatibility(const FString& Body); + void HandleGetPinInfo(const FJsonObject* Json, FJsonObject* Result); + void HandleCheckPinCompatibility(const FJsonObject* Json, FJsonObject* Result); // ----- Class/function discovery (read-only) ----- - FString HandleListClasses(const FString& Body); - FString HandleListFunctions(const FString& Body); - FString HandleListProperties(const FString& Body); + void HandleListClasses(const FJsonObject* Json, FJsonObject* Result); + void HandleListFunctions(const FJsonObject* Json, FJsonObject* Result); + void HandleListProperties(const FJsonObject* Json, FJsonObject* Result); // ----- Struct node manipulation (write) ----- - FString HandleChangeStructNodeType(const FString& Body); + void HandleChangeStructNodeType(const FJsonObject* Json, FJsonObject* Result); // ----- Reparent ----- - FString HandleReparentBlueprint(const FString& Body); + void HandleReparentBlueprint(const FJsonObject* Json, FJsonObject* Result); // ----- Create ----- - FString HandleCreateBlueprint(const FString& Body); - FString HandleCreateGraph(const FString& Body); + void HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result); + void HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result); // ----- User-defined types ----- - FString HandleCreateStruct(const FString& Body); - FString HandleCreateEnum(const FString& Body); - FString HandleAddStructProperty(const FString& Body); - FString HandleRemoveStructProperty(const FString& Body); + void HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result); + void HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result); + void HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result); // ----- Graph manipulation ----- - FString HandleDeleteGraph(const FString& Body); - FString HandleRenameGraph(const FString& Body); + void HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result); // ----- Variables ----- - FString HandleAddVariable(const FString& Body); - FString HandleRemoveVariable(const FString& Body); - FString HandleSetVariableMetadata(const FString& Body); + void HandleAddVariable(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveVariable(const FJsonObject* Json, FJsonObject* Result); + void HandleSetVariableMetadata(const FJsonObject* Json, FJsonObject* Result); // ----- Interfaces ----- - FString HandleAddInterface(const FString& Body); - FString HandleRemoveInterface(const FString& Body); - FString HandleListInterfaces(const FString& Body); + void HandleAddInterface(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveInterface(const FJsonObject* Json, FJsonObject* Result); + void HandleListInterfaces(const FJsonObject* Json, FJsonObject* Result); // ----- Event Dispatchers ----- - FString HandleAddEventDispatcher(const FString& Body); - FString HandleListEventDispatchers(const FString& Body); + void HandleAddEventDispatcher(const FJsonObject* Json, FJsonObject* Result); + void HandleListEventDispatchers(const FJsonObject* Json, FJsonObject* Result); // ----- Function Parameters ----- - FString HandleAddFunctionParameter(const FString& Body); + void HandleAddFunctionParameter(const FJsonObject* Json, FJsonObject* Result); // ----- Components ----- - FString HandleAddComponent(const FString& Body); - FString HandleRemoveComponent(const FString& Body); - FString HandleListComponents(const FString& Body); + void HandleAddComponent(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveComponent(const FJsonObject* Json, FJsonObject* Result); + void HandleListComponents(const FJsonObject* Json, FJsonObject* Result); // ----- Property defaults ----- - FString HandleSetBlueprintDefault(const FString& Body); + void HandleSetBlueprintDefault(const FJsonObject* Json, FJsonObject* Result); // ----- Generic node spawning via action database ----- - FString HandleSearchNodeActions(const FString& Body); - FString HandleSpawnNode(const FString& Body); + void HandleSearchNodeActions(const FJsonObject* Json, FJsonObject* Result); + void HandleSpawnNode(const FJsonObject* Json, FJsonObject* Result); // ----- Diagnostic ----- - FString HandleTestSave(const TMap& Params); + void HandleTestSave(const FJsonObject* Json, FJsonObject* Result); // ----- Snapshot / Safety tools (write) ----- - FString HandleSnapshotGraph(const FString& Body); - FString HandleDiffGraph(const FString& Body); - FString HandleRestoreGraph(const FString& Body); - FString HandleFindDisconnectedPins(const FString& Body); - FString HandleAnalyzeRebuildImpact(const FString& Body); + void HandleSnapshotGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleDiffGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleRestoreGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleFindDisconnectedPins(const FJsonObject* Json, FJsonObject* Result); + void HandleAnalyzeRebuildImpact(const FJsonObject* Json, FJsonObject* Result); // ----- Cross-Blueprint comparison (read-only) ----- - FString HandleDiffBlueprints(const FString& Body); + void HandleDiffBlueprints(const FJsonObject* Json, FJsonObject* Result); // ----- Material read-only handlers (Phase 1) ----- - FString HandleListMaterials(const TMap& Params); - FString HandleGetMaterial(const TMap& Params); - FString HandleGetMaterialGraph(const TMap& Params); - FString HandleDescribeMaterial(const FString& Body); - FString HandleSearchMaterials(const TMap& Params); - FString HandleFindMaterialReferences(const FString& Body); + void HandleListMaterials(const FJsonObject* Json, FJsonObject* Result); + void HandleGetMaterial(const FJsonObject* Json, FJsonObject* Result); + void HandleGetMaterialGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleDescribeMaterial(const FJsonObject* Json, FJsonObject* Result); + void HandleSearchMaterials(const FJsonObject* Json, FJsonObject* Result); + void HandleFindMaterialReferences(const FJsonObject* Json, FJsonObject* Result); // ----- Material mutation handlers (Phase 2) ----- - FString HandleCreateMaterial(const FString& Body); - FString HandleSetMaterialProperty(const FString& Body); - FString HandleAddMaterialExpression(const FString& Body); - FString HandleDeleteMaterialExpression(const FString& Body); - FString HandleConnectMaterialPins(const FString& Body); - FString HandleDisconnectMaterialPin(const FString& Body); - FString HandleSetExpressionValue(const FString& Body); - FString HandleMoveMaterialExpression(const FString& Body); + void HandleCreateMaterial(const FJsonObject* Json, FJsonObject* Result); + void HandleSetMaterialProperty(const FJsonObject* Json, FJsonObject* Result); + void HandleAddMaterialExpression(const FJsonObject* Json, FJsonObject* Result); + void HandleDeleteMaterialExpression(const FJsonObject* Json, FJsonObject* Result); + void HandleConnectMaterialPins(const FJsonObject* Json, FJsonObject* Result); + void HandleDisconnectMaterialPin(const FJsonObject* Json, FJsonObject* Result); + void HandleSetExpressionValue(const FJsonObject* Json, FJsonObject* Result); + void HandleMoveMaterialExpression(const FJsonObject* Json, FJsonObject* Result); // ----- Material instance handlers (Phase 3) ----- - FString HandleCreateMaterialInstance(const FString& Body); - FString HandleSetMaterialInstanceParameter(const FString& Body); - FString HandleGetMaterialInstanceParameters(const TMap& Params); - FString HandleReparentMaterialInstance(const FString& Body); + void HandleCreateMaterialInstance(const FJsonObject* Json, FJsonObject* Result); + void HandleSetMaterialInstanceParameter(const FJsonObject* Json, FJsonObject* Result); + void HandleGetMaterialInstanceParameters(const FJsonObject* Json, FJsonObject* Result); + void HandleReparentMaterialInstance(const FJsonObject* Json, FJsonObject* Result); // ----- Material function handlers (Phase 4) ----- - FString HandleListMaterialFunctions(const TMap& Params); - FString HandleGetMaterialFunction(const TMap& Params); - FString HandleCreateMaterialFunction(const FString& Body); + void HandleListMaterialFunctions(const FJsonObject* Json, FJsonObject* Result); + void HandleGetMaterialFunction(const FJsonObject* Json, FJsonObject* Result); + void HandleCreateMaterialFunction(const FJsonObject* Json, FJsonObject* Result); // ----- Material validation ----- - FString HandleValidateMaterial(const FString& Body); + void HandleValidateMaterial(const FJsonObject* Json, FJsonObject* Result); // ----- Material snapshot/diff/restore (Phase 5) ----- - FString HandleSnapshotMaterialGraph(const FString& Body); - FString HandleDiffMaterialGraph(const FString& Body); - FString HandleRestoreMaterialGraph(const FString& Body); + void HandleSnapshotMaterialGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleDiffMaterialGraph(const FJsonObject* Json, FJsonObject* Result); + void HandleRestoreMaterialGraph(const FJsonObject* Json, FJsonObject* Result); // ----- Animation Blueprint handlers ----- - FString HandleCreateAnimBlueprint(const FString& Body); - FString HandleAddAnimState(const FString& Body); - FString HandleRemoveAnimState(const FString& Body); - FString HandleAddAnimTransition(const FString& Body); - FString HandleSetTransitionRule(const FString& Body); - FString HandleAddAnimNode(const FString& Body); - FString HandleAddStateMachine(const FString& Body); - FString HandleSetStateAnimation(const FString& Body); - FString HandleListAnimSlots(const FString& Body); - FString HandleListSyncGroups(const FString& Body); - FString HandleCreateBlendSpace(const FString& Body); - FString HandleSetBlendSpaceSamples(const FString& Body); - FString HandleSetStateBlendSpace(const FString& Body); + void HandleCreateAnimBlueprint(const FJsonObject* Json, FJsonObject* Result); + void HandleAddAnimState(const FJsonObject* Json, FJsonObject* Result); + void HandleRemoveAnimState(const FJsonObject* Json, FJsonObject* Result); + void HandleAddAnimTransition(const FJsonObject* Json, FJsonObject* Result); + void HandleSetTransitionRule(const FJsonObject* Json, FJsonObject* Result); + void HandleAddAnimNode(const FJsonObject* Json, FJsonObject* Result); + void HandleAddStateMachine(const FJsonObject* Json, FJsonObject* Result); + void HandleSetStateAnimation(const FJsonObject* Json, FJsonObject* Result); + void HandleListAnimSlots(const FJsonObject* Json, FJsonObject* Result); + void HandleListSyncGroups(const FJsonObject* Json, FJsonObject* Result); + void HandleCreateBlendSpace(const FJsonObject* Json, FJsonObject* Result); + void HandleSetBlendSpaceSamples(const FJsonObject* Json, FJsonObject* Result); + void HandleSetStateBlendSpace(const FJsonObject* Json, FJsonObject* Result); // ----- Serialization ----- TSharedRef SerializeBlueprint(UBlueprint* BP); @@ -292,7 +292,9 @@ private: UEdGraphNode* FindNodeByGuid(UBlueprint* BP, const FString& GuidString, UEdGraph** OutGraph = nullptr); TSharedPtr ParseBodyJson(const FString& Body); FString MakeErrorJson(const FString& Message); + void MakeErrorJson(FJsonObject* Result, const FString& Message); bool SaveBlueprintPackage(UBlueprint* BP); + static void CopyJsonFields(const FJsonObject* Source, FJsonObject* Dest); static FString UrlDecode(const FString& EncodedString); // ----- Material helpers -----