From cad9947670e5191b9705d67acda5571b36490342 Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 17 Mar 2026 17:24:35 -0400 Subject: [PATCH] Lots of useful work --- Content/Testing/M_Test.uasset | 3 - .../BlueprintMCP/HalfBaked/Blueprint_Search.h | 37 ++++----- .../BlueprintMCP/HalfBaked/Material_Create.h | 83 ------------------- .../Handlers/BlueprintVariable_Create.h | 2 +- .../Handlers/BlueprintVariable_Delete.h | 2 +- .../Handlers/BlueprintVariable_Dump.h | 2 +- .../Handlers/BlueprintVariable_Modify.h | 2 +- .../Handlers/GraphNode_ShowMenu.h | 7 ++ .../BlueprintMCP/Handlers/GraphPin_Connect.h | 2 +- .../Handlers/GraphPin_Disconnect.h | 2 +- .../Source/BlueprintMCP/Handlers/Graph_Dump.h | 2 +- .../MaterialInstance_ClearParameter.h | 3 +- .../MaterialInstance_DumpParameters.h | 7 +- .../Handlers/MaterialInstance_SetParameter.h | 5 +- .../Material_Compile.h | 0 .../BlueprintMCP/Handlers/Material_Create.h | 45 ++++++++++ .../Material_DumpParameters.h | 8 +- .../BlueprintMCP/Handlers/ShowCommands.h | 4 +- .../Source/BlueprintMCP/Handlers/UserManual.h | 14 ++-- .../BlueprintMCP/Private/MCPFetcher.cpp | 64 ++++---------- .../Source/BlueprintMCP/Private/MCPServer.cpp | 3 - .../Source/BlueprintMCP/Private/MCPUtils.cpp | 75 ----------------- .../Private/MaterialParameter.cpp | 78 +++++++++++++++++ .../Source/BlueprintMCP/Public/MCPFetcher.h | 16 +--- .../Source/BlueprintMCP/Public/MCPUtils.h | 5 -- .../BlueprintMCP/Public/MaterialParameter.h | 13 +++ tools/mcp-bridge.py | 1 - 27 files changed, 208 insertions(+), 277 deletions(-) delete mode 100644 Content/Testing/M_Test.uasset delete mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Create.h rename Plugins/BlueprintMCP/Source/BlueprintMCP/{HalfBaked => Handlers}/Material_Compile.h (100%) create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h rename Plugins/BlueprintMCP/Source/BlueprintMCP/{HalfBaked => Handlers}/Material_DumpParameters.h (77%) create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MaterialParameter.cpp create mode 100644 Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MaterialParameter.h diff --git a/Content/Testing/M_Test.uasset b/Content/Testing/M_Test.uasset deleted file mode 100644 index 30c46a60..00000000 --- a/Content/Testing/M_Test.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:11baf80ff08598e08e6d2e290ab08a4f2393328f059c195cbc3f0e03e181aebd -size 13164 diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Blueprint_Search.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Blueprint_Search.h index ba11fe15..ee4b12e2 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Blueprint_Search.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Blueprint_Search.h @@ -5,6 +5,7 @@ #include "MCPHandler.h" #include "MCPAssets.h" #include "MCPUtils.h" +#include "MCPTypes.h" #include "Engine/Blueprint.h" #include "Engine/World.h" #include "Blueprint_Search.generated.h" @@ -26,12 +27,6 @@ public: UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)")) FString ParentClass; - UPROPERTY(meta=(Optional, Description="Include regular blueprints (default true)")) - bool IncludeRegular = true; - - UPROPERTY(meta=(Optional, Description="Include level blueprints (default true)")) - bool IncludeLevel = true; - virtual FString GetDescription() const override { return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type."); @@ -40,30 +35,28 @@ public: virtual void Handle() override { MCPAssets Assets; - Assets.NoScans().Substring(Query).Limit(500); - if (IncludeRegular) Assets.Scan(); - if (IncludeLevel) Assets.Scan(); - Assets.Info(); + Assets.Scan().Substring(Query).Limit(500); + if (!Assets.Info()) return; + + UClass *Parent = nullptr; + if (!ParentClass.IsEmpty()) + { + Parent = UMCPTypes::TextToOneObjectType(ParentClass); + if (!Parent) return; + } int32 Count = 0; for (const FAssetData& Asset : Assets.AllData()) { // Extract parent class name from asset tags FString ParentClassName; - if (Asset.AssetClassPath == UWorld::StaticClass()->GetClassPathName()) + Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName); + int32 DotIndex; + if (ParentClassName.FindLastChar('.', DotIndex)) { - ParentClassName = TEXT("LevelScriptActor"); - } - else - { - Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName); - int32 DotIndex; - if (ParentClassName.FindLastChar('.', DotIndex)) - { - ParentClassName = ParentClassName.Mid(DotIndex + 1); - } - ParentClassName.RemoveFromEnd(TEXT("'")); + ParentClassName = ParentClassName.Mid(DotIndex + 1); } + ParentClassName.RemoveFromEnd(TEXT("'")); // Apply parent class filter if (!ParentClass.IsEmpty()) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Create.h deleted file mode 100644 index 39c9bd93..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Create.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MCPServer.h" -#include "MCPHandler.h" -#include "MCPUtils.h" -#include "Materials/Material.h" -#include "MaterialDomain.h" -#include "Factories/MaterialFactoryNew.h" -#include "MCPPackageMaker.h" -#include "Material_Create.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UMCP_Material_Create : public UObject, public IMCPHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Full asset path for the new material (e.g. '/Game/Materials/M_Gold')")) - FString AssetPath; - - UPROPERTY(meta=(Optional, Description="Material domain: Surface, DeferredDecal, LightFunction, Volume, PostProcess, UI")) - FString Domain; - - UPROPERTY(meta=(Optional, Description="Blend mode: Opaque, Masked, Translucent, Additive, Modulate")) - FString BlendMode; - - UPROPERTY(meta=(Optional, Description="Whether the material is two-sided")) - bool TwoSided = false; - - virtual FString GetDescription() const override - { - return TEXT("Create a new UMaterial asset with optional domain, blend mode, and two-sided settings."); - } - - virtual void Handle() override - { - MCPPackageMaker Maker(AssetPath); - if (!Maker.Ok()) return; - - // Parse optional enum properties before creating the asset. - EMaterialDomain ParsedDomain = MD_Surface; - if (!Domain.IsEmpty()) - { - if (!MCPUtils::StringToEnum(Domain, ParsedDomain, TEXT("MD_"))) - return; - } - - EBlendMode ParsedBlendMode = BLEND_Opaque; - if (!BlendMode.IsEmpty()) - { - if (!MCPUtils::StringToEnum(BlendMode, ParsedBlendMode, TEXT("BLEND_"))) - return; - } - - // Create via IAssetTools + factory. - UMaterial* MaterialObj = Maker.CreateAsset(); - if (!MaterialObj) return; - - // Apply optional properties. - if (!Domain.IsEmpty()) - MaterialObj->MaterialDomain = ParsedDomain; - - if (!BlendMode.IsEmpty()) - MaterialObj->BlendMode = ParsedBlendMode; - - MaterialObj->TwoSided = TwoSided ? 1 : 0; - - bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj); - - UMCPServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName()); - UMCPServer::Printf(TEXT("Domain: %s\n"), *MCPUtils::EnumToString(MaterialObj->MaterialDomain, TEXT("MD_"))); - UMCPServer::Printf(TEXT("BlendMode: %s\n"), *MCPUtils::EnumToString(MaterialObj->BlendMode, TEXT("BLEND_"))); - UMCPServer::Printf(TEXT("TwoSided: %s\n"), MaterialObj->TwoSided ? TEXT("true") : TEXT("false")); - if (!bSaved) - UMCPServer::Print(TEXT("WARNING: Package save failed\n")); - } -}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Create.h index 693c6e51..58775238 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Create.h @@ -42,7 +42,7 @@ public: virtual void Handle() override { MCPFetcher F; - UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast(); + UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; // Check for duplicate variable name diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Delete.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Delete.h index c2249243..bbfa9c85 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Delete.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Delete.h @@ -35,7 +35,7 @@ public: virtual void Handle() override { MCPFetcher F; - UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast(); + UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; FBPVarEditor Editor(BP, Variable); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Dump.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Dump.h index 3306b1d1..aea4d4eb 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Dump.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Dump.h @@ -35,7 +35,7 @@ public: virtual void Handle() override { MCPFetcher F; - UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast(); + UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; FBPVarEditor Editor(BP, Variable); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Modify.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Modify.h index ddef4949..1325dff2 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Modify.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintVariable_Modify.h @@ -41,7 +41,7 @@ public: virtual void Handle() override { MCPFetcher F; - UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast(); + UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; FBPVarEditor Editor(BP, Variable); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_ShowMenu.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_ShowMenu.h index d2f73a01..b22ac919 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_ShowMenu.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_ShowMenu.h @@ -6,6 +6,7 @@ #include "MCPToolMenu.h" #include "MCPServer.h" #include "ToolMenus.h" +#include "MaterialGraph/MaterialGraphNode.h" #include "GraphNode_ShowMenu.generated.h" @@ -34,6 +35,11 @@ private: UEdGraphNode* NodeObj = F.Walk(Node).Cast(); if (!NodeObj) return; + if (Cast(NodeObj)) + { + UMCPServer::Printf(TEXT("Material graph nodes do not have usable context menus.")); + return; + } FToolMenuContext Context; TArray Entries = MCPToolMenu::GetMenuItems(NodeObj, Context); for (FToolMenuEntry &Entry : Entries) @@ -41,5 +47,6 @@ private: FString LabelText = Entry.Label.Get().ToString(); UMCPServer::Printf(TEXT("%s\n"), *LabelText); } + if (Entries.IsEmpty()) UMCPServer::Printf(TEXT("No selectable menu entries right now.\n")); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h index 633ebfe9..174795d5 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h @@ -50,7 +50,7 @@ public: virtual void Handle() override { MCPFetcher F; - UEdGraph* G = F.Walk(Graph).ToGraph().Cast(); + UEdGraph* G = F.Walk(Graph).Cast(); if (!G) return; int32 SuccessCount = 0; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h index f52ddc50..9816e02c 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h @@ -50,7 +50,7 @@ public: virtual void Handle() override { MCPFetcher F; - UEdGraph* G = F.Walk(Graph).ToGraph().Cast(); + UEdGraph* G = F.Walk(Graph).Cast(); if (!G) return; int32 SuccessCount = 0; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Graph_Dump.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Graph_Dump.h index 38b82ccd..a6f1eff8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Graph_Dump.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Graph_Dump.h @@ -37,7 +37,7 @@ public: virtual void Handle() override { MCPFetcher F; - UEdGraph *G = F.Walk(Graph).ToGraph().Cast(); + UEdGraph *G = F.Walk(Graph).Cast(); if (!G) return; MCPGraphExport Exporter(G); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h index ae0eb24b..620d98d4 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h @@ -5,6 +5,7 @@ #include "MCPHandler.h" #include "MCPFetcher.h" #include "MCPUtils.h" +#include "MaterialParameter.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialTypes.h" #include "MaterialInstance_ClearParameter.generated.h" @@ -45,7 +46,7 @@ public: // Parse the association string. EMaterialParameterAssociation Association; - if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association)) + if (!MCPMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association)) return; FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h index 71f7f3b7..1d5c0c46 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h @@ -5,6 +5,7 @@ #include "MCPHandler.h" #include "MCPFetcher.h" #include "MCPUtils.h" +#include "MaterialParameter.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialTypes.h" #include "MaterialInstance_DumpParameters.generated.h" @@ -34,7 +35,7 @@ public: UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast(); if (!MI) return; - auto AllParams = MCPUtils::GetMaterialParameters(MI); + auto AllParams = MCPMaterialParameter::GetMaterialParameters(MI); // Overridden parameters first. bool bHasOverrides = false; @@ -42,7 +43,7 @@ public: { if (!Meta.bOverride) continue; if (!bHasOverrides) { UMCPServer::Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; } - MCPUtils::FormatMaterialParameter(Info, Meta); + MCPMaterialParameter::FormatMaterialParameter(Info, Meta); } // Inherited (non-overridden) parameters. @@ -51,7 +52,7 @@ public: { if (Meta.bOverride) continue; if (!bHasInherited) { UMCPServer::Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; } - MCPUtils::FormatMaterialParameter(Info, Meta); + MCPMaterialParameter::FormatMaterialParameter(Info, Meta); } } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h index 3fc928c7..53ca560b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h @@ -5,6 +5,7 @@ #include "MCPHandler.h" #include "MCPFetcher.h" #include "MCPUtils.h" +#include "MaterialParameter.h" #include "Materials/MaterialInstanceConstant.h" #include "MaterialTypes.h" #include "Misc/DefaultValueHelper.h" @@ -49,14 +50,14 @@ public: // Parse the association string. EMaterialParameterAssociation Association; - if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association)) + if (!MCPMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association)) return; // Build the parameter ID to look up. FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); // Find it in the material's parameter map. - auto AllParams = MCPUtils::GetMaterialParameters(MI); + auto AllParams = MCPMaterialParameter::GetMaterialParameters(MI); FMaterialParameterMetadata* Found = AllParams.Find(ParamID); if (!Found) { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Compile.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h similarity index 100% rename from Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_Compile.h rename to Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h new file mode 100644 index 00000000..0c788306 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h @@ -0,0 +1,45 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPServer.h" +#include "MCPHandler.h" +#include "MCPUtils.h" +#include "Materials/Material.h" +#include "MaterialDomain.h" +#include "Factories/MaterialFactoryNew.h" +#include "MCPPackageMaker.h" +#include "Material_Create.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCP_Material_Create : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Full asset path for the new material")) + FString Material; + + virtual FString GetDescription() const override + { + return TEXT("Create a new UMaterial asset"); + } + + virtual void Handle() override + { + MCPPackageMaker Maker(Material); + if (!Maker.Ok()) return; + + // Create via IAssetTools + factory. + UMaterial* MaterialObj = Maker.CreateAsset(); + if (!MaterialObj) return; + + bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj); + UMCPServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName()); + if (!bSaved) UMCPServer::Print(TEXT("WARNING: Package save failed\n")); + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_DumpParameters.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h similarity index 77% rename from Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_DumpParameters.h rename to Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h index 1c1bc23b..8f684c25 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked/Material_DumpParameters.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h @@ -3,8 +3,9 @@ #include "CoreMinimal.h" #include "MCPHandler.h" #include "MCPFetcher.h" +#include "MCPServer.h" #include "MCPUtils.h" -#include "Materials/Material.h" +#include "MaterialParameter.h" #include "MaterialTypes.h" #include "Material_DumpParameters.generated.h" @@ -33,11 +34,12 @@ public: UMaterial* Mat = F.Asset(Material).Cast(); if (!Mat) return; - auto AllParams = MCPUtils::GetMaterialParameters(Mat); + auto AllParams = MCPMaterialParameter::GetMaterialParameters(Mat); for (auto& [Info, Meta] : AllParams) { - MCPUtils::FormatMaterialParameter(Info, Meta); + MCPMaterialParameter::FormatMaterialParameter(Info, Meta); } + if (AllParams.IsEmpty()) UMCPServer::Printf(TEXT("No material parameters.\n")); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/ShowCommands.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/ShowCommands.h index f0aef34c..9234da00 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/ShowCommands.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/ShowCommands.h @@ -78,8 +78,8 @@ public: { UMCPServer::Printf(TEXT("\n")); EmitCommandList(false); - UMCPServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n")); - EmitCommandList(true); + // UMCPServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n")); + // EmitCommandList(true); UMCPServer::Printf(TEXT("\n")); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/UserManual.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/UserManual.h index fcd1bbd1..51e6fd7a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/UserManual.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/UserManual.h @@ -35,12 +35,10 @@ public: "\n component — move from a blueprint to a component" "\n levelblueprint — move from a world to a blueprint" "\n" - "\n DUMP COMMANDS:" + "\n Steps do not always require a parameter. For example, materials" + "\n only have one graph, so you can just say:" "\n" - "\n There are several commands whose names end in 'Dump'. These" - "\n are essential tools for viewing the state of the world." - "\n They are particularly important because they show you what" - "\n unique IDs exist that you can refer to." + "\n /Game/Materials/MyMaterial,graph" "\n" "\n TYPES:" "\n" @@ -80,6 +78,12 @@ public: "\n properties, which actually come from the material expressions." "\n You can edit these using Property_Set on the node." "\n" + "\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:" + "\n" + "\n UserManual: this explanation" + "\n ShowCommands: a full list of all the commands" + "\n Graph_Dump: a detailed listing of any UEdGraph" + "\n Property_Dump: show information on many objects" "\n" )); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp index f4e2bc75..0aa82f5f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp @@ -25,7 +25,6 @@ MCPFetcher::WalkFunc MCPFetcher::GetWalker(const FString& Step) return nullptr; } - void MCPFetcher::SetObj(UObject* InObj) { UMCPServer::AddTouchedObject(InObj); @@ -49,15 +48,26 @@ MCPFetcher& MCPFetcher::SetError() return *this; } +void MCPFetcher::PathFailed(const TCHAR* Expected) +{ + SetError(); + if (ResultPin) + UMCPServer::Printf(TEXT("ERROR: Path specifies a pin, but expected %s\n"), Expected); + else if (Obj) + UMCPServer::Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj->GetClass()->GetName(), Expected); + else + UMCPServer::Printf(TEXT("ERROR: Path led to a null pointer\n")); +} + MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected) { - bError = true; + SetError(); if (ResultPin) - UMCPServer::Printf(TEXT("ERROR: Input to '%s' is a pin, expected %s\n"), Walker, Expected); + UMCPServer::Printf(TEXT("ERROR: Input to '%s' is a pin, but expected %s\n"), Walker, Expected); else if (Obj) - UMCPServer::Printf(TEXT("ERROR: Input to '%s' is %s, expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected); + UMCPServer::Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected); else - UMCPServer::Printf(TEXT("ERROR: Input to '%s' is null, expected %s\n"), Walker, Expected); + UMCPServer::Printf(TEXT("ERROR: Path led to a null pointer\n")); return *this; } @@ -334,47 +344,3 @@ MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value) return *this; } -MCPFetcher& MCPFetcher::ToBlueprint() -{ - if (bError) return *this; - if (::Cast(Obj)) return *this; - - if (UWorld* World = ::Cast(Obj)) - { - if (!World->PersistentLevel) - { - UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no PersistentLevel\n")); - return SetError(); - } - ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true); - if (!LevelBP) - { - UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no level blueprint\n")); - return SetError(); - } - SetObj(LevelBP); - return *this; - } - - return TypeMismatch(TEXT("ToBlueprint"), TEXT("Blueprint or World")); -} - -MCPFetcher& MCPFetcher::ToGraph() -{ - if (bError) return *this; - if (::Cast(Obj)) return *this; - - if (UMaterial* Mat = ::Cast(Obj)) - { - MCPUtils::EnsureMaterialGraph(Mat); - if (!Mat->MaterialGraph) - { - UMCPServer::Printf(TEXT("ERROR: ToGraph: Material '%s' has no material graph\n"), *Mat->GetName()); - return SetError(); - } - SetObj(Mat->MaterialGraph); - return *this; - } - - return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material")); -} diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp index 8f70ac68..bb84da4e 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPServer.cpp @@ -423,9 +423,6 @@ void UMCPServer::ClientThreadFunc(UMCPServer* Server, TSharedPtr MCPUtils::GetMaterialParameters(UMaterialInterface* Material) -{ - TMap Result; - if (!Material) return Result; - TMap Temp; - for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++) - { - Material->GetAllParametersOfType((EMaterialParameterType)i, Temp); - Result.Append(Temp); - } - return Result; -} - -bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation) -{ - if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase)) - OutAssociation = GlobalParameter; - else if (Str.Equals(TEXT("Layer"), ESearchCase::IgnoreCase)) - OutAssociation = LayerParameter; - else if (Str.Equals(TEXT("Blend"), ESearchCase::IgnoreCase)) - OutAssociation = BlendParameter; - else - { - UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str); - return false; - } - return true; -} - -void MCPUtils::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta) -{ - // Association prefix for layer/blend parameters. - FString Prefix; - if (Info.Association == LayerParameter) - Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index); - else if (Info.Association == BlendParameter) - Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index); - - switch (Meta.Value.Type) - { - case EMaterialParameterType::Scalar: - UMCPServer::Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar()); - break; - case EMaterialParameterType::Vector: - { - FLinearColor C = Meta.Value.AsLinearColor(); - UMCPServer::Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"), - *Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A); - break; - } - case EMaterialParameterType::DoubleVector: - { - FVector4d V = Meta.Value.AsVector4d(); - UMCPServer::Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"), - *Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W); - break; - } - case EMaterialParameterType::Texture: - { - UTexture* Tex = Cast(Meta.Value.AsTextureObject()); - UMCPServer::Printf(TEXT(" %sTexture \"%s\" = %s\n"), - *Prefix, *Info.Name.ToString(), Tex ? *MCPUtils::FormatName(Tex) : TEXT("None")); - break; - } - case EMaterialParameterType::StaticSwitch: - UMCPServer::Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"), - *Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false")); - break; - default: - UMCPServer::Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString()); - break; - } -} - bool MCPUtils::SaveGenericPackage(UObject* Asset) { if (!Asset) return false; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MaterialParameter.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MaterialParameter.cpp new file mode 100644 index 00000000..259ad71f --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MaterialParameter.cpp @@ -0,0 +1,78 @@ +#include "MaterialParameter.h" +#include "MCPUtils.h" +#include "MCPServer.h" + +TMap MCPMaterialParameter::GetMaterialParameters(UMaterialInterface* Material) +{ + TMap Result; + if (!Material) return Result; + TMap Temp; + for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++) + { + Material->GetAllParametersOfType((EMaterialParameterType)i, Temp); + Result.Append(Temp); + } + return Result; +} + +bool MCPMaterialParameter::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation) +{ + if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase)) + OutAssociation = GlobalParameter; + else if (Str.Equals(TEXT("Layer"), ESearchCase::IgnoreCase)) + OutAssociation = LayerParameter; + else if (Str.Equals(TEXT("Blend"), ESearchCase::IgnoreCase)) + OutAssociation = BlendParameter; + else + { + UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str); + return false; + } + return true; +} + +void MCPMaterialParameter::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta) +{ + // Association prefix for layer/blend parameters. + FString Prefix; + if (Info.Association == LayerParameter) + Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index); + else if (Info.Association == BlendParameter) + Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index); + + switch (Meta.Value.Type) + { + case EMaterialParameterType::Scalar: + UMCPServer::Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar()); + break; + case EMaterialParameterType::Vector: + { + FLinearColor C = Meta.Value.AsLinearColor(); + UMCPServer::Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"), + *Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A); + break; + } + case EMaterialParameterType::DoubleVector: + { + FVector4d V = Meta.Value.AsVector4d(); + UMCPServer::Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"), + *Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W); + break; + } + case EMaterialParameterType::Texture: + { + UTexture* Tex = Cast(Meta.Value.AsTextureObject()); + UMCPServer::Printf(TEXT(" %sTexture \"%s\" = %s\n"), + *Prefix, *Info.Name.ToString(), Tex ? *MCPUtils::FormatName(Tex) : TEXT("None")); + break; + } + case EMaterialParameterType::StaticSwitch: + UMCPServer::Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"), + *Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false")); + break; + default: + UMCPServer::Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString()); + break; + } +} + diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h index 33dbcbdf..eff2961f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h @@ -54,15 +54,6 @@ public: MCPFetcher& Component(const FString& Value); MCPFetcher& LevelBlueprint(const FString& Value); - // The following walkers cannot be invoked from - // paths, only procedurally. If the current object - // is already the target type, they do nothing. - // Otherwise, they attempt to convert (e.g. World - // to its level blueprint, Material to its graph). - // - MCPFetcher& ToBlueprint(); - MCPFetcher& ToGraph(); - // Return true if there haven't been any errors. // Note that errors always automatically generate // output to MCPServer::Printf. @@ -77,8 +68,7 @@ public: { if (bError) return nullptr; T* Result = ::Cast(Obj); - if (Result == nullptr) - TypeMismatch(TEXT("Cast"), *T::StaticClass()->GetName()); + if (Result == nullptr) PathFailed(*T::StaticClass()->GetName()); return Result; } @@ -144,6 +134,7 @@ private: void SetObj(UObject* InObj); void SetPin(UEdGraphPin* InPin); MCPFetcher& SetError(); + void PathFailed(const TCHAR *Kind); MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected); bool CheckAssetIsA(UClass* StaticClass); WalkFunc GetWalker(const FString &Step); @@ -152,7 +143,6 @@ private: template<> inline UEdGraphPin* MCPFetcher::Cast() { if (bError) return nullptr; - if (!ResultPin) - TypeMismatch(TEXT("Cast"), TEXT("pin")); + if (!ResultPin) PathFailed(TEXT("UEdGraphPin")); return ResultPin; } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index 6909b60f..dc0e4390 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -153,11 +153,6 @@ public: static FString ActionFullName(const TSharedPtr& Action); static TArray> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false); - // ----- Material Parameters ----- - static TMap GetMaterialParameters(UMaterialInterface* Material); - static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation); - static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta); - // ----- Editable template ----- static TArray SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal); static FProperty* FindPropertyByName(UObject* Obj, const FString& Name); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MaterialParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MaterialParameter.h new file mode 100644 index 00000000..a5d4622b --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MaterialParameter.h @@ -0,0 +1,13 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Materials/Material.h" + +class MCPMaterialParameter +{ +public: + static TMap GetMaterialParameters(UMaterialInterface* Material); + static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation); + static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta); +}; + diff --git a/tools/mcp-bridge.py b/tools/mcp-bridge.py index 527348ee..0150fcc5 100644 --- a/tools/mcp-bridge.py +++ b/tools/mcp-bridge.py @@ -20,7 +20,6 @@ TOOL_DESCRIPTION = ( "Send a command to the Unreal Editor's BlueprintMCP plugin. " "The 'command' field specifies which operation to perform; " "additional fields are command-specific parameters. " - 'Use {"command": "ShowCommands"} to list available commands. ' 'Use {"command": "UserManual"} to get an overview. ' "If the editor is not running, the call will return an error; " "just ask the user to start the editor and try again."