@@ -50,6 +50,14 @@ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleCommand(
5050 {
5151 return HandleGetPropertyValidTypes (Params);
5252 }
53+ if (CommandType == TEXT (" search_class_paths" ))
54+ {
55+ return HandleSearchClassPaths (Params);
56+ }
57+ if (CommandType == TEXT (" get_mass_config_traits" ))
58+ {
59+ return HandleGetMassConfigTraits (Params);
60+ }
5361
5462 return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
5563 FString::Printf (TEXT (" Unknown data asset command: %s" ), *CommandType));
@@ -776,3 +784,259 @@ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleListDataAssets(
776784 Result->SetArrayField (TEXT (" assets" ), AssetArray);
777785 return Result;
778786}
787+
788+ // ============================================================================
789+ // search_class_paths — Search UClass by name filter + parent class constraint
790+ // ============================================================================
791+
792+ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleSearchClassPaths (
793+ const TSharedPtr<FJsonObject>& Params)
794+ {
795+ FString Filter;
796+ Params->TryGetStringField (TEXT (" filter" ), Filter);
797+
798+ FString ParentClassName;
799+ Params->TryGetStringField (TEXT (" parent_class" ), ParentClassName);
800+
801+ int32 MaxResults = 50 ;
802+ if (Params->HasField (TEXT (" max_results" )))
803+ {
804+ MaxResults = static_cast <int32>(Params->GetNumberField (TEXT (" max_results" )));
805+ }
806+
807+ bool bIncludeProperties = false ;
808+ if (Params->HasField (TEXT (" include_properties" )))
809+ {
810+ bIncludeProperties = Params->GetBoolField (TEXT (" include_properties" ));
811+ }
812+
813+ // Resolve parent class constraint
814+ UClass* ParentClass = UObject::StaticClass (); // Default: search everything
815+ if (!ParentClassName.IsEmpty ())
816+ {
817+ // Try direct path first (e.g., "/Script/MassEntity.MassEntityTraitBase")
818+ UClass* Found = FindObject<UClass>(nullptr , *ParentClassName);
819+
820+ // Try common patterns
821+ if (!Found)
822+ {
823+ // Search all classes for name match
824+ for (TObjectIterator<UClass> It; It; ++It)
825+ {
826+ UClass* C = *It;
827+ if (!C)
828+ {
829+ continue ;
830+ }
831+
832+ const FString Name = C->GetName ();
833+ if (Name == ParentClassName ||
834+ Name == (TEXT (" U" ) + ParentClassName) ||
835+ Name == (TEXT (" F" ) + ParentClassName) ||
836+ Name == ParentClassName.RightChop (1 ))
837+ {
838+ Found = C;
839+ break ;
840+ }
841+ }
842+ }
843+
844+ if (!Found)
845+ {
846+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
847+ FString::Printf (TEXT (" Parent class not found: %s" ), *ParentClassName));
848+ }
849+ ParentClass = Found;
850+ }
851+
852+ // Get all derived classes
853+ TArray<UClass*> Derived;
854+ GetDerivedClasses (ParentClass, Derived, true );
855+
856+ // Also include the parent class itself if it matches
857+ if (!ParentClass->HasAnyClassFlags (CLASS_Abstract))
858+ {
859+ Derived.Insert (ParentClass, 0 );
860+ }
861+
862+ // Filter by name keyword
863+ TArray<TSharedPtr<FJsonValue>> ResultArray;
864+ const FString FilterLower = Filter.ToLower ();
865+
866+ for (UClass* C : Derived)
867+ {
868+ if (!C || C->HasAnyClassFlags (CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists))
869+ {
870+ continue ;
871+ }
872+
873+ const FString ClassName = C->GetName ();
874+
875+ // Apply name filter
876+ if (!Filter.IsEmpty () && !ClassName.ToLower ().Contains (FilterLower))
877+ {
878+ continue ;
879+ }
880+
881+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
882+ Entry->SetStringField (TEXT (" name" ), ClassName);
883+ Entry->SetStringField (TEXT (" class_path" ), C->GetPathName ());
884+
885+ // Include editable properties if requested
886+ if (bIncludeProperties)
887+ {
888+ TArray<TSharedPtr<FJsonValue>> PropsArray;
889+ for (TFieldIterator<FProperty> PropIt (C, EFieldIteratorFlags::ExcludeSuper); PropIt; ++PropIt)
890+ {
891+ FProperty* Prop = *PropIt;
892+ if (!Prop || !Prop->HasAnyPropertyFlags (CPF_Edit))
893+ {
894+ continue ;
895+ }
896+
897+ TSharedPtr<FJsonObject> PropObj = MakeShared<FJsonObject>();
898+ PropObj->SetStringField (TEXT (" name" ), Prop->GetName ());
899+ PropObj->SetStringField (TEXT (" type" ), Prop->GetCPPType ());
900+
901+ // Get default value
902+ const UObject* CDO = C->GetDefaultObject ();
903+ if (CDO )
904+ {
905+ const void * ValuePtr = Prop->ContainerPtrToValuePtr <void >(CDO );
906+ FString DefaultStr;
907+ Prop->ExportTextItem_Direct (DefaultStr, ValuePtr, nullptr , nullptr , PPF_None);
908+ if (!DefaultStr.IsEmpty ())
909+ {
910+ PropObj->SetStringField (TEXT (" default" ), DefaultStr);
911+ }
912+ }
913+
914+ PropsArray.Add (MakeShared<FJsonValueObject>(PropObj));
915+ }
916+ Entry->SetArrayField (TEXT (" properties" ), PropsArray);
917+ }
918+
919+ ResultArray.Add (MakeShared<FJsonValueObject>(Entry));
920+
921+ if (ResultArray.Num () >= MaxResults)
922+ {
923+ break ;
924+ }
925+ }
926+
927+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
928+ Result->SetNumberField (TEXT (" count" ), ResultArray.Num ());
929+ Result->SetStringField (TEXT (" parent_class" ), ParentClass->GetName ());
930+ if (!Filter.IsEmpty ())
931+ {
932+ Result->SetStringField (TEXT (" filter" ), Filter);
933+ }
934+ Result->SetArrayField (TEXT (" classes" ), ResultArray);
935+ return Result;
936+ }
937+
938+ // ============================================================================
939+ // get_mass_config_traits — Read all traits from a Mass Entity Config with
940+ // their properties expanded (not just object paths)
941+ // ============================================================================
942+
943+ TSharedPtr<FJsonObject> FEpicUnrealMCPDataAssetCommands::HandleGetMassConfigTraits (
944+ const TSharedPtr<FJsonObject>& Params)
945+ {
946+ FString AssetPath;
947+ if (!Params->TryGetStringField (TEXT (" asset_path" ), AssetPath))
948+ {
949+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Missing 'asset_path'" ));
950+ }
951+
952+ UObject* Asset = StaticLoadObject (UObject::StaticClass (), nullptr , *AssetPath);
953+ if (!Asset)
954+ {
955+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (
956+ FString::Printf (TEXT (" Asset not found: %s" ), *AssetPath));
957+ }
958+
959+ // Find the Config struct property
960+ FStructProperty* ConfigProp = nullptr ;
961+ for (TFieldIterator<FStructProperty> It (Asset->GetClass ()); It; ++It)
962+ {
963+ if (It->GetName () == TEXT (" Config" ))
964+ {
965+ ConfigProp = *It;
966+ break ;
967+ }
968+ }
969+
970+ if (!ConfigProp)
971+ {
972+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Asset has no 'Config' struct property (not a MassEntityConfigAsset?)" ));
973+ }
974+
975+ const void * ConfigPtr = ConfigProp->ContainerPtrToValuePtr <void >(Asset);
976+
977+ // Find the Traits array inside the Config struct
978+ FArrayProperty* TraitsArrayProp = nullptr ;
979+ for (TFieldIterator<FArrayProperty> It (ConfigProp->Struct ); It; ++It)
980+ {
981+ if (It->GetName () == TEXT (" Traits" ))
982+ {
983+ TraitsArrayProp = *It;
984+ break ;
985+ }
986+ }
987+
988+ if (!TraitsArrayProp)
989+ {
990+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Config struct has no 'Traits' array" ));
991+ }
992+
993+ FObjectProperty* InnerObjProp = CastField<FObjectProperty>(TraitsArrayProp->Inner );
994+ if (!InnerObjProp)
995+ {
996+ return FEpicUnrealMCPCommonUtils::CreateErrorResponse (TEXT (" Traits array inner type is not an object" ));
997+ }
998+
999+ // Read the array
1000+ FScriptArrayHelper ArrayHelper (TraitsArrayProp, TraitsArrayProp->ContainerPtrToValuePtr <void >(ConfigPtr));
1001+
1002+ TArray<TSharedPtr<FJsonValue>> TraitsArray;
1003+ for (int32 i = 0 ; i < ArrayHelper.Num (); ++i)
1004+ {
1005+ UObject* TraitObj = InnerObjProp->GetObjectPropertyValue (ArrayHelper.GetRawPtr (i));
1006+ if (!TraitObj)
1007+ {
1008+ continue ;
1009+ }
1010+
1011+ TSharedPtr<FJsonObject> TraitJson = MakeShared<FJsonObject>();
1012+ TraitJson->SetNumberField (TEXT (" index" ), i);
1013+ TraitJson->SetStringField (TEXT (" name" ), TraitObj->GetName ());
1014+ TraitJson->SetStringField (TEXT (" class" ), TraitObj->GetClass ()->GetName ());
1015+ TraitJson->SetStringField (TEXT (" class_path" ), TraitObj->GetClass ()->GetPathName ());
1016+
1017+ // Serialize all editable properties
1018+ TSharedPtr<FJsonObject> PropsJson = MakeShared<FJsonObject>();
1019+ for (TFieldIterator<FProperty> PropIt (TraitObj->GetClass (), EFieldIteratorFlags::ExcludeSuper); PropIt; ++PropIt)
1020+ {
1021+ FProperty* Prop = *PropIt;
1022+ if (!Prop || !Prop->HasAnyPropertyFlags (CPF_Edit))
1023+ {
1024+ continue ;
1025+ }
1026+
1027+ const void * ValuePtr = Prop->ContainerPtrToValuePtr <void >(TraitObj);
1028+ FString ValueStr;
1029+ Prop->ExportTextItem_Direct (ValueStr, ValuePtr, nullptr , nullptr , PPF_None);
1030+ PropsJson->SetStringField (Prop->GetName (), ValueStr);
1031+ }
1032+ TraitJson->SetObjectField (TEXT (" properties" ), PropsJson);
1033+
1034+ TraitsArray.Add (MakeShared<FJsonValueObject>(TraitJson));
1035+ }
1036+
1037+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1038+ Result->SetStringField (TEXT (" asset_path" ), AssetPath);
1039+ Result->SetNumberField (TEXT (" trait_count" ), TraitsArray.Num ());
1040+ Result->SetArrayField (TEXT (" traits" ), TraitsArray);
1041+ return Result;
1042+ }
0 commit comments