Refactoring json code and more
This commit is contained in:
BIN
Content/Testing/M_Test.uasset
LFS
BIN
Content/Testing/M_Test.uasset
LFS
Binary file not shown.
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Animation/AnimSequence.h"
|
#include "Animation/AnimSequence.h"
|
||||||
#include "Animation/BlendSpace.h"
|
#include "Animation/BlendSpace.h"
|
||||||
@@ -101,7 +102,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& SampleVal : Samples.Array)
|
for (const TSharedPtr<FJsonValue>& SampleVal : Samples.Array)
|
||||||
{
|
{
|
||||||
FBlendSpaceSampleEntry Entry;
|
FBlendSpaceSampleEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FBlendSpaceSampleEntry::StaticStruct(), &Entry, SampleVal)) return;
|
if (!MCPJson::PopulateFromJson(FBlendSpaceSampleEntry::StaticStruct(), &Entry, SampleVal)) return;
|
||||||
|
|
||||||
UAnimSequence* AnimSeq = nullptr;
|
UAnimSequence* AnimSeq = nullptr;
|
||||||
if (!Entry.AnimationAsset.IsEmpty())
|
if (!Entry.AnimationAsset.IsEmpty())
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
@@ -125,7 +126,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& ParamVal : Parameters.Array)
|
for (const TSharedPtr<FJsonValue>& ParamVal : Parameters.Array)
|
||||||
{
|
{
|
||||||
FDispatcherParamEntry Entry;
|
FDispatcherParamEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FDispatcherParamEntry::StaticStruct(), &Entry, ParamVal)) return;
|
if (!MCPJson::PopulateFromJson(FDispatcherParamEntry::StaticStruct(), &Entry, ParamVal)) return;
|
||||||
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
|
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
|
||||||
|
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "StructUtils/UserDefinedStruct.h"
|
#include "StructUtils/UserDefinedStruct.h"
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
@@ -60,7 +61,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& PropVal : Properties.Array)
|
for (const TSharedPtr<FJsonValue>& PropVal : Properties.Array)
|
||||||
{
|
{
|
||||||
FStructPropertyEntry Entry;
|
FStructPropertyEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FStructPropertyEntry::StaticStruct(), &Entry, PropVal)) return;
|
if (!MCPJson::PopulateFromJson(FStructPropertyEntry::StaticStruct(), &Entry, PropVal)) return;
|
||||||
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
|
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
|
||||||
|
|
||||||
FEdGraphPinType PinType;
|
FEdGraphPinType PinType;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
@@ -62,7 +63,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
|
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
|
||||||
{
|
{
|
||||||
FSpawnNodeEntry Entry;
|
FSpawnNodeEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal))
|
if (!MCPJson::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Find the action by exact full name
|
// Find the action by exact full name
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPProperty.h"
|
#include "MCPProperty.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "EdGraph/EdGraphPin.h"
|
#include "EdGraph/EdGraphPin.h"
|
||||||
#include "EdGraphSchema_K2.h"
|
#include "EdGraphSchema_K2.h"
|
||||||
@@ -124,7 +125,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||||
{
|
{
|
||||||
FSetNodeDefaultEntry Entry;
|
FSetNodeDefaultEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal))
|
if (!MCPJson::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (K2Schema)
|
if (K2Schema)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
@@ -36,8 +37,8 @@ class UMCP_GraphNode_SetPositions : public UObject, public IMCPHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph"))
|
||||||
FString Blueprint;
|
FString Graph;
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Array of {node, x, y} objects"))
|
UPROPERTY(meta=(Description="Array of {node, x, y} objects"))
|
||||||
FMCPJsonArray Nodes;
|
FMCPJsonArray Nodes;
|
||||||
@@ -50,23 +51,22 @@ public:
|
|||||||
virtual void Handle() override
|
virtual void Handle() override
|
||||||
{
|
{
|
||||||
MCPFetcher F;
|
MCPFetcher F;
|
||||||
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
|
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
|
||||||
if (!BP) return;
|
if (!TargetGraph) return;
|
||||||
|
|
||||||
int32 SuccessCount = 0;
|
int32 SuccessCount = 0;
|
||||||
|
|
||||||
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
|
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
|
||||||
{
|
{
|
||||||
FMoveNodeEntry Entry;
|
FMoveNodeEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal)) continue;
|
if (!MCPJson::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal)) continue;
|
||||||
|
|
||||||
MCPFetcher FN(BP);
|
MCPFetcher FN(TargetGraph);
|
||||||
UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
|
UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
|
||||||
if (!Node) continue;
|
if (!Node) continue;
|
||||||
|
|
||||||
Node->NodePosX = Entry.X;
|
Node->NodePosX = Entry.X;
|
||||||
Node->NodePosY = Entry.Y;
|
Node->NodePosY = Entry.Y;
|
||||||
UMCPServer::Printf(TEXT("Moved %s to (%d, %d)\n"), *MCPUtils::FormatName(Node), Entry.X, Entry.Y);
|
|
||||||
SuccessCount++;
|
SuccessCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
@@ -58,7 +59,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
|
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
|
||||||
{
|
{
|
||||||
FConnectPinsEntry Entry;
|
FConnectPinsEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal))
|
if (!MCPJson::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
MCPFetcher FS(G);
|
MCPFetcher FS(G);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
@@ -58,7 +59,7 @@ public:
|
|||||||
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
|
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
|
||||||
{
|
{
|
||||||
FDisconnectPinEntry Entry;
|
FDisconnectPinEntry Entry;
|
||||||
if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal)) continue;
|
if (!MCPJson::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal)) continue;
|
||||||
|
|
||||||
MCPFetcher FP(G);
|
MCPFetcher FP(G);
|
||||||
UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
|
UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
|
||||||
|
|||||||
@@ -23,64 +23,32 @@ class UMCP_Graph_Dump : public UObject, public IMCPHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Description="Path to a blueprint, material, or graph, e.g. /Game/Foo or /Game/Foo,graph:EventGraph"))
|
UPROPERTY(meta=(Description="Path to graph"))
|
||||||
FString Object;
|
FString Graph;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Optional, Description="True to include less-significant details"))
|
||||||
|
bool bDetails;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Dump blueprint or material graphs as readable text. "
|
return TEXT("Dump blueprint or material graphs as readable text. ");
|
||||||
"If given a blueprint or material, dumps all graphs. If given a specific graph, dumps only that one.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle() override
|
virtual void Handle() override
|
||||||
{
|
{
|
||||||
MCPFetcher F;
|
MCPFetcher F;
|
||||||
F.Walk(Object);
|
UEdGraph *G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
|
||||||
if (!F.Ok()) return;
|
if (!G) return;
|
||||||
|
|
||||||
if (UEdGraph* Graph = Cast<UEdGraph>(F.GetObj()))
|
FlxBlueprintExporter Exporter(G);
|
||||||
{
|
|
||||||
EmitGraph(Graph);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UBlueprint* BP = Cast<UBlueprint>(F.GetObj()))
|
|
||||||
{
|
|
||||||
TArray<UEdGraph*> Graphs = MCPUtils::AllGraphs(BP);
|
|
||||||
for (UEdGraph* Graph : Graphs)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("\n======== %s ========\n"), *MCPUtils::FormatName(Graph));
|
|
||||||
EmitGraph(Graph);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UMaterial* Mat = Cast<UMaterial>(F.GetObj()))
|
|
||||||
{
|
|
||||||
MCPUtils::EnsureMaterialGraph(Mat);
|
|
||||||
if (!Mat->MaterialGraph)
|
|
||||||
{
|
|
||||||
UMCPServer::Print(TEXT("ERROR: Could not build MaterialGraph for this material\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
EmitGraph(Mat->MaterialGraph);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Expected a blueprint, material, or graph, got %s\n"),
|
|
||||||
*MCPUtils::FormatName(F.GetObj()->GetClass()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void EmitGraph(UEdGraph* Graph)
|
|
||||||
{
|
|
||||||
FlxBlueprintExporter Exporter(Graph);
|
|
||||||
UMCPServer::Print(*Exporter.GetOutput());
|
UMCPServer::Print(*Exporter.GetOutput());
|
||||||
FString Details = Exporter.GetDetails();
|
if (bDetails)
|
||||||
if (!Details.IsEmpty())
|
|
||||||
{
|
{
|
||||||
UMCPServer::Print(TEXT("\n"));
|
UMCPServer::Print(Exporter.GetDetails());
|
||||||
UMCPServer::Print(*Details);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("Note: use bDetails=true to see suppressed details."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "ShowCommands.generated.h"
|
#include "ShowCommands.generated.h"
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ public:
|
|||||||
if (!bFirst) UMCPServer::Print(TEXT(","));
|
if (!bFirst) UMCPServer::Print(TEXT(","));
|
||||||
bFirst = false;
|
bFirst = false;
|
||||||
if (PropIt->HasMetaData(TEXT("Optional"))) UMCPServer::Print(TEXT("?"));
|
if (PropIt->HasMetaData(TEXT("Optional"))) UMCPServer::Print(TEXT("?"));
|
||||||
UMCPServer::Print(MCPUtils::PropertyNameToJsonKey(PropIt->GetName()));
|
UMCPServer::Print(PropIt->GetName());
|
||||||
}
|
}
|
||||||
UMCPServer::Print(TEXT(")\n"));
|
UMCPServer::Print(TEXT(")\n"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ void FlxBlueprintExporter::EmitGraph()
|
|||||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||||
EmitNode(Node);
|
EmitNode(Node);
|
||||||
}
|
}
|
||||||
|
Output.Append(TEXT("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitDetails()
|
void FlxBlueprintExporter::EmitDetails()
|
||||||
|
|||||||
140
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPJson.cpp
Normal file
140
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPJson.cpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#include "MCPJson.h"
|
||||||
|
#include "MCPTypes.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "UObject/UnrealType.h"
|
||||||
|
#include "UObject/EnumProperty.h"
|
||||||
|
#include "Dom/JsonValue.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(
|
||||||
|
UStruct* StructType, void* Container, const FJsonObject* Json)
|
||||||
|
{
|
||||||
|
bool Ok = true;
|
||||||
|
|
||||||
|
// Build a set of known property names (as JSON keys) for the unknown-field check.
|
||||||
|
TSet<FString> KnownKeys;
|
||||||
|
TArray<FProperty*> Properties;
|
||||||
|
|
||||||
|
for (TFieldIterator<FProperty> It(StructType, EFieldIterationFlags::None); It; ++It)
|
||||||
|
{
|
||||||
|
FProperty* Prop = *It;
|
||||||
|
Properties.Add(Prop);
|
||||||
|
KnownKeys.Add(Prop->GetName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for unknown fields in the JSON
|
||||||
|
for (const auto& KV : Json->Values)
|
||||||
|
{
|
||||||
|
if (!KnownKeys.Contains(KV.Key))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
||||||
|
Ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate each property from JSON
|
||||||
|
for (FProperty* Property : Properties)
|
||||||
|
{
|
||||||
|
if (!PopulateFromJson(Property, Container, Json)) Ok = false;
|
||||||
|
}
|
||||||
|
return Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(
|
||||||
|
FProperty* Property, void* Container, const FJsonObject* Json)
|
||||||
|
{
|
||||||
|
FString JsonKey = Property->GetName();
|
||||||
|
bool bOptional = Property->HasMetaData(TEXT("Optional"));
|
||||||
|
|
||||||
|
if (!Json->HasField(JsonKey))
|
||||||
|
{
|
||||||
|
if (!bOptional)
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
|
||||||
|
// Special handling for FMCPJsonObject and FMCPJsonArray
|
||||||
|
if (FStructProperty* StructProp = CastField<FStructProperty>(Property))
|
||||||
|
{
|
||||||
|
if (StructProp->Struct == FMCPJsonObject::StaticStruct())
|
||||||
|
{
|
||||||
|
if (!Json->HasTypedField<EJson::Object>(JsonKey))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: '%s' must be an object\n"), *JsonKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static_cast<FMCPJsonObject*>(ValuePtr)->Json = Json->GetObjectField(JsonKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StructProp->Struct == FMCPJsonArray::StaticStruct())
|
||||||
|
{
|
||||||
|
if (!Json->HasTypedField<EJson::Array>(JsonKey))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: '%s' must be an array\n"), *JsonKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static_cast<FMCPJsonArray*>(ValuePtr)->Array = Json->GetArrayField(JsonKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle based on JSON value type.
|
||||||
|
TSharedPtr<FJsonValue> JsonValue = Json->TryGetField(JsonKey);
|
||||||
|
|
||||||
|
if (JsonValue->Type == EJson::Number)
|
||||||
|
{
|
||||||
|
double D = JsonValue->AsNumber();
|
||||||
|
if (FIntProperty* IntProp = CastField<FIntProperty>(Property))
|
||||||
|
{ IntProp->SetPropertyValue(ValuePtr, (int32)D); return true; }
|
||||||
|
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Property))
|
||||||
|
{ FloatProp->SetPropertyValue(ValuePtr, (float)D); return true; }
|
||||||
|
if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Property))
|
||||||
|
{ DoubleProp->SetPropertyValue(ValuePtr, D); return true; }
|
||||||
|
if (FByteProperty* ByteProp = CastField<FByteProperty>(Property))
|
||||||
|
{ ByteProp->SetPropertyValue(ValuePtr, (uint8)D); return true; }
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: '%s' received a number but expects %s\n"), *JsonKey, *Property->GetCPPType());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JsonValue->Type == EJson::Boolean)
|
||||||
|
{
|
||||||
|
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Property))
|
||||||
|
{ BoolProp->SetPropertyValue(ValuePtr, JsonValue->AsBool()); return true; }
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: '%s' received a boolean but expects %s\n"), *JsonKey, *Property->GetCPPType());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JsonValue->Type == EJson::String)
|
||||||
|
{
|
||||||
|
FString ValueStr = JsonValue->AsString();
|
||||||
|
const TCHAR* Result = Property->ImportText_Direct(*ValueStr, ValuePtr, nullptr, PPF_None);
|
||||||
|
if (!Result)
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Could not parse '%s' for parameter '%s'\n"), *ValueStr, *JsonKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: '%s' must be a string, number, or boolean\n"), *JsonKey);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(
|
||||||
|
UStruct* StructType, void* Container,
|
||||||
|
const TSharedPtr<FJsonValue>& JsonValue)
|
||||||
|
{
|
||||||
|
if (!JsonValue.IsValid() || (JsonValue->Type != EJson::Object))
|
||||||
|
{
|
||||||
|
UMCPServer::Print(TEXT("ERROR: Expected a JSON object\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get());
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "LogCapture.h"
|
#include "LogCapture.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "MCPAssets.h"
|
#include "MCPAssets.h"
|
||||||
@@ -316,7 +317,7 @@ void UMCPServer::TryCallHandler(const FString &Line)
|
|||||||
IMCPHandler* Handler = Cast<IMCPHandler>(HandlerObj.Get());
|
IMCPHandler* Handler = Cast<IMCPHandler>(HandlerObj.Get());
|
||||||
|
|
||||||
// Populate the handler object with the request parameters.
|
// Populate the handler object with the request parameters.
|
||||||
if (!MCPUtils::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request))
|
if (!MCPJson::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request))
|
||||||
{
|
{
|
||||||
UMCPServer::Printf(TEXT("\nUsage:\n\n"));
|
UMCPServer::Printf(TEXT("\nUsage:\n\n"));
|
||||||
MCPUtils::FormatCommandHelp(*HandlerClass);
|
MCPUtils::FormatCommandHelp(*HandlerClass);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
@@ -789,220 +790,6 @@ TArray<TSharedPtr<FEdGraphSchemaAction>> MCPUtils::SearchGraphActions(UEdGraph*
|
|||||||
#include "UObject/UnrealType.h"
|
#include "UObject/UnrealType.h"
|
||||||
#include "UObject/EnumProperty.h"
|
#include "UObject/EnumProperty.h"
|
||||||
|
|
||||||
FString MCPUtils::PropertyNameToJsonKey(const FString& PropName)
|
|
||||||
{
|
|
||||||
if (PropName.IsEmpty())
|
|
||||||
{
|
|
||||||
return PropName;
|
|
||||||
}
|
|
||||||
FString Result = PropName;
|
|
||||||
Result[0] = FChar::ToLower(Result[0]);
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
FString MCPUtils::SetPropertyFromJson(
|
|
||||||
void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json)
|
|
||||||
{
|
|
||||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
||||||
|
|
||||||
// FString
|
|
||||||
if (FStrProperty* StrProp = CastField<FStrProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::String>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a string"), *FieldName);
|
|
||||||
}
|
|
||||||
StrProp->SetPropertyValue(ValuePtr, Json->GetStringField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// int32
|
|
||||||
if (FIntProperty* IntProp = CastField<FIntProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Number>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a number"), *FieldName);
|
|
||||||
}
|
|
||||||
IntProp->SetPropertyValue(ValuePtr, (int32)Json->GetNumberField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// float
|
|
||||||
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Number>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a number"), *FieldName);
|
|
||||||
}
|
|
||||||
FloatProp->SetPropertyValue(ValuePtr, (float)Json->GetNumberField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// double
|
|
||||||
if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Number>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a number"), *FieldName);
|
|
||||||
}
|
|
||||||
DoubleProp->SetPropertyValue(ValuePtr, Json->GetNumberField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// bool
|
|
||||||
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Boolean>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a boolean"), *FieldName);
|
|
||||||
}
|
|
||||||
BoolProp->SetPropertyValue(ValuePtr, Json->GetBoolField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum (FEnumProperty — C++ enum class)
|
|
||||||
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::String>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a string"), *FieldName);
|
|
||||||
}
|
|
||||||
FString ValueStr = Json->GetStringField(FieldName);
|
|
||||||
UEnum* Enum = EnumProp->GetEnum();
|
|
||||||
int64 EnumVal = Enum->GetValueByNameString(ValueStr);
|
|
||||||
if (EnumVal == INDEX_NONE)
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s': unknown enum value '%s'"), *FieldName, *ValueStr);
|
|
||||||
}
|
|
||||||
FNumericProperty* UnderlyingProp = EnumProp->GetUnderlyingProperty();
|
|
||||||
UnderlyingProp->SetIntPropertyValue(ValuePtr, EnumVal);
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum (FByteProperty with Enum — old-style UENUM)
|
|
||||||
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (ByteProp->Enum)
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::String>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a string"), *FieldName);
|
|
||||||
}
|
|
||||||
FString ValueStr = Json->GetStringField(FieldName);
|
|
||||||
int64 EnumVal = ByteProp->Enum->GetValueByNameString(ValueStr);
|
|
||||||
if (EnumVal == INDEX_NONE)
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s': unknown enum value '%s'"), *FieldName, *ValueStr);
|
|
||||||
}
|
|
||||||
ByteProp->SetPropertyValue(ValuePtr, (uint8)EnumVal);
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
// Plain byte without enum — treat as number
|
|
||||||
if (!Json->HasTypedField<EJson::Number>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be a number"), *FieldName);
|
|
||||||
}
|
|
||||||
ByteProp->SetPropertyValue(ValuePtr, (uint8)Json->GetNumberField(FieldName));
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FMCPJsonObject — stash a JSON object into the struct
|
|
||||||
if (FStructProperty* StructProp = CastField<FStructProperty>(Prop))
|
|
||||||
{
|
|
||||||
if (StructProp->Struct == FMCPJsonObject::StaticStruct())
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Object>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be an object"), *FieldName);
|
|
||||||
}
|
|
||||||
FMCPJsonObject* Obj = StructProp->ContainerPtrToValuePtr<FMCPJsonObject>(Container);
|
|
||||||
Obj->Json = Json->GetObjectField(FieldName);
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FMCPJsonArray — stash a JSON array into the struct
|
|
||||||
if (StructProp->Struct == FMCPJsonArray::StaticStruct())
|
|
||||||
{
|
|
||||||
if (!Json->HasTypedField<EJson::Array>(FieldName))
|
|
||||||
{
|
|
||||||
return FString::Printf(TEXT("'%s' must be an array"), *FieldName);
|
|
||||||
}
|
|
||||||
FMCPJsonArray* Arr = StructProp->ContainerPtrToValuePtr<FMCPJsonArray>(Container);
|
|
||||||
Arr->Array = Json->GetArrayField(FieldName);
|
|
||||||
return FString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FString::Printf(TEXT("'%s': unsupported property type '%s'"),
|
|
||||||
*FieldName, *Prop->GetCPPType());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MCPUtils::PopulateFromJson(
|
|
||||||
UStruct* StructType,
|
|
||||||
void* Container,
|
|
||||||
const TSharedPtr<FJsonValue>& JsonValue)
|
|
||||||
{
|
|
||||||
if (!JsonValue.IsValid() || (JsonValue->Type != EJson::Object))
|
|
||||||
{
|
|
||||||
UMCPServer::Print(TEXT("ERROR: Expected a JSON object\n"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MCPUtils::PopulateFromJson(
|
|
||||||
UStruct* StructType,
|
|
||||||
void* Container,
|
|
||||||
const FJsonObject* Json)
|
|
||||||
{
|
|
||||||
// Build a set of known property names (as JSON keys) for the unknown-field check.
|
|
||||||
TSet<FString> KnownKeys;
|
|
||||||
TArray<FProperty*> Properties;
|
|
||||||
|
|
||||||
for (TFieldIterator<FProperty> It(StructType, EFieldIterationFlags::None); It; ++It)
|
|
||||||
{
|
|
||||||
FProperty* Prop = *It;
|
|
||||||
Properties.Add(Prop);
|
|
||||||
KnownKeys.Add(PropertyNameToJsonKey(Prop->GetName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unknown fields in the JSON
|
|
||||||
for (const auto& KV : Json->Values)
|
|
||||||
{
|
|
||||||
if (!KnownKeys.Contains(KV.Key))
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate each property from JSON
|
|
||||||
for (FProperty* Prop : Properties)
|
|
||||||
{
|
|
||||||
FString JsonKey = PropertyNameToJsonKey(Prop->GetName());
|
|
||||||
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
|
||||||
|
|
||||||
if (!Json->HasField(JsonKey))
|
|
||||||
{
|
|
||||||
if (!bOptional)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
FString PropError = SetPropertyFromJson(Container, Prop, JsonKey, Json);
|
|
||||||
if (!PropError.IsEmpty())
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: %s\n"), *PropError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// CollectHandlerClasses — find all concrete IMCPHandler classes
|
// CollectHandlerClasses — find all concrete IMCPHandler classes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -1173,7 +960,7 @@ void MCPUtils::FormatCommandHelp(UClass* HandlerClass)
|
|||||||
if (!bFirst) UMCPServer::Print(TEXT(","));
|
if (!bFirst) UMCPServer::Print(TEXT(","));
|
||||||
bFirst = false;
|
bFirst = false;
|
||||||
if (PropIt->HasMetaData(TEXT("Optional"))) UMCPServer::Print(TEXT("?"));
|
if (PropIt->HasMetaData(TEXT("Optional"))) UMCPServer::Print(TEXT("?"));
|
||||||
UMCPServer::Print(PropertyNameToJsonKey(PropIt->GetName()));
|
UMCPServer::Print(PropIt->GetName());
|
||||||
}
|
}
|
||||||
UMCPServer::Print(TEXT(")\n"));
|
UMCPServer::Print(TEXT(")\n"));
|
||||||
|
|
||||||
@@ -1181,7 +968,7 @@ void MCPUtils::FormatCommandHelp(UClass* HandlerClass)
|
|||||||
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||||
{
|
{
|
||||||
FProperty* Prop = *PropIt;
|
FProperty* Prop = *PropIt;
|
||||||
FString Name = PropertyNameToJsonKey(Prop->GetName());
|
FString Name = Prop->GetName();
|
||||||
FString Type = UMCPTypes::TypeToText(Prop);
|
FString Type = UMCPTypes::TypeToText(Prop);
|
||||||
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
||||||
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
||||||
|
|||||||
15
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPJson.h
Normal file
15
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPJson.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "Dom/JsonObject.h"
|
||||||
|
|
||||||
|
// JSON utility functions used by MCP handlers.
|
||||||
|
// This is effectively a namespace — all methods are static.
|
||||||
|
class MCPJson
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool PopulateFromJson(FProperty* Property, void* Container, const FJsonObject* Json);
|
||||||
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue);
|
||||||
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json);
|
||||||
|
};
|
||||||
@@ -165,11 +165,6 @@ public:
|
|||||||
static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value);
|
static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value);
|
||||||
static bool SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner);
|
static bool SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner);
|
||||||
|
|
||||||
// ----- JSON helpers -----
|
|
||||||
static FString PropertyNameToJsonKey(const FString& PropName);
|
|
||||||
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue);
|
|
||||||
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json);
|
|
||||||
|
|
||||||
// ----- Text formatting -----
|
// ----- Text formatting -----
|
||||||
static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix);
|
static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix);
|
||||||
|
|
||||||
@@ -183,6 +178,5 @@ public:
|
|||||||
private:
|
private:
|
||||||
static void SanitizeNameInPlace(FString& Name);
|
static void SanitizeNameInPlace(FString& Name);
|
||||||
static void AppendNumericSuffix(FString &Name, int32 N);
|
static void AppendNumericSuffix(FString &Name, int32 N);
|
||||||
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user