Add MCPErrorCallback. Simplify error handling.
This commit is contained in:
@@ -200,7 +200,7 @@ FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* Ou
|
|||||||
// Load helpers
|
// Load helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FString& OutError)
|
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error)
|
||||||
{
|
{
|
||||||
// Regular blueprint asset
|
// Regular blueprint asset
|
||||||
UBlueprint* BP = Cast<UBlueprint>(Asset.GetAsset());
|
UBlueprint* BP = Cast<UBlueprint>(Asset.GetAsset());
|
||||||
@@ -214,20 +214,22 @@ UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FS
|
|||||||
if (LevelBP) return LevelBP;
|
if (LevelBP) return LevelBP;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutError = FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
|
Error.SetError(FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
|
||||||
*Asset.AssetName.ToString());
|
*Asset.AssetName.ToString()));
|
||||||
return nullptr;
|
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 (!Asset)
|
||||||
{
|
{
|
||||||
if (OutError.IsEmpty())
|
if (FindError.IsEmpty())
|
||||||
OutError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
|
FindError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
|
||||||
|
Error.SetError(FindError);
|
||||||
return nullptr;
|
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;
|
if (ParamName.IsEmpty() || ParamType.IsEmpty()) continue;
|
||||||
|
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
||||||
TEXT("Parameter '%s': %s"), *ParamName, *TypeError));
|
|
||||||
}
|
|
||||||
|
|
||||||
EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output);
|
EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output);
|
||||||
|
|
||||||
|
|||||||
@@ -145,12 +145,8 @@ void FBlueprintMCPServer::HandleSetMaterialInstanceParameter(const FJsonObject*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the Material Instance
|
// Load the Material Instance
|
||||||
FString LoadError;
|
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, Result);
|
||||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, LoadError);
|
if (!MI) return;
|
||||||
if (!MI)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the parameter type — explicit or auto-detect from parent
|
// Determine the parameter type — explicit or auto-detect from parent
|
||||||
FString TypeStr;
|
FString TypeStr;
|
||||||
@@ -366,12 +362,8 @@ void FBlueprintMCPServer::HandleGetMaterialInstanceParameters(const FJsonObject*
|
|||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required query parameter: name"));
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required query parameter: name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
FString LoadError;
|
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(NameParam, Result);
|
||||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(NameParam, LoadError);
|
if (!MI) return;
|
||||||
if (!MI)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result->SetStringField(TEXT("name"), MI->GetName());
|
Result->SetStringField(TEXT("name"), MI->GetName());
|
||||||
Result->SetStringField(TEXT("path"), MI->GetPathName());
|
Result->SetStringField(TEXT("path"), MI->GetPathName());
|
||||||
@@ -614,12 +606,8 @@ void FBlueprintMCPServer::HandleReparentMaterialInstance(const FJsonObject* Json
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the Material Instance
|
// Load the Material Instance
|
||||||
FString LoadError;
|
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, Result);
|
||||||
UMaterialInstanceConstant* MI = UMCPAssetFinder::LoadAsset<UMaterialInstanceConstant>(MIName, LoadError);
|
if (!MI) return;
|
||||||
if (!MI)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture old parent
|
// Capture old parent
|
||||||
FString OldParentPath = MI->Parent ? MI->Parent->GetPathName() : TEXT("None");
|
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);
|
Json->TryGetBoolField(TEXT("dryRun"), bDryRun);
|
||||||
|
|
||||||
// Load material
|
// Load material
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
FString OldValue;
|
FString OldValue;
|
||||||
FString NewValue;
|
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"));
|
return MCPUtils::MakeErrorJson(Result, TEXT("Specify either 'material' or 'materialFunction', not both"));
|
||||||
}
|
}
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
Owner = MatFunc;
|
Owner = MatFunc;
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
Owner = Material;
|
Owner = Material;
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
@@ -700,16 +694,14 @@ void FBlueprintMCPServer::HandleDeleteMaterialExpression(const FJsonObject* Json
|
|||||||
|
|
||||||
if (!MaterialFunctionName.IsEmpty())
|
if (!MaterialFunctionName.IsEmpty())
|
||||||
{
|
{
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,16 +819,14 @@ void FBlueprintMCPServer::HandleConnectMaterialPins(const FJsonObject* Json, FJs
|
|||||||
|
|
||||||
if (!MaterialFunctionName.IsEmpty())
|
if (!MaterialFunctionName.IsEmpty())
|
||||||
{
|
{
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,16 +968,14 @@ void FBlueprintMCPServer::HandleDisconnectMaterialPin(const FJsonObject* Json, F
|
|||||||
|
|
||||||
if (!MaterialFunctionName.IsEmpty())
|
if (!MaterialFunctionName.IsEmpty())
|
||||||
{
|
{
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,16 +1088,14 @@ void FBlueprintMCPServer::HandleSetExpressionValue(const FJsonObject* Json, FJso
|
|||||||
|
|
||||||
if (!MaterialFunctionName.IsEmpty())
|
if (!MaterialFunctionName.IsEmpty())
|
||||||
{
|
{
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1377,16 +1363,14 @@ void FBlueprintMCPServer::HandleMoveMaterialExpression(const FJsonObject* Json,
|
|||||||
|
|
||||||
if (!MaterialFunctionName.IsEmpty())
|
if (!MaterialFunctionName.IsEmpty())
|
||||||
{
|
{
|
||||||
FString LoadError;
|
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, Result);
|
||||||
MatFunc = UMCPAssetFinder::LoadAsset<UMaterialFunction>(MaterialFunctionName, LoadError);
|
if (!MatFunc) return;
|
||||||
if (!MatFunc) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = MatFunc->GetName();
|
AssetDisplayName = MatFunc->GetName();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FString LoadError;
|
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material) { MCPUtils::MakeErrorJson(Result, LoadError); return; }
|
|
||||||
AssetDisplayName = Material->GetName();
|
AssetDisplayName = Material->GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1550,12 +1534,8 @@ void FBlueprintMCPServer::HandleSnapshotMaterialGraph(const FJsonObject* Json, F
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load material
|
// Load material
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
MCPUtils::EnsureMaterialGraph(Material);
|
MCPUtils::EnsureMaterialGraph(Material);
|
||||||
if (!Material->MaterialGraph)
|
if (!Material->MaterialGraph)
|
||||||
@@ -1649,12 +1629,8 @@ void FBlueprintMCPServer::HandleDiffMaterialGraph(const FJsonObject* Json, FJson
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load material
|
// Load material
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
MCPUtils::EnsureMaterialGraph(Material);
|
MCPUtils::EnsureMaterialGraph(Material);
|
||||||
if (!Material->MaterialGraph)
|
if (!Material->MaterialGraph)
|
||||||
@@ -1815,12 +1791,8 @@ void FBlueprintMCPServer::HandleRestoreMaterialGraph(const FJsonObject* Json, FJ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load material
|
// Load material
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
MCPUtils::EnsureMaterialGraph(Material);
|
MCPUtils::EnsureMaterialGraph(Material);
|
||||||
if (!Material->MaterialGraph)
|
if (!Material->MaterialGraph)
|
||||||
|
|||||||
@@ -361,12 +361,8 @@ void FBlueprintMCPServer::HandleGetMaterialGraph(const FJsonObject* Json, FJsonO
|
|||||||
|
|
||||||
FString DecodedName = MCPUtils::UrlDecode(Name);
|
FString DecodedName = MCPUtils::UrlDecode(Name);
|
||||||
|
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(DecodedName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialGraph — material '%s'"), *Material->GetName());
|
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"));
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: material"));
|
||||||
}
|
}
|
||||||
|
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: DescribeMaterial — '%s'"), *Material->GetName());
|
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 DecodedName = MCPUtils::UrlDecode(Name);
|
||||||
|
|
||||||
FString LoadError;
|
UMaterialFunction* MF = UMCPAssetFinder::LoadAsset<UMaterialFunction>(DecodedName, Result);
|
||||||
UMaterialFunction* MF = UMCPAssetFinder::LoadAsset<UMaterialFunction>(DecodedName, LoadError);
|
if (!MF) return;
|
||||||
if (!MF)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName());
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterialFunction — '%s'"), *MF->GetName());
|
||||||
|
|
||||||
@@ -882,12 +870,8 @@ void FBlueprintMCPServer::HandleValidateMaterial(const FJsonObject* Json, FJsonO
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load material
|
// Load material
|
||||||
FString LoadError;
|
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, Result);
|
||||||
UMaterial* Material = UMCPAssetFinder::LoadAsset<UMaterial>(MaterialName, LoadError);
|
if (!Material) return;
|
||||||
if (!Material)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Validating material '%s'"), *Material->GetName());
|
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)
|
// Resolve the new type using the shared resolver (supports primitives, structs, enums, and object references)
|
||||||
FEdGraphPinType NewPinType;
|
FEdGraphPinType NewPinType;
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(NewTypeName, NewPinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(NewTypeName, NewPinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the entry node: K2Node_FunctionEntry in a function graph,
|
// Find the entry node: K2Node_FunctionEntry in a function graph,
|
||||||
// or K2Node_CustomEvent in any graph
|
// or K2Node_CustomEvent in any graph
|
||||||
@@ -416,11 +413,8 @@ void FBlueprintMCPServer::HandleAddFunctionParameter(const FJsonObject* Json, FJ
|
|||||||
|
|
||||||
// Resolve param type
|
// Resolve param type
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(ParamType, PinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the entry node using 3 strategies
|
// Find the entry node using 3 strategies
|
||||||
UK2Node_EditablePinBase* EntryNode = nullptr;
|
UK2Node_EditablePinBase* EntryNode = nullptr;
|
||||||
|
|||||||
@@ -121,12 +121,8 @@ void FBlueprintMCPServer::HandleGetBlueprint(const FJsonObject* Json, FJsonObjec
|
|||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing 'name' parameter"));
|
||||||
}
|
}
|
||||||
|
|
||||||
FString LoadError;
|
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Name, Result);
|
||||||
UBlueprint* BP = UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(Name, LoadError);
|
if (!BP) return;
|
||||||
if (!BP)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(BP);
|
TSharedRef<FJsonObject> Tmp = MCPUtils::SerializeBlueprint(BP);
|
||||||
MCPUtils::CopyJsonFields(&*Tmp, Result);
|
MCPUtils::CopyJsonFields(&*Tmp, Result);
|
||||||
|
|||||||
@@ -238,20 +238,13 @@ public:
|
|||||||
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
{
|
{
|
||||||
// Find the struct
|
// Find the struct
|
||||||
FString StructError;
|
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result);
|
||||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, StructError);
|
if (!Struct) return;
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, StructError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve type
|
// Resolve type
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(Type, PinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(Type, PinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *Type, *TypeError));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot existing GUIDs so we can find the newly added one
|
// Snapshot existing GUIDs so we can find the newly added one
|
||||||
TSet<FGuid> ExistingGuids;
|
TSet<FGuid> ExistingGuids;
|
||||||
@@ -311,12 +304,8 @@ public:
|
|||||||
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
{
|
{
|
||||||
// Find the struct
|
// Find the struct
|
||||||
FString StructError;
|
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, Result);
|
||||||
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadAsset<UUserDefinedStruct>(AssetPath, StructError);
|
if (!Struct) return;
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, StructError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the property GUID by name
|
// Find the property GUID by name
|
||||||
FGuid TargetGuid;
|
FGuid TargetGuid;
|
||||||
|
|||||||
@@ -68,11 +68,8 @@ void FBlueprintMCPServer::HandleChangeVariableType(const FJsonObject* Json, FJso
|
|||||||
ResolveInput = TypeCategory + TEXT(":") + NewTypeName;
|
ResolveInput = TypeCategory + TEXT(":") + NewTypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(ResolveInput, NewPinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(ResolveInput, NewPinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derive typeCategory from the resolved pin type for the response
|
// Derive typeCategory from the resolved pin type for the response
|
||||||
if (TypeCategory.IsEmpty())
|
if (TypeCategory.IsEmpty())
|
||||||
@@ -270,11 +267,8 @@ void FBlueprintMCPServer::HandleAddVariable(const FJsonObject* Json, FJsonObject
|
|||||||
|
|
||||||
// Resolve the type using the shared helper
|
// Resolve the type using the shared helper
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
FString TypeError;
|
if (!MCPUtils::ResolveTypeFromString(VariableType, PinType, Result))
|
||||||
if (!MCPUtils::ResolveTypeFromString(VariableType, PinType, TypeError))
|
return;
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TypeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set container type for arrays
|
// Set container type for arrays
|
||||||
if (bIsArray)
|
if (bIsArray)
|
||||||
|
|||||||
@@ -78,6 +78,22 @@ extern int32 TrySavePackageSEH(
|
|||||||
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
||||||
#endif
|
#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
|
// JSON helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -703,7 +719,7 @@ UClass* MCPUtils::FindClassByName(const FString& ClassName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MCPUtils::ResolveTypeFromString(
|
bool MCPUtils::ResolveTypeFromString(
|
||||||
const FString& TypeName, FEdGraphPinType& OutPinType, FString& OutError)
|
const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error)
|
||||||
{
|
{
|
||||||
FString TypeLower = TypeName.ToLower();
|
FString TypeLower = TypeName.ToLower();
|
||||||
|
|
||||||
@@ -776,7 +792,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
UClass* FoundClass = FindClassByName(ClassName);
|
UClass* FoundClass = FindClassByName(ClassName);
|
||||||
if (!FoundClass)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
|
||||||
@@ -788,7 +804,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
UClass* FoundClass = FindClassByName(ClassName);
|
UClass* FoundClass = FindClassByName(ClassName);
|
||||||
if (!FoundClass)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
|
||||||
@@ -800,7 +816,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
UClass* FoundClass = FindClassByName(ClassName);
|
UClass* FoundClass = FindClassByName(ClassName);
|
||||||
if (!FoundClass)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Class;
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Class;
|
||||||
@@ -812,7 +828,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
UClass* FoundClass = FindClassByName(ClassName);
|
UClass* FoundClass = FindClassByName(ClassName);
|
||||||
if (!FoundClass)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
|
||||||
@@ -824,7 +840,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
UClass* FoundClass = FindClassByName(ClassName);
|
UClass* FoundClass = FindClassByName(ClassName);
|
||||||
if (!FoundClass)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Interface;
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Interface;
|
||||||
@@ -900,9 +916,9 @@ bool MCPUtils::ResolveTypeFromString(
|
|||||||
}
|
}
|
||||||
else
|
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)"),
|
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;
|
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 "AssetRegistry/AssetData.h"
|
||||||
#include "StructUtils/UserDefinedStruct.h"
|
#include "StructUtils/UserDefinedStruct.h"
|
||||||
#include "Engine/UserDefinedEnum.h"
|
#include "Engine/UserDefinedEnum.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
#include "MCPAssetFinder.generated.h"
|
#include "MCPAssetFinder.generated.h"
|
||||||
|
|
||||||
class UBlueprint;
|
class UBlueprint;
|
||||||
@@ -45,36 +46,38 @@ public:
|
|||||||
static TArray<FAssetData*> SearchAssets(FName Class, const FString& NameOrPath, FString* OutError = nullptr);
|
static TArray<FAssetData*> SearchAssets(FName Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||||
static TArray<FAssetData*> SearchAssets(UClass *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>
|
template<typename T>
|
||||||
static T* LoadAsset(FAssetData& Asset, FString& OutError)
|
static T* LoadAsset(FAssetData& Asset, MCPErrorCallback Error)
|
||||||
{
|
{
|
||||||
T* Result = Cast<T>(Asset.GetAsset());
|
T* Result = Cast<T>(Asset.GetAsset());
|
||||||
if (!Result)
|
if (!Result)
|
||||||
OutError = FString::Printf(TEXT("Asset '%s' found but could not be loaded as %s."),
|
Error.SetError(FString::Printf(TEXT("Asset '%s' found but could not be loaded as %s."),
|
||||||
*Asset.AssetName.ToString(), *T::StaticClass()->GetName());
|
*Asset.AssetName.ToString(), *T::StaticClass()->GetName()));
|
||||||
return Result;
|
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>
|
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 (!Asset)
|
||||||
{
|
{
|
||||||
if (OutError.IsEmpty())
|
if (FindError.IsEmpty())
|
||||||
OutError = FString::Printf(TEXT("'%s' not found."), *NameOrPath);
|
FindError = FString::Printf(TEXT("'%s' not found."), *NameOrPath);
|
||||||
|
Error.SetError(FindError);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return LoadAsset<T>(*Asset, OutError);
|
return LoadAsset<T>(*Asset, Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FAssetData* FindAnyAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
static FAssetData* FindAnyAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||||
|
|
||||||
// Load a blueprint or level blueprint from an asset (handles UWorld → level blueprint extraction).
|
// Load a blueprint or level blueprint from an asset (handles UWorld → level blueprint extraction).
|
||||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(FAssetData& Asset, FString& OutError);
|
static UBlueprint* LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error);
|
||||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, FString& OutError);
|
static UBlueprint* LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Fetch assets from the Unreal asset registry and store them locally.
|
// 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.
|
// Stateless utility functions used by MCP handlers and the MCP server.
|
||||||
// This is effectively a namespace — all methods are static.
|
// This is effectively a namespace — all methods are static.
|
||||||
class MCPUtils
|
class MCPUtils
|
||||||
@@ -141,7 +143,7 @@ public:
|
|||||||
|
|
||||||
// ----- Type resolution -----
|
// ----- Type resolution -----
|
||||||
static UClass* FindClassByName(const FString& ClassName);
|
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 -----
|
// ----- Material helpers -----
|
||||||
static void EnsureMaterialGraph(UMaterial* Material);
|
static void EnsureMaterialGraph(UMaterial* Material);
|
||||||
@@ -166,3 +168,16 @@ public:
|
|||||||
private:
|
private:
|
||||||
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
|
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