Add MCPErrorCallback. Simplify error handling.
This commit is contained in:
@@ -200,7 +200,7 @@ FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* Ou
|
||||
// Load helpers
|
||||
// ============================================================
|
||||
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FString& OutError)
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error)
|
||||
{
|
||||
// Regular blueprint asset
|
||||
UBlueprint* BP = Cast<UBlueprint>(Asset.GetAsset());
|
||||
@@ -214,20 +214,22 @@ UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FS
|
||||
if (LevelBP) return LevelBP;
|
||||
}
|
||||
|
||||
OutError = FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
|
||||
*Asset.AssetName.ToString());
|
||||
Error.SetError(FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
|
||||
*Asset.AssetName.ToString()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, FString& OutError)
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error)
|
||||
{
|
||||
FAssetData* Asset = FindAsset(BlueprintsAndMaps, NameOrPath, &OutError);
|
||||
FString FindError;
|
||||
FAssetData* Asset = FindAsset(BlueprintsAndMaps, NameOrPath, &FindError);
|
||||
if (!Asset)
|
||||
{
|
||||
if (OutError.IsEmpty())
|
||||
OutError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
|
||||
if (FindError.IsEmpty())
|
||||
FindError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
|
||||
Error.SetError(FindError);
|
||||
return nullptr;
|
||||
}
|
||||
return LoadBlueprintOrLevelBlueprint(*Asset, OutError);
|
||||
return LoadBlueprintOrLevelBlueprint(*Asset, Error);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,12 +131,8 @@ void FBlueprintMCPServer::HandleAddEventDispatcher(const FJsonObject* Json, FJso
|
||||
if (ParamName.IsEmpty() || ParamType.IsEmpty()) continue;
|
||||
|
||||
FEdGraphPinType PinType;
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
TEXT("Parameter '%s': %s"), *ParamName, *TypeError));
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, Result))
|
||||
return;
|
||||
|
||||
EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output);
|
||||
|
||||
|
||||
@@ -145,12 +145,8 @@ void FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FJsonObject*
|
||||
}
|
||||
|
||||
// Load the Material Instance
|
||||
FString LoadError;
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, LoadError);
|
||||
if (!MI)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, Result);
|
||||
if (!MI) return;
|
||||
|
||||
// Determine the parameter type — explicit or auto-detect from parent
|
||||
FString TypeStr;
|
||||
@@ -366,12 +362,8 @@ void FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const FJsonObject*
|
||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required query parameter: name"));
|
||||
}
|
||||
|
||||
FString LoadError;
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(NameParam, LoadError);
|
||||
if (!MI)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(NameParam, Result);
|
||||
if (!MI) return;
|
||||
|
||||
Result->SetStringField(TEXT("name"), MI->GetName());
|
||||
Result->SetStringField(TEXT("path"), MI->GetPathName());
|
||||
@@ -614,12 +606,8 @@ void FBlueprintMCPServer::HandleReparentMaterialInstance(const FJsonObject* Json
|
||||
}
|
||||
|
||||
// Load the Material Instance
|
||||
FString LoadError;
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, LoadError);
|
||||
if (!MI)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, Result);
|
||||
if (!MI) return;
|
||||
|
||||
// Capture old parent
|
||||
FString OldParentPath = MI->Parent ? MI->Parent->GetPathName() : TEXT("None");
|
||||
|
||||
@@ -218,12 +218,8 @@ void FBlueprintMCPServer::HandleSetMaterialProperty(const FJsonObject* Json, FJs
|
||||
Json->TryGetBoolField(TEXT("dryRun"), bDryRun);
|
||||
|
||||
// Load material
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
FString OldValue;
|
||||
FString NewValue;
|
||||
@@ -562,17 +558,15 @@ void FBlueprintMCPServer::HandleAddMaterialExpression(const FJsonObject* Json, F
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both"));
|
||||
}
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
Owner = MatFunc;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
Owner = Material;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
@@ -700,16 +694,14 @@ void FBlueprintMCPServer::HandleDeleteMaterialExpression(const FJsonObject* Json
|
||||
|
||||
if (!MaterialFunctionName.IsEmpty())
|
||||
{
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
|
||||
@@ -827,16 +819,14 @@ void FBlueprintMCPServer::HandleConnectMaterialPins(const FJsonObject* Json, FJs
|
||||
|
||||
if (!MaterialFunctionName.IsEmpty())
|
||||
{
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
|
||||
@@ -978,16 +968,14 @@ void FBlueprintMCPServer::HandleDisconnectMaterialPin(const FJsonObject* Json, F
|
||||
|
||||
if (!MaterialFunctionName.IsEmpty())
|
||||
{
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
|
||||
@@ -1100,16 +1088,14 @@ void FBlueprintMCPServer::HandleSetExpressionValue(const FJsonObject* Json, FJso
|
||||
|
||||
if (!MaterialFunctionName.IsEmpty())
|
||||
{
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
|
||||
@@ -1377,16 +1363,14 @@ void FBlueprintMCPServer::HandleMoveMaterialExpression(const FJsonObject* Json,
|
||||
|
||||
if (!MaterialFunctionName.IsEmpty())
|
||||
{
|
||||
FString LoadError;
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||
if (!MatFunc) return;
|
||||
AssetDisplayName = MatFunc->GetName();
|
||||
}
|
||||
else
|
||||
{
|
||||
FString LoadError;
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
AssetDisplayName = Material->GetName();
|
||||
}
|
||||
|
||||
@@ -1550,12 +1534,8 @@ void FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FJsonObject* Json, F
|
||||
}
|
||||
|
||||
// Load material
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
MCPUtils::EnsureMaterialGraph(Material);
|
||||
if (!Material->MaterialGraph)
|
||||
@@ -1649,12 +1629,8 @@ void FBlueprintMCPServer::HandleDiffMaterialGraph(const FJsonObject* Json, FJson
|
||||
}
|
||||
|
||||
// Load material
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
MCPUtils::EnsureMaterialGraph(Material);
|
||||
if (!Material->MaterialGraph)
|
||||
@@ -1815,12 +1791,8 @@ void FBlueprintMCPServer::HandleRestoreMaterialGraph(const FJsonObject* Json, FJ
|
||||
}
|
||||
|
||||
// Load material
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
MCPUtils::EnsureMaterialGraph(Material);
|
||||
if (!Material->MaterialGraph)
|
||||
|
||||
@@ -361,12 +361,8 @@ void FBlueprintMCPServer::HandleGetMaterialGraph(const FJsonObject* Json, FJsonO
|
||||
|
||||
FString DecodedName = MCPUtils::UrlDecode(Name);
|
||||
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialGraph — material '%s'"), *Material->GetName());
|
||||
|
||||
@@ -411,12 +407,8 @@ void FBlueprintMCPServer::HandleDescribeMaterial(const FJsonObject* Json, FJsonO
|
||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: material"));
|
||||
}
|
||||
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *Material->GetName());
|
||||
|
||||
@@ -798,12 +790,8 @@ void FBlueprintMCPServer::HandleGetMaterialFunction(const FJsonObject* Json, FJs
|
||||
|
||||
FString DecodedName = MCPUtils::UrlDecode(Name);
|
||||
|
||||
FString LoadError;
|
||||
UMaterialFunction* MF = UMCPAssetFinder::LoadAsset<UMaterialFunction>(DecodedName, LoadError);
|
||||
if (!MF)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterialFunction* MF = UMCPAssetFinder::LoadAsset<UMaterialFunction>(DecodedName, Result);
|
||||
if (!MF) return;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName());
|
||||
|
||||
@@ -882,12 +870,8 @@ void FBlueprintMCPServer::HandleValidateMaterial(const FJsonObject* Json, FJsonO
|
||||
}
|
||||
|
||||
// Load material
|
||||
FString LoadError;
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
||||
if (!Material)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||
if (!Material) return;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *Material->GetName());
|
||||
|
||||
|
||||
@@ -37,11 +37,8 @@ void FBlueprintMCPServer::HandleChangeFunctionParamType(const FJsonObject* Json,
|
||||
|
||||
// Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references)
|
||||
FEdGraphPinType NewPinType;
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(NewTypeName, NewPinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(NewTypeName, NewPinType, Result))
|
||||
return;
|
||||
|
||||
// Find the entry node: K2Node_FunctionEntry in a function graph,
|
||||
// or K2Node_CustomEvent in any graph
|
||||
@@ -416,11 +413,8 @@ void FBlueprintMCPServer::HandleAddFunctionParameter(const FJsonObject* Json, FJ
|
||||
|
||||
// Resolve param type
|
||||
FEdGraphPinType PinType;
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, Result))
|
||||
return;
|
||||
|
||||
// Find the entry node using 3 strategies
|
||||
UK2Node_EditablePinBase* EntryNode = nullptr;
|
||||
|
||||
@@ -121,12 +121,8 @@ void FBlueprintMCPServer::HandleGetBlueprint(const FJsonObject* Json, FJsonObjec
|
||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
|
||||
}
|
||||
|
||||
FString LoadError;
|
||||
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Name, LoadError);
|
||||
if (!BP)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
||||
}
|
||||
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Name, Result);
|
||||
if (!BP) return;
|
||||
|
||||
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(BP);
|
||||
MCPUtils::CopyJsonFields(&*Tmp, Result);
|
||||
|
||||
@@ -238,20 +238,13 @@ public:
|
||||
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||
{
|
||||
// Find the struct
|
||||
FString StructError;
|
||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, StructError);
|
||||
if (!Struct)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, StructError);
|
||||
}
|
||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result);
|
||||
if (!Struct) return;
|
||||
|
||||
// Resolve type
|
||||
FEdGraphPinType PinType;
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(Type, PinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *Type, *TypeError));
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(Type, PinType, Result))
|
||||
return;
|
||||
|
||||
// Snapshot existing GUIDs so we can find the newly added one
|
||||
TSet<FGuid> ExistingGuids;
|
||||
@@ -311,12 +304,8 @@ public:
|
||||
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||
{
|
||||
// Find the struct
|
||||
FString StructError;
|
||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, StructError);
|
||||
if (!Struct)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, StructError);
|
||||
}
|
||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result);
|
||||
if (!Struct) return;
|
||||
|
||||
// Find the property GUID by name
|
||||
FGuid TargetGuid;
|
||||
|
||||
@@ -68,11 +68,8 @@ void FBlueprintMCPServer::HandleChangeVariableType(const FJsonObject* Json, FJso
|
||||
ResolveInput = TypeCategory + TEXT(":") + NewTypeName;
|
||||
}
|
||||
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(ResolveInput, NewPinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(ResolveInput, NewPinType, Result))
|
||||
return;
|
||||
|
||||
// Derive typeCategory from the resolved pin type for the response
|
||||
if (TypeCategory.IsEmpty())
|
||||
@@ -270,11 +267,8 @@ void FBlueprintMCPServer::HandleAddVariable(const FJsonObject* Json, FJsonObject
|
||||
|
||||
// Resolve the type using the shared helper
|
||||
FEdGraphPinType PinType;
|
||||
FString TypeError;
|
||||
if (!MCPUtils::ResolveTypeFromString(VariableType, PinType, TypeError))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
||||
}
|
||||
if (!MCPUtils::ResolveTypeFromString(VariableType, PinType, Result))
|
||||
return;
|
||||
|
||||
// Set container type for arrays
|
||||
if (bIsArray)
|
||||
|
||||
@@ -78,6 +78,22 @@ extern int32 TrySavePackageSEH(
|
||||
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// MCPErrorCallback
|
||||
// ============================================================
|
||||
|
||||
MCPErrorCallback::MCPErrorCallback(std::nullptr_t)
|
||||
: Func([](const FString&) {})
|
||||
{}
|
||||
|
||||
MCPErrorCallback::MCPErrorCallback(FString& OutError)
|
||||
: Func([&OutError](const FString& Msg) { OutError = Msg; })
|
||||
{}
|
||||
|
||||
MCPErrorCallback::MCPErrorCallback(FJsonObject* Result)
|
||||
: Func([Result](const FString& Msg) { MCPUtils::MakeErrorJson(Result, Msg); })
|
||||
{}
|
||||
|
||||
// ============================================================
|
||||
// JSON helpers
|
||||
// ============================================================
|
||||
@@ -703,7 +719,7 @@ UClass* MCPUtils::FindClassByName(const FString& ClassName)
|
||||
}
|
||||
|
||||
bool MCPUtils::ResolveTypeFromString(
|
||||
const FString& TypeName, FEdGraphPinType& OutPinType, FString& OutError)
|
||||
const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error)
|
||||
{
|
||||
FString TypeLower = TypeName.ToLower();
|
||||
|
||||
@@ -776,7 +792,7 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
UClass* FoundClass = FindClassByName(ClassName);
|
||||
if (!FoundClass)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Class '%s' not found for object reference type"), *ClassName);
|
||||
Error.SetError(FString::Printf(TEXT("Class '%s' not found for object reference type"), *ClassName));
|
||||
return false;
|
||||
}
|
||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
|
||||
@@ -788,7 +804,7 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
UClass* FoundClass = FindClassByName(ClassName);
|
||||
if (!FoundClass)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Class '%s' not found for soft object reference type"), *ClassName);
|
||||
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft object reference type"), *ClassName));
|
||||
return false;
|
||||
}
|
||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
|
||||
@@ -800,7 +816,7 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
UClass* FoundClass = FindClassByName(ClassName);
|
||||
if (!FoundClass)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Class '%s' not found for class reference type (TSubclassOf)"), *ClassName);
|
||||
Error.SetError(FString::Printf(TEXT("Class '%s' not found for class reference type (TSubclassOf)"), *ClassName));
|
||||
return false;
|
||||
}
|
||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Class;
|
||||
@@ -812,7 +828,7 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
UClass* FoundClass = FindClassByName(ClassName);
|
||||
if (!FoundClass)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Class '%s' not found for soft class reference type"), *ClassName);
|
||||
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft class reference type"), *ClassName));
|
||||
return false;
|
||||
}
|
||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
|
||||
@@ -824,7 +840,7 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
UClass* FoundClass = FindClassByName(ClassName);
|
||||
if (!FoundClass)
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Class '%s' not found for interface reference type"), *ClassName);
|
||||
Error.SetError(FString::Printf(TEXT("Class '%s' not found for interface reference type"), *ClassName));
|
||||
return false;
|
||||
}
|
||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Interface;
|
||||
@@ -900,9 +916,9 @@ bool MCPUtils::ResolveTypeFromString(
|
||||
}
|
||||
else
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
Error.SetError(FString::Printf(
|
||||
TEXT("Unknown type '%s'. Use: bool, int, float, string, name, text, byte, vector, rotator, transform, object, a struct/enum name (e.g. FVector, EMyEnum), or colon syntax for references (object:Actor, softobject:Actor, class:Actor, softclass:Actor, interface:MyInterface)"),
|
||||
*TypeName);
|
||||
*TypeName));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
5
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/TODO.md
Normal file
5
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/TODO.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# BlueprintMCP TODO
|
||||
|
||||
- When compiling blueprints, always check for errors properly. Many handlers call `FKismetEditorUtilities::CompileBlueprint` and assume success without checking. The validation handler (`MCPHandlers_Validation.h`) already has proper error collection logic — all compile sites should follow that pattern.
|
||||
|
||||
- Add `FindBlueprintGraph` to `UMCPAssetFinder`: load blueprint, URL-decode graph name, search all graphs by name, return `UEdGraph*` or nullptr with error. Currently ~10 handlers duplicate this 25-line pattern (see `MCPHandlers_Mutation.h:186-210` for a typical example).
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "StructUtils/UserDefinedStruct.h"
|
||||
#include "Engine/UserDefinedEnum.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "MCPAssetFinder.generated.h"
|
||||
|
||||
class UBlueprint;
|
||||
@@ -45,36 +46,38 @@ public:
|
||||
static TArray<FAssetData*> SearchAssets(FName Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
static TArray<FAssetData*> SearchAssets(UClass *Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
|
||||
// Load an asset from an FAssetData. Returns nullptr and sets OutError on failure.
|
||||
// Load an asset from an FAssetData. Returns nullptr and reports error on failure.
|
||||
template<typename T>
|
||||
static T* LoadAsset(FAssetData& Asset, FString& OutError)
|
||||
static T* LoadAsset(FAssetData& Asset, MCPErrorCallback Error)
|
||||
{
|
||||
T* Result = Cast<T>(Asset.GetAsset());
|
||||
if (!Result)
|
||||
OutError = FString::Printf(TEXT("Asset '%s' found but could not be loaded as %s."),
|
||||
*Asset.AssetName.ToString(), *T::StaticClass()->GetName());
|
||||
Error.SetError(FString::Printf(TEXT("Asset '%s' found but could not be loaded as %s."),
|
||||
*Asset.AssetName.ToString(), *T::StaticClass()->GetName()));
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Load an asset by name or path. Returns nullptr and sets OutError on failure.
|
||||
// Load an asset by name or path. Returns nullptr and reports error on failure.
|
||||
template<typename T>
|
||||
static T* LoadAsset(const FString& NameOrPath, FString& OutError)
|
||||
static T* LoadAsset(const FString& NameOrPath, MCPErrorCallback Error)
|
||||
{
|
||||
FAssetData* Asset = FindAsset(T::StaticClass(), NameOrPath, &OutError);
|
||||
FString FindError;
|
||||
FAssetData* Asset = FindAsset(T::StaticClass(), NameOrPath, &FindError);
|
||||
if (!Asset)
|
||||
{
|
||||
if (OutError.IsEmpty())
|
||||
OutError = FString::Printf(TEXT("'%s' not found."), *NameOrPath);
|
||||
if (FindError.IsEmpty())
|
||||
FindError = FString::Printf(TEXT("'%s' not found."), *NameOrPath);
|
||||
Error.SetError(FindError);
|
||||
return nullptr;
|
||||
}
|
||||
return LoadAsset<T>(*Asset, OutError);
|
||||
return LoadAsset<T>(*Asset, Error);
|
||||
}
|
||||
|
||||
static FAssetData* FindAnyAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||
|
||||
// Load a blueprint or level blueprint from an asset (handles UWorld → level blueprint extraction).
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FString& OutError);
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, FString& OutError);
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error);
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error);
|
||||
|
||||
private:
|
||||
// Fetch assets from the Unreal asset registry and store them locally.
|
||||
|
||||
@@ -100,6 +100,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct MCPErrorCallback;
|
||||
|
||||
// Stateless utility functions used by MCP handlers and the MCP server.
|
||||
// This is effectively a namespace — all methods are static.
|
||||
class MCPUtils
|
||||
@@ -141,7 +143,7 @@ public:
|
||||
|
||||
// ----- Type resolution -----
|
||||
static UClass* FindClassByName(const FString& ClassName);
|
||||
static bool ResolveTypeFromString(const FString& TypeName, FEdGraphPinType& OutPinType, FString& OutError);
|
||||
static bool ResolveTypeFromString(const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error);
|
||||
|
||||
// ----- Material helpers -----
|
||||
static void EnsureMaterialGraph(UMaterial* Material);
|
||||
@@ -166,3 +168,16 @@ public:
|
||||
private:
|
||||
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
|
||||
};
|
||||
|
||||
// ----- Error callback -----
|
||||
|
||||
struct MCPErrorCallback
|
||||
{
|
||||
TFunction<void(const FString&)> Func;
|
||||
|
||||
MCPErrorCallback(std::nullptr_t);
|
||||
MCPErrorCallback(FString& OutError);
|
||||
MCPErrorCallback(FJsonObject* Result);
|
||||
|
||||
void SetError(const FString& Msg) const { Func(Msg); }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user