diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h index ea2b2682..b073b2b4 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPHandlers_AssetMutation.h @@ -12,6 +12,7 @@ #include "AssetRegistry/IAssetRegistry.h" #include "AssetToolsModule.h" #include "IAssetTools.h" +#include "FileHelpers.h" #include "MCPHandlers_AssetMutation.generated.h" // --------------------------------------------------------------------------- @@ -241,3 +242,97 @@ public: } } }; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="backup_asset")) +class UMCPHandler_BackupAsset : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Full package path of the asset (e.g. /Game/Widgets/WB_Hotkeys)")) + FString AssetPath; + + virtual FString GetDescription() const override + { + return TEXT("Copy an asset's .uasset file to a .uasset.bak backup."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + FString Filename = FPaths::ConvertRelativePathToFull( + FPackageName::LongPackageNameToFilename(AssetPath, FPackageName::GetAssetPackageExtension())); + + if (!IFileManager::Get().FileExists(*Filename)) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Asset file not found: %s"), *Filename)); + } + + FString BackupFilename = Filename + TEXT(".bak"); + uint32 CopyResult = IFileManager::Get().Copy(*BackupFilename, *Filename, true); + if (CopyResult != COPY_OK) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to back up %s"), *Filename)); + } + + Result->SetStringField(TEXT("backupFile"), BackupFilename); + } +}; + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS(meta=(ToolName="restore_asset")) +class UMCPHandler_RestoreAsset : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Full package path of the asset (e.g. /Game/Widgets/WB_Hotkeys)")) + FString AssetPath; + + virtual FString GetDescription() const override + { + return TEXT("Restore a .uasset file from its .uasset.bak backup, reloading it in the editor."); + } + + virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override + { + FString Filename = FPaths::ConvertRelativePathToFull( + FPackageName::LongPackageNameToFilename(AssetPath, FPackageName::GetAssetPackageExtension())); + FString BackupFilename = Filename + TEXT(".bak"); + + if (!IFileManager::Get().FileExists(*BackupFilename)) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Backup file not found: %s"), *BackupFilename)); + } + + // Release file handles if the package is loaded + UPackage* Package = FindPackage(nullptr, *AssetPath); + if (Package) + { + ResetLoaders(Package); + } + + // Copy backup over the original + uint32 CopyResult = IFileManager::Get().Copy(*Filename, *BackupFilename, true); + if (CopyResult != COPY_OK) + { + return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to restore %s"), *Filename)); + } + + // Reload the package if it was loaded + if (Package) + { + bool bReloaded = false; + FText ErrorMessage; + UEditorLoadingAndSavingUtils::ReloadPackages({Package}, bReloaded, ErrorMessage, EReloadPackagesInteractionMode::AssumePositive); + } + + Result->SetStringField(TEXT("restoredFrom"), BackupFilename); + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp index d23b1b8a..1aa5ee60 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp @@ -253,23 +253,12 @@ void FMCPServer::DispatchToolCall(const FString& ToolName, const FJsonObject* Pa { if (UClass** HandlerClass = MCPHandlerRegistry.Find(ToolName)) { - const bool bIsMutation = MutationEndpoints.Contains(ToolName); - if (bIsMutation && GEditor) - { - GEditor->BeginTransaction(FText::FromString(FString::Printf(TEXT("BlueprintMCP: %s"), *ToolName))); - } - TStrongObjectPtr HandlerObj(NewObject(GetTransientPackage(), *HandlerClass)); IMCPHandler* Handler = Cast(HandlerObj.Get()); if (MCPUtils::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), Params, Result)) { Handler->Handle(Params, Result); } - - if (bIsMutation && GEditor) - { - GEditor->EndTransaction(); - } } else { @@ -316,7 +305,6 @@ bool FMCPServer::Start(int32 InPort, bool bEditorMode) // Register handlers BuildMCPHandlerRegistry(); - RegisterHandlers(); // Create TCP listen socket ISocketSubsystem* SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); @@ -535,65 +523,9 @@ bool FMCPServer::ProcessOneRequest() } // ============================================================ -// RegisterHandlers / BuildMCPHandlerRegistry +// BuildMCPHandlerRegistry // ============================================================ -void FMCPServer::RegisterHandlers() -{ - // Mutation endpoints — wrapped in undo transactions by DispatchToolCall() - MutationEndpoints = { - TEXT("replace_function_calls_in_blueprint"), - TEXT("change_blueprint_variable_type"), - TEXT("change_function_parameter_type"), - TEXT("remove_function_parameter"), - TEXT("delete_asset"), - TEXT("connect_blueprint_pins"), - TEXT("disconnect_blueprint_pins"), - TEXT("refresh_all_nodes_in_graph"), - TEXT("set_pin_default_values"), - TEXT("set_node_positions"), - TEXT("change_struct_node_type"), - TEXT("delete_node_from_graph"), - TEXT("duplicate_nodes_in_graph"), - TEXT("spawn_nodes_in_graph"), - TEXT("set_node_comment"), - TEXT("rename_asset"), - TEXT("reparent_blueprint"), - TEXT("set_class_default_value"), - TEXT("create_blueprint_asset"), - TEXT("create_blueprint_graph"), - TEXT("delete_blueprint_graph"), - TEXT("rename_blueprint_graph"), - TEXT("add_blueprint_variable"), - TEXT("remove_blueprint_variable"), - TEXT("set_blueprint_variable_metadata"), - TEXT("add_blueprint_interface"), - TEXT("remove_blueprint_interface"), - TEXT("add_event_dispatcher"), - TEXT("add_function_parameter"), - TEXT("add_blueprint_component"), - TEXT("remove_blueprint_component"), - TEXT("create_material_asset"), - TEXT("set_material_property"), - TEXT("add_material_expression"), - TEXT("delete_material_expression"), - TEXT("connect_material_expression_pins"), - TEXT("disconnect_material_expression_pin"), - TEXT("set_material_expression_property"), - TEXT("set_material_expression_position"), - TEXT("create_material_instance_asset"), - TEXT("set_material_instance_parameter"), - TEXT("reparent_material_instance"), - TEXT("create_material_function_asset"), - TEXT("add_anim_state_to_machine"), - TEXT("remove_anim_state_from_machine"), - TEXT("add_anim_state_transition"), - TEXT("set_anim_transition_rule"), - TEXT("set_anim_state_animation"), - }; - -} - void FMCPServer::BuildMCPHandlerRegistry() { for (TObjectIterator It; It; ++It) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h index a40736a2..e61d35f7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPServer.h @@ -51,8 +51,6 @@ public: private: // ----- Tool dispatch ----- TMap MCPHandlerRegistry; // tool name -> UMCPHandler subclass - TSet MutationEndpoints; - void RegisterHandlers(); void BuildMCPHandlerRegistry(); // Dispatch a tool call to the appropriate handler