chore: added mcp server plugin to game

This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-06 20:52:41 +01:00
parent 749568a469
commit f7bac4cc6d
23 changed files with 5181 additions and 1 deletions

View File

@ -0,0 +1,924 @@
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
#include "Commands/UnrealMCPCommonUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_Event.h"
#include "K2Node_CallFunction.h"
#include "K2Node_VariableGet.h"
#include "K2Node_InputAction.h"
#include "K2Node_Self.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GameFramework/InputSettings.h"
#include "Camera/CameraActor.h"
#include "Kismet/GameplayStatics.h"
#include "EdGraphSchema_K2.h"
// Declare the log category
DEFINE_LOG_CATEGORY_STATIC(LogUnrealMCP, Log, All);
FUnrealMCPBlueprintNodeCommands::FUnrealMCPBlueprintNodeCommands()
{
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
{
if (CommandType == TEXT("connect_blueprint_nodes"))
{
return HandleConnectBlueprintNodes(Params);
}
else if (CommandType == TEXT("add_blueprint_get_self_component_reference"))
{
return HandleAddBlueprintGetSelfComponentReference(Params);
}
else if (CommandType == TEXT("add_blueprint_event_node"))
{
return HandleAddBlueprintEvent(Params);
}
else if (CommandType == TEXT("add_blueprint_function_node"))
{
return HandleAddBlueprintFunctionCall(Params);
}
else if (CommandType == TEXT("add_blueprint_variable"))
{
return HandleAddBlueprintVariable(Params);
}
else if (CommandType == TEXT("add_blueprint_input_action_node"))
{
return HandleAddBlueprintInputActionNode(Params);
}
else if (CommandType == TEXT("add_blueprint_self_reference"))
{
return HandleAddBlueprintSelfReference(Params);
}
else if (CommandType == TEXT("find_blueprint_nodes"))
{
return HandleFindBlueprintNodes(Params);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown blueprint node command: %s"), *CommandType));
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleConnectBlueprintNodes(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString SourceNodeId;
if (!Params->TryGetStringField(TEXT("source_node_id"), SourceNodeId))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'source_node_id' parameter"));
}
FString TargetNodeId;
if (!Params->TryGetStringField(TEXT("target_node_id"), TargetNodeId))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'target_node_id' parameter"));
}
FString SourcePinName;
if (!Params->TryGetStringField(TEXT("source_pin"), SourcePinName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'source_pin' parameter"));
}
FString TargetPinName;
if (!Params->TryGetStringField(TEXT("target_pin"), TargetPinName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'target_pin' parameter"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Find the nodes
UEdGraphNode* SourceNode = nullptr;
UEdGraphNode* TargetNode = nullptr;
for (UEdGraphNode* Node : EventGraph->Nodes)
{
if (Node->NodeGuid.ToString() == SourceNodeId)
{
SourceNode = Node;
}
else if (Node->NodeGuid.ToString() == TargetNodeId)
{
TargetNode = Node;
}
}
if (!SourceNode || !TargetNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Source or target node not found"));
}
// Connect the nodes
if (FUnrealMCPCommonUtils::ConnectGraphNodes(EventGraph, SourceNode, SourcePinName, TargetNode, TargetPinName))
{
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("source_node_id"), SourceNodeId);
ResultObj->SetStringField(TEXT("target_node_id"), TargetNodeId);
return ResultObj;
}
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to connect nodes"));
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintGetSelfComponentReference(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString ComponentName;
if (!Params->TryGetStringField(TEXT("component_name"), ComponentName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'component_name' parameter"));
}
// Get position parameters (optional)
FVector2D NodePosition(0.0f, 0.0f);
if (Params->HasField(TEXT("node_position")))
{
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// We'll skip component verification since the GetAllNodes API may have changed in UE5.5
// Create the variable get node directly
UK2Node_VariableGet* GetComponentNode = NewObject<UK2Node_VariableGet>(EventGraph);
if (!GetComponentNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create get component node"));
}
// Set up the variable reference properly for UE5.5
FMemberReference& VarRef = GetComponentNode->VariableReference;
VarRef.SetSelfMember(FName(*ComponentName));
// Set node position
GetComponentNode->NodePosX = NodePosition.X;
GetComponentNode->NodePosY = NodePosition.Y;
// Add to graph
EventGraph->AddNode(GetComponentNode);
GetComponentNode->CreateNewGuid();
GetComponentNode->PostPlacedNewNode();
GetComponentNode->AllocateDefaultPins();
// Explicitly reconstruct node for UE5.5
GetComponentNode->ReconstructNode();
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("node_id"), GetComponentNode->NodeGuid.ToString());
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintEvent(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString EventName;
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'event_name' parameter"));
}
// Get position parameters (optional)
FVector2D NodePosition(0.0f, 0.0f);
if (Params->HasField(TEXT("node_position")))
{
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Create the event node
UK2Node_Event* EventNode = FUnrealMCPCommonUtils::CreateEventNode(EventGraph, EventName, NodePosition);
if (!EventNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create event node"));
}
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("node_id"), EventNode->NodeGuid.ToString());
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintFunctionCall(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString FunctionName;
if (!Params->TryGetStringField(TEXT("function_name"), FunctionName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'function_name' parameter"));
}
// Get position parameters (optional)
FVector2D NodePosition(0.0f, 0.0f);
if (Params->HasField(TEXT("node_position")))
{
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
}
// Check for target parameter (optional)
FString Target;
Params->TryGetStringField(TEXT("target"), Target);
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Find the function
UFunction* Function = nullptr;
UK2Node_CallFunction* FunctionNode = nullptr;
// Add extensive logging for debugging
UE_LOG(LogTemp, Display, TEXT("Looking for function '%s' in target '%s'"),
*FunctionName, Target.IsEmpty() ? TEXT("Blueprint") : *Target);
// Check if we have a target class specified
if (!Target.IsEmpty())
{
// Try to find the target class
UClass* TargetClass = nullptr;
// First try without a prefix
TargetClass = FindObject<UClass>(ANY_PACKAGE, *Target);
UE_LOG(LogTemp, Display, TEXT("Tried to find class '%s': %s"),
*Target, TargetClass ? TEXT("Found") : TEXT("Not found"));
// If not found, try with U prefix (common convention for UE classes)
if (!TargetClass && !Target.StartsWith(TEXT("U")))
{
FString TargetWithPrefix = FString(TEXT("U")) + Target;
TargetClass = FindObject<UClass>(ANY_PACKAGE, *TargetWithPrefix);
UE_LOG(LogTemp, Display, TEXT("Tried to find class '%s': %s"),
*TargetWithPrefix, TargetClass ? TEXT("Found") : TEXT("Not found"));
}
// If still not found, try with common component names
if (!TargetClass)
{
// Try some common component class names
TArray<FString> PossibleClassNames;
PossibleClassNames.Add(FString(TEXT("U")) + Target + TEXT("Component"));
PossibleClassNames.Add(Target + TEXT("Component"));
for (const FString& ClassName : PossibleClassNames)
{
TargetClass = FindObject<UClass>(ANY_PACKAGE, *ClassName);
if (TargetClass)
{
UE_LOG(LogTemp, Display, TEXT("Found class using alternative name '%s'"), *ClassName);
break;
}
}
}
// Special case handling for common classes like UGameplayStatics
if (!TargetClass && Target == TEXT("UGameplayStatics"))
{
// For UGameplayStatics, use a direct reference to known class
TargetClass = FindObject<UClass>(ANY_PACKAGE, TEXT("UGameplayStatics"));
if (!TargetClass)
{
// Try loading it from its known package
TargetClass = LoadObject<UClass>(nullptr, TEXT("/Script/Engine.GameplayStatics"));
UE_LOG(LogTemp, Display, TEXT("Explicitly loading GameplayStatics: %s"),
TargetClass ? TEXT("Success") : TEXT("Failed"));
}
}
// If we found a target class, look for the function there
if (TargetClass)
{
UE_LOG(LogTemp, Display, TEXT("Looking for function '%s' in class '%s'"),
*FunctionName, *TargetClass->GetName());
// First try exact name
Function = TargetClass->FindFunctionByName(*FunctionName);
// If not found, try class hierarchy
UClass* CurrentClass = TargetClass;
while (!Function && CurrentClass)
{
UE_LOG(LogTemp, Display, TEXT("Searching in class: %s"), *CurrentClass->GetName());
// Try exact match
Function = CurrentClass->FindFunctionByName(*FunctionName);
// Try case-insensitive match
if (!Function)
{
for (TFieldIterator<UFunction> FuncIt(CurrentClass); FuncIt; ++FuncIt)
{
UFunction* AvailableFunc = *FuncIt;
UE_LOG(LogTemp, Display, TEXT(" - Available function: %s"), *AvailableFunc->GetName());
if (AvailableFunc->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
{
UE_LOG(LogTemp, Display, TEXT(" - Found case-insensitive match: %s"), *AvailableFunc->GetName());
Function = AvailableFunc;
break;
}
}
}
// Move to parent class
CurrentClass = CurrentClass->GetSuperClass();
}
// Special handling for known functions
if (!Function)
{
if (TargetClass->GetName() == TEXT("GameplayStatics") &&
(FunctionName == TEXT("GetActorOfClass") || FunctionName.Equals(TEXT("GetActorOfClass"), ESearchCase::IgnoreCase)))
{
UE_LOG(LogTemp, Display, TEXT("Using special case handling for GameplayStatics::GetActorOfClass"));
// Create the function node directly
FunctionNode = NewObject<UK2Node_CallFunction>(EventGraph);
if (FunctionNode)
{
// Direct setup for known function
FunctionNode->FunctionReference.SetExternalMember(
FName(TEXT("GetActorOfClass")),
TargetClass
);
FunctionNode->NodePosX = NodePosition.X;
FunctionNode->NodePosY = NodePosition.Y;
EventGraph->AddNode(FunctionNode);
FunctionNode->CreateNewGuid();
FunctionNode->PostPlacedNewNode();
FunctionNode->AllocateDefaultPins();
UE_LOG(LogTemp, Display, TEXT("Created GetActorOfClass node directly"));
// List all pins
for (UEdGraphPin* Pin : FunctionNode->Pins)
{
UE_LOG(LogTemp, Display, TEXT(" - Pin: %s, Direction: %d, Category: %s"),
*Pin->PinName.ToString(), (int32)Pin->Direction, *Pin->PinType.PinCategory.ToString());
}
}
}
}
}
}
// If we still haven't found the function, try in the blueprint's class
if (!Function && !FunctionNode)
{
UE_LOG(LogTemp, Display, TEXT("Trying to find function in blueprint class"));
Function = Blueprint->GeneratedClass->FindFunctionByName(*FunctionName);
}
// Create the function call node if we found the function
if (Function && !FunctionNode)
{
FunctionNode = FUnrealMCPCommonUtils::CreateFunctionCallNode(EventGraph, Function, NodePosition);
}
if (!FunctionNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Function not found: %s in target %s"), *FunctionName, Target.IsEmpty() ? TEXT("Blueprint") : *Target));
}
// Set parameters if provided
if (Params->HasField(TEXT("params")))
{
const TSharedPtr<FJsonObject>* ParamsObj;
if (Params->TryGetObjectField(TEXT("params"), ParamsObj))
{
// Process parameters
for (const TPair<FString, TSharedPtr<FJsonValue>>& Param : (*ParamsObj)->Values)
{
const FString& ParamName = Param.Key;
const TSharedPtr<FJsonValue>& ParamValue = Param.Value;
// Find the parameter pin
UEdGraphPin* ParamPin = FUnrealMCPCommonUtils::FindPin(FunctionNode, ParamName, EGPD_Input);
if (ParamPin)
{
UE_LOG(LogTemp, Display, TEXT("Found parameter pin '%s' of category '%s'"),
*ParamName, *ParamPin->PinType.PinCategory.ToString());
UE_LOG(LogTemp, Display, TEXT(" Current default value: '%s'"), *ParamPin->DefaultValue);
if (ParamPin->PinType.PinSubCategoryObject.IsValid())
{
UE_LOG(LogTemp, Display, TEXT(" Pin subcategory: '%s'"),
*ParamPin->PinType.PinSubCategoryObject->GetName());
}
// Set parameter based on type
if (ParamValue->Type == EJson::String)
{
FString StringVal = ParamValue->AsString();
UE_LOG(LogTemp, Display, TEXT(" Setting string parameter '%s' to: '%s'"),
*ParamName, *StringVal);
// Handle class reference parameters (e.g., ActorClass in GetActorOfClass)
if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class)
{
// For class references, we require the exact class name with proper prefix
// - Actor classes must start with 'A' (e.g., ACameraActor)
// - Non-actor classes must start with 'U' (e.g., UObject)
const FString& ClassName = StringVal;
// TODO: This likely won't work in UE5.5+, so don't rely on it.
UClass* Class = FindObject<UClass>(ANY_PACKAGE, *ClassName);
if (!Class)
{
Class = LoadObject<UClass>(nullptr, *ClassName);
UE_LOG(LogUnrealMCP, Display, TEXT("FindObject<UClass> failed. Assuming soft path path: %s"), *ClassName);
}
// If not found, try with Engine module path
if (!Class)
{
FString EngineClassName = FString::Printf(TEXT("/Script/Engine.%s"), *ClassName);
Class = LoadObject<UClass>(nullptr, *EngineClassName);
UE_LOG(LogUnrealMCP, Display, TEXT("Trying Engine module path: %s"), *EngineClassName);
}
if (!Class)
{
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to find class '%s'. Make sure to use the exact class name with proper prefix (A for actors, U for non-actors)"), *ClassName);
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Failed to find class '%s'"), *ClassName));
}
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(EventGraph->GetSchema());
if (!K2Schema)
{
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to get K2Schema"));
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get K2Schema"));
}
K2Schema->TrySetDefaultObject(*ParamPin, Class);
if (ParamPin->DefaultObject != Class)
{
UE_LOG(LogUnrealMCP, Error, TEXT("Failed to set class reference for pin '%s' to '%s'"), *ParamPin->PinName.ToString(), *ClassName);
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Failed to set class reference for pin '%s'"), *ParamPin->PinName.ToString()));
}
UE_LOG(LogUnrealMCP, Log, TEXT("Successfully set class reference for pin '%s' to '%s'"), *ParamPin->PinName.ToString(), *ClassName);
continue;
}
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Int)
{
// Ensure we're using an integer value (no decimal)
int32 IntValue = FMath::RoundToInt(ParamValue->AsNumber());
ParamPin->DefaultValue = FString::FromInt(IntValue);
UE_LOG(LogTemp, Display, TEXT(" Set integer parameter '%s' to: %d (string: '%s')"),
*ParamName, IntValue, *ParamPin->DefaultValue);
}
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Float)
{
// For other numeric types
float FloatValue = ParamValue->AsNumber();
ParamPin->DefaultValue = FString::SanitizeFloat(FloatValue);
UE_LOG(LogTemp, Display, TEXT(" Set float parameter '%s' to: %f (string: '%s')"),
*ParamName, FloatValue, *ParamPin->DefaultValue);
}
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean)
{
bool BoolValue = ParamValue->AsBool();
ParamPin->DefaultValue = BoolValue ? TEXT("true") : TEXT("false");
UE_LOG(LogTemp, Display, TEXT(" Set boolean parameter '%s' to: %s"),
*ParamName, *ParamPin->DefaultValue);
}
else if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct && ParamPin->PinType.PinSubCategoryObject == TBaseStructure<FVector>::Get())
{
// Handle array parameters - like Vector parameters
const TArray<TSharedPtr<FJsonValue>>* ArrayValue;
if (ParamValue->TryGetArray(ArrayValue))
{
// Check if this could be a vector (array of 3 numbers)
if (ArrayValue->Num() == 3)
{
// Create a proper vector string: (X=0.0,Y=0.0,Z=1000.0)
float X = (*ArrayValue)[0]->AsNumber();
float Y = (*ArrayValue)[1]->AsNumber();
float Z = (*ArrayValue)[2]->AsNumber();
FString VectorString = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), X, Y, Z);
ParamPin->DefaultValue = VectorString;
UE_LOG(LogTemp, Display, TEXT(" Set vector parameter '%s' to: %s"),
*ParamName, *VectorString);
UE_LOG(LogTemp, Display, TEXT(" Final pin value: '%s'"),
*ParamPin->DefaultValue);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Array parameter type not fully supported yet"));
}
}
}
}
else if (ParamValue->Type == EJson::Number)
{
// Handle integer vs float parameters correctly
if (ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Int)
{
// Ensure we're using an integer value (no decimal)
int32 IntValue = FMath::RoundToInt(ParamValue->AsNumber());
ParamPin->DefaultValue = FString::FromInt(IntValue);
UE_LOG(LogTemp, Display, TEXT(" Set integer parameter '%s' to: %d (string: '%s')"),
*ParamName, IntValue, *ParamPin->DefaultValue);
}
else
{
// For other numeric types
float FloatValue = ParamValue->AsNumber();
ParamPin->DefaultValue = FString::SanitizeFloat(FloatValue);
UE_LOG(LogTemp, Display, TEXT(" Set float parameter '%s' to: %f (string: '%s')"),
*ParamName, FloatValue, *ParamPin->DefaultValue);
}
}
else if (ParamValue->Type == EJson::Boolean)
{
bool BoolValue = ParamValue->AsBool();
ParamPin->DefaultValue = BoolValue ? TEXT("true") : TEXT("false");
UE_LOG(LogTemp, Display, TEXT(" Set boolean parameter '%s' to: %s"),
*ParamName, *ParamPin->DefaultValue);
}
else if (ParamValue->Type == EJson::Array)
{
UE_LOG(LogTemp, Display, TEXT(" Processing array parameter '%s'"), *ParamName);
// Handle array parameters - like Vector parameters
const TArray<TSharedPtr<FJsonValue>>* ArrayValue;
if (ParamValue->TryGetArray(ArrayValue))
{
// Check if this could be a vector (array of 3 numbers)
if (ArrayValue->Num() == 3 &&
(ParamPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct) &&
(ParamPin->PinType.PinSubCategoryObject == TBaseStructure<FVector>::Get()))
{
// Create a proper vector string: (X=0.0,Y=0.0,Z=1000.0)
float X = (*ArrayValue)[0]->AsNumber();
float Y = (*ArrayValue)[1]->AsNumber();
float Z = (*ArrayValue)[2]->AsNumber();
FString VectorString = FString::Printf(TEXT("(X=%f,Y=%f,Z=%f)"), X, Y, Z);
ParamPin->DefaultValue = VectorString;
UE_LOG(LogTemp, Display, TEXT(" Set vector parameter '%s' to: %s"),
*ParamName, *VectorString);
UE_LOG(LogTemp, Display, TEXT(" Final pin value: '%s'"),
*ParamPin->DefaultValue);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Array parameter type not fully supported yet"));
}
}
}
// Add handling for other types as needed
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Parameter pin '%s' not found"), *ParamName);
}
}
}
}
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("node_id"), FunctionNode->NodeGuid.ToString());
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintVariable(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString VariableName;
if (!Params->TryGetStringField(TEXT("variable_name"), VariableName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'variable_name' parameter"));
}
FString VariableType;
if (!Params->TryGetStringField(TEXT("variable_type"), VariableType))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'variable_type' parameter"));
}
// Get optional parameters
bool IsExposed = false;
if (Params->HasField(TEXT("is_exposed")))
{
IsExposed = Params->GetBoolField(TEXT("is_exposed"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Create variable based on type
FEdGraphPinType PinType;
// Set up pin type based on variable_type string
if (VariableType == TEXT("Boolean"))
{
PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
}
else if (VariableType == TEXT("Integer") || VariableType == TEXT("Int"))
{
PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
}
else if (VariableType == TEXT("Float"))
{
PinType.PinCategory = UEdGraphSchema_K2::PC_Float;
}
else if (VariableType == TEXT("String"))
{
PinType.PinCategory = UEdGraphSchema_K2::PC_String;
}
else if (VariableType == TEXT("Vector"))
{
PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
PinType.PinSubCategoryObject = TBaseStructure<FVector>::Get();
}
else
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unsupported variable type: %s"), *VariableType));
}
// Create the variable
FBlueprintEditorUtils::AddMemberVariable(Blueprint, FName(*VariableName), PinType);
// Set variable properties
FBPVariableDescription* NewVar = nullptr;
for (FBPVariableDescription& Variable : Blueprint->NewVariables)
{
if (Variable.VarName == FName(*VariableName))
{
NewVar = &Variable;
break;
}
}
if (NewVar)
{
// Set exposure in editor
if (IsExposed)
{
NewVar->PropertyFlags |= CPF_Edit;
}
}
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("variable_name"), VariableName);
ResultObj->SetStringField(TEXT("variable_type"), VariableType);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintInputActionNode(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString ActionName;
if (!Params->TryGetStringField(TEXT("action_name"), ActionName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'action_name' parameter"));
}
// Get position parameters (optional)
FVector2D NodePosition(0.0f, 0.0f);
if (Params->HasField(TEXT("node_position")))
{
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Create the input action node
UK2Node_InputAction* InputActionNode = FUnrealMCPCommonUtils::CreateInputActionNode(EventGraph, ActionName, NodePosition);
if (!InputActionNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create input action node"));
}
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("node_id"), InputActionNode->NodeGuid.ToString());
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleAddBlueprintSelfReference(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
// Get position parameters (optional)
FVector2D NodePosition(0.0f, 0.0f);
if (Params->HasField(TEXT("node_position")))
{
NodePosition = FUnrealMCPCommonUtils::GetVector2DFromJson(Params, TEXT("node_position"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Create the self node
UK2Node_Self* SelfNode = FUnrealMCPCommonUtils::CreateSelfReferenceNode(EventGraph, NodePosition);
if (!SelfNode)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create self node"));
}
// Mark the blueprint as modified
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("node_id"), SelfNode->NodeGuid.ToString());
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPBlueprintNodeCommands::HandleFindBlueprintNodes(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString NodeType;
if (!Params->TryGetStringField(TEXT("node_type"), NodeType))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'node_type' parameter"));
}
// Find the blueprint
UBlueprint* Blueprint = FUnrealMCPCommonUtils::FindBlueprint(BlueprintName);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get the event graph
UEdGraph* EventGraph = FUnrealMCPCommonUtils::FindOrCreateEventGraph(Blueprint);
if (!EventGraph)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get event graph"));
}
// Create a JSON array for the node GUIDs
TArray<TSharedPtr<FJsonValue>> NodeGuidArray;
// Filter nodes by the exact requested type
if (NodeType == TEXT("Event"))
{
FString EventName;
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'event_name' parameter for Event node search"));
}
// Look for nodes with exact event name (e.g., ReceiveBeginPlay)
for (UEdGraphNode* Node : EventGraph->Nodes)
{
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
{
UE_LOG(LogTemp, Display, TEXT("Found event node with name %s: %s"), *EventName, *EventNode->NodeGuid.ToString());
NodeGuidArray.Add(MakeShared<FJsonValueString>(EventNode->NodeGuid.ToString()));
}
}
}
// Add other node types as needed (InputAction, etc.)
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetArrayField(TEXT("node_guids"), NodeGuidArray);
return ResultObj;
}

View File

@ -0,0 +1,709 @@
#include "Commands/UnrealMCPCommonUtils.h"
#include "GameFramework/Actor.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_Event.h"
#include "K2Node_CallFunction.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "K2Node_InputAction.h"
#include "K2Node_Self.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Components/StaticMeshComponent.h"
#include "Components/LightComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Components/SceneComponent.h"
#include "UObject/UObjectIterator.h"
#include "Engine/Selection.h"
#include "EditorAssetLibrary.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintActionDatabase.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
// JSON Utilities
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::CreateErrorResponse(const FString& Message)
{
TSharedPtr<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
ResponseObject->SetBoolField(TEXT("success"), false);
ResponseObject->SetStringField(TEXT("error"), Message);
return ResponseObject;
}
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::CreateSuccessResponse(const TSharedPtr<FJsonObject>& Data)
{
TSharedPtr<FJsonObject> ResponseObject = MakeShared<FJsonObject>();
ResponseObject->SetBoolField(TEXT("success"), true);
if (Data.IsValid())
{
ResponseObject->SetObjectField(TEXT("data"), Data);
}
return ResponseObject;
}
void FUnrealMCPCommonUtils::GetIntArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<int32>& OutArray)
{
OutArray.Reset();
if (!JsonObject->HasField(FieldName))
{
return;
}
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonObject->TryGetArrayField(FieldName, JsonArray))
{
for (const TSharedPtr<FJsonValue>& Value : *JsonArray)
{
OutArray.Add((int32)Value->AsNumber());
}
}
}
void FUnrealMCPCommonUtils::GetFloatArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<float>& OutArray)
{
OutArray.Reset();
if (!JsonObject->HasField(FieldName))
{
return;
}
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonObject->TryGetArrayField(FieldName, JsonArray))
{
for (const TSharedPtr<FJsonValue>& Value : *JsonArray)
{
OutArray.Add((float)Value->AsNumber());
}
}
}
FVector2D FUnrealMCPCommonUtils::GetVector2DFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
{
FVector2D Result(0.0f, 0.0f);
if (!JsonObject->HasField(FieldName))
{
return Result;
}
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 2)
{
Result.X = (float)(*JsonArray)[0]->AsNumber();
Result.Y = (float)(*JsonArray)[1]->AsNumber();
}
return Result;
}
FVector FUnrealMCPCommonUtils::GetVectorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
{
FVector Result(0.0f, 0.0f, 0.0f);
if (!JsonObject->HasField(FieldName))
{
return Result;
}
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
{
Result.X = (float)(*JsonArray)[0]->AsNumber();
Result.Y = (float)(*JsonArray)[1]->AsNumber();
Result.Z = (float)(*JsonArray)[2]->AsNumber();
}
return Result;
}
FRotator FUnrealMCPCommonUtils::GetRotatorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName)
{
FRotator Result(0.0f, 0.0f, 0.0f);
if (!JsonObject->HasField(FieldName))
{
return Result;
}
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonObject->TryGetArrayField(FieldName, JsonArray) && JsonArray->Num() >= 3)
{
Result.Pitch = (float)(*JsonArray)[0]->AsNumber();
Result.Yaw = (float)(*JsonArray)[1]->AsNumber();
Result.Roll = (float)(*JsonArray)[2]->AsNumber();
}
return Result;
}
// Blueprint Utilities
UBlueprint* FUnrealMCPCommonUtils::FindBlueprint(const FString& BlueprintName)
{
return FindBlueprintByName(BlueprintName);
}
UBlueprint* FUnrealMCPCommonUtils::FindBlueprintByName(const FString& BlueprintName)
{
FString AssetPath = TEXT("/Game/Blueprints/") + BlueprintName;
return LoadObject<UBlueprint>(nullptr, *AssetPath);
}
UEdGraph* FUnrealMCPCommonUtils::FindOrCreateEventGraph(UBlueprint* Blueprint)
{
if (!Blueprint)
{
return nullptr;
}
// Try to find the event graph
for (UEdGraph* Graph : Blueprint->UbergraphPages)
{
if (Graph->GetName().Contains(TEXT("EventGraph")))
{
return Graph;
}
}
// Create a new event graph if none exists
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FName(TEXT("EventGraph")), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
FBlueprintEditorUtils::AddUbergraphPage(Blueprint, NewGraph);
return NewGraph;
}
// Blueprint node utilities
UK2Node_Event* FUnrealMCPCommonUtils::CreateEventNode(UEdGraph* Graph, const FString& EventName, const FVector2D& Position)
{
if (!Graph)
{
return nullptr;
}
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
if (!Blueprint)
{
return nullptr;
}
// Check for existing event node with this exact name
for (UEdGraphNode* Node : Graph->Nodes)
{
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
{
UE_LOG(LogTemp, Display, TEXT("Using existing event node with name %s (ID: %s)"),
*EventName, *EventNode->NodeGuid.ToString());
return EventNode;
}
}
// No existing node found, create a new one
UK2Node_Event* EventNode = nullptr;
// Find the function to create the event
UClass* BlueprintClass = Blueprint->GeneratedClass;
UFunction* EventFunction = BlueprintClass->FindFunctionByName(FName(*EventName));
if (EventFunction)
{
EventNode = NewObject<UK2Node_Event>(Graph);
EventNode->EventReference.SetExternalMember(FName(*EventName), BlueprintClass);
EventNode->NodePosX = Position.X;
EventNode->NodePosY = Position.Y;
Graph->AddNode(EventNode, true);
EventNode->PostPlacedNewNode();
EventNode->AllocateDefaultPins();
UE_LOG(LogTemp, Display, TEXT("Created new event node with name %s (ID: %s)"),
*EventName, *EventNode->NodeGuid.ToString());
}
else
{
UE_LOG(LogTemp, Error, TEXT("Failed to find function for event name: %s"), *EventName);
}
return EventNode;
}
UK2Node_CallFunction* FUnrealMCPCommonUtils::CreateFunctionCallNode(UEdGraph* Graph, UFunction* Function, const FVector2D& Position)
{
if (!Graph || !Function)
{
return nullptr;
}
UK2Node_CallFunction* FunctionNode = NewObject<UK2Node_CallFunction>(Graph);
FunctionNode->SetFromFunction(Function);
FunctionNode->NodePosX = Position.X;
FunctionNode->NodePosY = Position.Y;
Graph->AddNode(FunctionNode, true);
FunctionNode->CreateNewGuid();
FunctionNode->PostPlacedNewNode();
FunctionNode->AllocateDefaultPins();
return FunctionNode;
}
UK2Node_VariableGet* FUnrealMCPCommonUtils::CreateVariableGetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position)
{
if (!Graph || !Blueprint)
{
return nullptr;
}
UK2Node_VariableGet* VariableGetNode = NewObject<UK2Node_VariableGet>(Graph);
FName VarName(*VariableName);
FProperty* Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
if (Property)
{
VariableGetNode->VariableReference.SetFromField<FProperty>(Property, false);
VariableGetNode->NodePosX = Position.X;
VariableGetNode->NodePosY = Position.Y;
Graph->AddNode(VariableGetNode, true);
VariableGetNode->PostPlacedNewNode();
VariableGetNode->AllocateDefaultPins();
return VariableGetNode;
}
return nullptr;
}
UK2Node_VariableSet* FUnrealMCPCommonUtils::CreateVariableSetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position)
{
if (!Graph || !Blueprint)
{
return nullptr;
}
UK2Node_VariableSet* VariableSetNode = NewObject<UK2Node_VariableSet>(Graph);
FName VarName(*VariableName);
FProperty* Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
if (Property)
{
VariableSetNode->VariableReference.SetFromField<FProperty>(Property, false);
VariableSetNode->NodePosX = Position.X;
VariableSetNode->NodePosY = Position.Y;
Graph->AddNode(VariableSetNode, true);
VariableSetNode->PostPlacedNewNode();
VariableSetNode->AllocateDefaultPins();
return VariableSetNode;
}
return nullptr;
}
UK2Node_InputAction* FUnrealMCPCommonUtils::CreateInputActionNode(UEdGraph* Graph, const FString& ActionName, const FVector2D& Position)
{
if (!Graph)
{
return nullptr;
}
UK2Node_InputAction* InputActionNode = NewObject<UK2Node_InputAction>(Graph);
InputActionNode->InputActionName = FName(*ActionName);
InputActionNode->NodePosX = Position.X;
InputActionNode->NodePosY = Position.Y;
Graph->AddNode(InputActionNode, true);
InputActionNode->CreateNewGuid();
InputActionNode->PostPlacedNewNode();
InputActionNode->AllocateDefaultPins();
return InputActionNode;
}
UK2Node_Self* FUnrealMCPCommonUtils::CreateSelfReferenceNode(UEdGraph* Graph, const FVector2D& Position)
{
if (!Graph)
{
return nullptr;
}
UK2Node_Self* SelfNode = NewObject<UK2Node_Self>(Graph);
SelfNode->NodePosX = Position.X;
SelfNode->NodePosY = Position.Y;
Graph->AddNode(SelfNode, true);
SelfNode->CreateNewGuid();
SelfNode->PostPlacedNewNode();
SelfNode->AllocateDefaultPins();
return SelfNode;
}
bool FUnrealMCPCommonUtils::ConnectGraphNodes(UEdGraph* Graph, UEdGraphNode* SourceNode, const FString& SourcePinName,
UEdGraphNode* TargetNode, const FString& TargetPinName)
{
if (!Graph || !SourceNode || !TargetNode)
{
return false;
}
UEdGraphPin* SourcePin = FindPin(SourceNode, SourcePinName, EGPD_Output);
UEdGraphPin* TargetPin = FindPin(TargetNode, TargetPinName, EGPD_Input);
if (SourcePin && TargetPin)
{
SourcePin->MakeLinkTo(TargetPin);
return true;
}
return false;
}
UEdGraphPin* FUnrealMCPCommonUtils::FindPin(UEdGraphNode* Node, const FString& PinName, EEdGraphPinDirection Direction)
{
if (!Node)
{
return nullptr;
}
// Log all pins for debugging
UE_LOG(LogTemp, Display, TEXT("FindPin: Looking for pin '%s' (Direction: %d) in node '%s'"),
*PinName, (int32)Direction, *Node->GetName());
for (UEdGraphPin* Pin : Node->Pins)
{
UE_LOG(LogTemp, Display, TEXT(" - Available pin: '%s', Direction: %d, Category: %s"),
*Pin->PinName.ToString(), (int32)Pin->Direction, *Pin->PinType.PinCategory.ToString());
}
// First try exact match
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin->PinName.ToString() == PinName && (Direction == EGPD_MAX || Pin->Direction == Direction))
{
UE_LOG(LogTemp, Display, TEXT(" - Found exact matching pin: '%s'"), *Pin->PinName.ToString());
return Pin;
}
}
// If no exact match and we're looking for a component reference, try case-insensitive match
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin->PinName.ToString().Equals(PinName, ESearchCase::IgnoreCase) &&
(Direction == EGPD_MAX || Pin->Direction == Direction))
{
UE_LOG(LogTemp, Display, TEXT(" - Found case-insensitive matching pin: '%s'"), *Pin->PinName.ToString());
return Pin;
}
}
// If we're looking for a component output and didn't find it by name, try to find the first data output pin
if (Direction == EGPD_Output && Cast<UK2Node_VariableGet>(Node) != nullptr)
{
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
UE_LOG(LogTemp, Display, TEXT(" - Found fallback data output pin: '%s'"), *Pin->PinName.ToString());
return Pin;
}
}
}
UE_LOG(LogTemp, Warning, TEXT(" - No matching pin found for '%s'"), *PinName);
return nullptr;
}
// Actor utilities
TSharedPtr<FJsonValue> FUnrealMCPCommonUtils::ActorToJson(AActor* Actor)
{
if (!Actor)
{
return MakeShared<FJsonValueNull>();
}
TSharedPtr<FJsonObject> ActorObject = MakeShared<FJsonObject>();
ActorObject->SetStringField(TEXT("name"), Actor->GetName());
ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
FVector Location = Actor->GetActorLocation();
TArray<TSharedPtr<FJsonValue>> LocationArray;
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.X));
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Y));
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Z));
ActorObject->SetArrayField(TEXT("location"), LocationArray);
FRotator Rotation = Actor->GetActorRotation();
TArray<TSharedPtr<FJsonValue>> RotationArray;
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Pitch));
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Yaw));
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Roll));
ActorObject->SetArrayField(TEXT("rotation"), RotationArray);
FVector Scale = Actor->GetActorScale3D();
TArray<TSharedPtr<FJsonValue>> ScaleArray;
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.X));
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Y));
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Z));
ActorObject->SetArrayField(TEXT("scale"), ScaleArray);
return MakeShared<FJsonValueObject>(ActorObject);
}
TSharedPtr<FJsonObject> FUnrealMCPCommonUtils::ActorToJsonObject(AActor* Actor, bool bDetailed)
{
if (!Actor)
{
return nullptr;
}
TSharedPtr<FJsonObject> ActorObject = MakeShared<FJsonObject>();
ActorObject->SetStringField(TEXT("name"), Actor->GetName());
ActorObject->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
FVector Location = Actor->GetActorLocation();
TArray<TSharedPtr<FJsonValue>> LocationArray;
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.X));
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Y));
LocationArray.Add(MakeShared<FJsonValueNumber>(Location.Z));
ActorObject->SetArrayField(TEXT("location"), LocationArray);
FRotator Rotation = Actor->GetActorRotation();
TArray<TSharedPtr<FJsonValue>> RotationArray;
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Pitch));
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Yaw));
RotationArray.Add(MakeShared<FJsonValueNumber>(Rotation.Roll));
ActorObject->SetArrayField(TEXT("rotation"), RotationArray);
FVector Scale = Actor->GetActorScale3D();
TArray<TSharedPtr<FJsonValue>> ScaleArray;
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.X));
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Y));
ScaleArray.Add(MakeShared<FJsonValueNumber>(Scale.Z));
ActorObject->SetArrayField(TEXT("scale"), ScaleArray);
return ActorObject;
}
UK2Node_Event* FUnrealMCPCommonUtils::FindExistingEventNode(UEdGraph* Graph, const FString& EventName)
{
if (!Graph)
{
return nullptr;
}
// Look for existing event nodes
for (UEdGraphNode* Node : Graph->Nodes)
{
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
if (EventNode && EventNode->EventReference.GetMemberName() == FName(*EventName))
{
UE_LOG(LogTemp, Display, TEXT("Found existing event node with name: %s"), *EventName);
return EventNode;
}
}
return nullptr;
}
bool FUnrealMCPCommonUtils::SetObjectProperty(UObject* Object, const FString& PropertyName,
const TSharedPtr<FJsonValue>& Value, FString& OutErrorMessage)
{
if (!Object)
{
OutErrorMessage = TEXT("Invalid object");
return false;
}
FProperty* Property = Object->GetClass()->FindPropertyByName(*PropertyName);
if (!Property)
{
OutErrorMessage = FString::Printf(TEXT("Property not found: %s"), *PropertyName);
return false;
}
void* PropertyAddr = Property->ContainerPtrToValuePtr<void>(Object);
// Handle different property types
if (Property->IsA<FBoolProperty>())
{
((FBoolProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsBool());
return true;
}
else if (Property->IsA<FIntProperty>())
{
int32 IntValue = static_cast<int32>(Value->AsNumber());
FIntProperty* IntProperty = CastField<FIntProperty>(Property);
if (IntProperty)
{
IntProperty->SetPropertyValue_InContainer(Object, IntValue);
return true;
}
}
else if (Property->IsA<FFloatProperty>())
{
((FFloatProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsNumber());
return true;
}
else if (Property->IsA<FStrProperty>())
{
((FStrProperty*)Property)->SetPropertyValue(PropertyAddr, Value->AsString());
return true;
}
else if (Property->IsA<FByteProperty>())
{
FByteProperty* ByteProp = CastField<FByteProperty>(Property);
UEnum* EnumDef = ByteProp ? ByteProp->GetIntPropertyEnum() : nullptr;
// If this is a TEnumAsByte property (has associated enum)
if (EnumDef)
{
// Handle numeric value
if (Value->Type == EJson::Number)
{
uint8 ByteValue = static_cast<uint8>(Value->AsNumber());
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric value: %d"),
*PropertyName, ByteValue);
return true;
}
// Handle string enum value
else if (Value->Type == EJson::String)
{
FString EnumValueName = Value->AsString();
// Try to convert numeric string to number first
if (EnumValueName.IsNumeric())
{
uint8 ByteValue = FCString::Atoi(*EnumValueName);
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric string value: %s -> %d"),
*PropertyName, *EnumValueName, ByteValue);
return true;
}
// Handle qualified enum names (e.g., "Player0" or "EAutoReceiveInput::Player0")
if (EnumValueName.Contains(TEXT("::")))
{
EnumValueName.Split(TEXT("::"), nullptr, &EnumValueName);
}
int64 EnumValue = EnumDef->GetValueByNameString(EnumValueName);
if (EnumValue == INDEX_NONE)
{
// Try with full name as fallback
EnumValue = EnumDef->GetValueByNameString(Value->AsString());
}
if (EnumValue != INDEX_NONE)
{
ByteProp->SetPropertyValue(PropertyAddr, static_cast<uint8>(EnumValue));
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to name value: %s -> %lld"),
*PropertyName, *EnumValueName, EnumValue);
return true;
}
else
{
// Log all possible enum values for debugging
UE_LOG(LogTemp, Warning, TEXT("Could not find enum value for '%s'. Available options:"), *EnumValueName);
for (int32 i = 0; i < EnumDef->NumEnums(); i++)
{
UE_LOG(LogTemp, Warning, TEXT(" - %s (value: %d)"),
*EnumDef->GetNameStringByIndex(i), EnumDef->GetValueByIndex(i));
}
OutErrorMessage = FString::Printf(TEXT("Could not find enum value for '%s'"), *EnumValueName);
return false;
}
}
}
else
{
// Regular byte property
uint8 ByteValue = static_cast<uint8>(Value->AsNumber());
ByteProp->SetPropertyValue(PropertyAddr, ByteValue);
return true;
}
}
else if (Property->IsA<FEnumProperty>())
{
FEnumProperty* EnumProp = CastField<FEnumProperty>(Property);
UEnum* EnumDef = EnumProp ? EnumProp->GetEnum() : nullptr;
FNumericProperty* UnderlyingNumericProp = EnumProp ? EnumProp->GetUnderlyingProperty() : nullptr;
if (EnumDef && UnderlyingNumericProp)
{
// Handle numeric value
if (Value->Type == EJson::Number)
{
int64 EnumValue = static_cast<int64>(Value->AsNumber());
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric value: %lld"),
*PropertyName, EnumValue);
return true;
}
// Handle string enum value
else if (Value->Type == EJson::String)
{
FString EnumValueName = Value->AsString();
// Try to convert numeric string to number first
if (EnumValueName.IsNumeric())
{
int64 EnumValue = FCString::Atoi64(*EnumValueName);
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to numeric string value: %s -> %lld"),
*PropertyName, *EnumValueName, EnumValue);
return true;
}
// Handle qualified enum names
if (EnumValueName.Contains(TEXT("::")))
{
EnumValueName.Split(TEXT("::"), nullptr, &EnumValueName);
}
int64 EnumValue = EnumDef->GetValueByNameString(EnumValueName);
if (EnumValue == INDEX_NONE)
{
// Try with full name as fallback
EnumValue = EnumDef->GetValueByNameString(Value->AsString());
}
if (EnumValue != INDEX_NONE)
{
UnderlyingNumericProp->SetIntPropertyValue(PropertyAddr, EnumValue);
UE_LOG(LogTemp, Display, TEXT("Setting enum property %s to name value: %s -> %lld"),
*PropertyName, *EnumValueName, EnumValue);
return true;
}
else
{
// Log all possible enum values for debugging
UE_LOG(LogTemp, Warning, TEXT("Could not find enum value for '%s'. Available options:"), *EnumValueName);
for (int32 i = 0; i < EnumDef->NumEnums(); i++)
{
UE_LOG(LogTemp, Warning, TEXT(" - %s (value: %d)"),
*EnumDef->GetNameStringByIndex(i), EnumDef->GetValueByIndex(i));
}
OutErrorMessage = FString::Printf(TEXT("Could not find enum value for '%s'"), *EnumValueName);
return false;
}
}
}
}
OutErrorMessage = FString::Printf(TEXT("Unsupported property type: %s for property %s"),
*Property->GetClass()->GetName(), *PropertyName);
return false;
}

View File

@ -0,0 +1,600 @@
#include "Commands/UnrealMCPEditorCommands.h"
#include "Commands/UnrealMCPCommonUtils.h"
#include "Editor.h"
#include "EditorViewportClient.h"
#include "LevelEditorViewport.h"
#include "ImageUtils.h"
#include "HighResScreenshot.h"
#include "Engine/GameViewportClient.h"
#include "Misc/FileHelper.h"
#include "GameFramework/Actor.h"
#include "Engine/Selection.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/DirectionalLight.h"
#include "Engine/PointLight.h"
#include "Engine/SpotLight.h"
#include "Camera/CameraActor.h"
#include "Components/StaticMeshComponent.h"
#include "EditorSubsystem.h"
#include "Subsystems/EditorActorSubsystem.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
{
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
{
// Actor manipulation commands
if (CommandType == TEXT("get_actors_in_level"))
{
return HandleGetActorsInLevel(Params);
}
else if (CommandType == TEXT("find_actors_by_name"))
{
return HandleFindActorsByName(Params);
}
else if (CommandType == TEXT("spawn_actor") || CommandType == TEXT("create_actor"))
{
if (CommandType == TEXT("create_actor"))
{
UE_LOG(LogTemp, Warning, TEXT("'create_actor' command is deprecated and will be removed in a future version. Please use 'spawn_actor' instead."));
}
return HandleSpawnActor(Params);
}
else if (CommandType == TEXT("delete_actor"))
{
return HandleDeleteActor(Params);
}
else if (CommandType == TEXT("set_actor_transform"))
{
return HandleSetActorTransform(Params);
}
else if (CommandType == TEXT("get_actor_properties"))
{
return HandleGetActorProperties(Params);
}
else if (CommandType == TEXT("set_actor_property"))
{
return HandleSetActorProperty(Params);
}
// Blueprint actor spawning
else if (CommandType == TEXT("spawn_blueprint_actor"))
{
return HandleSpawnBlueprintActor(Params);
}
// Editor viewport commands
else if (CommandType == TEXT("focus_viewport"))
{
return HandleFocusViewport(Params);
}
else if (CommandType == TEXT("take_screenshot"))
{
return HandleTakeScreenshot(Params);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown editor command: %s"), *CommandType));
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params)
{
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
TArray<TSharedPtr<FJsonValue>> ActorArray;
for (AActor* Actor : AllActors)
{
if (Actor)
{
ActorArray.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
}
}
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetArrayField(TEXT("actors"), ActorArray);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params)
{
FString Pattern;
if (!Params->TryGetStringField(TEXT("pattern"), Pattern))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'pattern' parameter"));
}
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
TArray<TSharedPtr<FJsonValue>> MatchingActors;
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName().Contains(Pattern))
{
MatchingActors.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
}
}
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetArrayField(TEXT("actors"), MatchingActors);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnActor(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString ActorType;
if (!Params->TryGetStringField(TEXT("type"), ActorType))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'type' parameter"));
}
// Get actor name (required parameter)
FString ActorName;
if (!Params->TryGetStringField(TEXT("name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
// Get optional transform parameters
FVector Location(0.0f, 0.0f, 0.0f);
FRotator Rotation(0.0f, 0.0f, 0.0f);
FVector Scale(1.0f, 1.0f, 1.0f);
if (Params->HasField(TEXT("location")))
{
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
}
if (Params->HasField(TEXT("rotation")))
{
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
}
if (Params->HasField(TEXT("scale")))
{
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
}
// Create the actor based on type
AActor* NewActor = nullptr;
UWorld* World = GEditor->GetEditorWorldContext().World();
if (!World)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
}
// Check if an actor with this name already exists
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == ActorName)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor with name '%s' already exists"), *ActorName));
}
}
FActorSpawnParameters SpawnParams;
SpawnParams.Name = *ActorName;
if (ActorType == TEXT("StaticMeshActor"))
{
NewActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), Location, Rotation, SpawnParams);
}
else if (ActorType == TEXT("PointLight"))
{
NewActor = World->SpawnActor<APointLight>(APointLight::StaticClass(), Location, Rotation, SpawnParams);
}
else if (ActorType == TEXT("SpotLight"))
{
NewActor = World->SpawnActor<ASpotLight>(ASpotLight::StaticClass(), Location, Rotation, SpawnParams);
}
else if (ActorType == TEXT("DirectionalLight"))
{
NewActor = World->SpawnActor<ADirectionalLight>(ADirectionalLight::StaticClass(), Location, Rotation, SpawnParams);
}
else if (ActorType == TEXT("CameraActor"))
{
NewActor = World->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), Location, Rotation, SpawnParams);
}
else
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown actor type: %s"), *ActorType));
}
if (NewActor)
{
// Set scale (since SpawnActor only takes location and rotation)
FTransform Transform = NewActor->GetTransform();
Transform.SetScale3D(Scale);
NewActor->SetActorTransform(Transform);
// Return the created actor's details
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create actor"));
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleDeleteActor(const TSharedPtr<FJsonObject>& Params)
{
FString ActorName;
if (!Params->TryGetStringField(TEXT("name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == ActorName)
{
// Store actor info before deletion for the response
TSharedPtr<FJsonObject> ActorInfo = FUnrealMCPCommonUtils::ActorToJsonObject(Actor);
// Delete the actor
Actor->Destroy();
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetObjectField(TEXT("deleted_actor"), ActorInfo);
return ResultObj;
}
}
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params)
{
// Get actor name
FString ActorName;
if (!Params->TryGetStringField(TEXT("name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
// Find the actor
AActor* TargetActor = nullptr;
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == ActorName)
{
TargetActor = Actor;
break;
}
}
if (!TargetActor)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
}
// Get transform parameters
FTransform NewTransform = TargetActor->GetTransform();
if (Params->HasField(TEXT("location")))
{
NewTransform.SetLocation(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location")));
}
if (Params->HasField(TEXT("rotation")))
{
NewTransform.SetRotation(FQuat(FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"))));
}
if (Params->HasField(TEXT("scale")))
{
NewTransform.SetScale3D(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale")));
}
// Set the new transform
TargetActor->SetActorTransform(NewTransform);
// Return updated actor info
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params)
{
// Get actor name
FString ActorName;
if (!Params->TryGetStringField(TEXT("name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
// Find the actor
AActor* TargetActor = nullptr;
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == ActorName)
{
TargetActor = Actor;
break;
}
}
if (!TargetActor)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
}
// Always return detailed properties for this command
return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params)
{
// Get actor name
FString ActorName;
if (!Params->TryGetStringField(TEXT("name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
// Find the actor
AActor* TargetActor = nullptr;
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == ActorName)
{
TargetActor = Actor;
break;
}
}
if (!TargetActor)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
}
// Get property name
FString PropertyName;
if (!Params->TryGetStringField(TEXT("property_name"), PropertyName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter"));
}
// Get property value
if (!Params->HasField(TEXT("property_value")))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter"));
}
TSharedPtr<FJsonValue> PropertyValue = Params->Values.FindRef(TEXT("property_value"));
// Set the property using our utility function
FString ErrorMessage;
if (FUnrealMCPCommonUtils::SetObjectProperty(TargetActor, PropertyName, PropertyValue, ErrorMessage))
{
// Property set successfully
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("actor"), ActorName);
ResultObj->SetStringField(TEXT("property"), PropertyName);
ResultObj->SetBoolField(TEXT("success"), true);
// Also include the full actor details
ResultObj->SetObjectField(TEXT("actor_details"), FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true));
return ResultObj;
}
else
{
return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage);
}
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString ActorName;
if (!Params->TryGetStringField(TEXT("actor_name"), ActorName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'actor_name' parameter"));
}
// Find the blueprint
if (BlueprintName.IsEmpty())
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Blueprint name is empty"));
}
FString Root = TEXT("/Game/Blueprints/");
FString AssetPath = Root + BlueprintName;
if (!FPackageName::DoesPackageExist(AssetPath))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint '%s' not found it must reside under /Game/Blueprints"), *BlueprintName));
}
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *AssetPath);
if (!Blueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
}
// Get transform parameters
FVector Location(0.0f, 0.0f, 0.0f);
FRotator Rotation(0.0f, 0.0f, 0.0f);
FVector Scale(1.0f, 1.0f, 1.0f);
if (Params->HasField(TEXT("location")))
{
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
}
if (Params->HasField(TEXT("rotation")))
{
Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
}
if (Params->HasField(TEXT("scale")))
{
Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
}
// Spawn the actor
UWorld* World = GEditor->GetEditorWorldContext().World();
if (!World)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
}
FTransform SpawnTransform;
SpawnTransform.SetLocation(Location);
SpawnTransform.SetRotation(FQuat(Rotation));
SpawnTransform.SetScale3D(Scale);
FActorSpawnParameters SpawnParams;
SpawnParams.Name = *ActorName;
AActor* NewActor = World->SpawnActor<AActor>(Blueprint->GeneratedClass, SpawnTransform, SpawnParams);
if (NewActor)
{
return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor"));
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFocusViewport(const TSharedPtr<FJsonObject>& Params)
{
// Get target actor name if provided
FString TargetActorName;
bool HasTargetActor = Params->TryGetStringField(TEXT("target"), TargetActorName);
// Get location if provided
FVector Location(0.0f, 0.0f, 0.0f);
bool HasLocation = false;
if (Params->HasField(TEXT("location")))
{
Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
HasLocation = true;
}
// Get distance
float Distance = 1000.0f;
if (Params->HasField(TEXT("distance")))
{
Distance = Params->GetNumberField(TEXT("distance"));
}
// Get orientation if provided
FRotator Orientation(0.0f, 0.0f, 0.0f);
bool HasOrientation = false;
if (Params->HasField(TEXT("orientation")))
{
Orientation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("orientation"));
HasOrientation = true;
}
// Get the active viewport
FLevelEditorViewportClient* ViewportClient = (FLevelEditorViewportClient*)GEditor->GetActiveViewport()->GetClient();
if (!ViewportClient)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get active viewport"));
}
// If we have a target actor, focus on it
if (HasTargetActor)
{
// Find the actor
AActor* TargetActor = nullptr;
TArray<AActor*> AllActors;
UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
for (AActor* Actor : AllActors)
{
if (Actor && Actor->GetName() == TargetActorName)
{
TargetActor = Actor;
break;
}
}
if (!TargetActor)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *TargetActorName));
}
// Focus on the actor
ViewportClient->SetViewLocation(TargetActor->GetActorLocation() - FVector(Distance, 0.0f, 0.0f));
}
// Otherwise use the provided location
else if (HasLocation)
{
ViewportClient->SetViewLocation(Location - FVector(Distance, 0.0f, 0.0f));
}
else
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Either 'target' or 'location' must be provided"));
}
// Set orientation if provided
if (HasOrientation)
{
ViewportClient->SetViewRotation(Orientation);
}
// Force viewport to redraw
ViewportClient->Invalidate();
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetBoolField(TEXT("success"), true);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params)
{
// Get file path parameter
FString FilePath;
if (!Params->TryGetStringField(TEXT("filepath"), FilePath))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'filepath' parameter"));
}
// Ensure the file path has a proper extension
if (!FilePath.EndsWith(TEXT(".png")))
{
FilePath += TEXT(".png");
}
// Get the active viewport
if (GEditor && GEditor->GetActiveViewport())
{
FViewport* Viewport = GEditor->GetActiveViewport();
TArray<FColor> Bitmap;
FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y);
if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
{
TArray<uint8> CompressedBitmap;
FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap);
if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
{
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("filepath"), FilePath);
return ResultObj;
}
}
}
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
}

View File

@ -0,0 +1,72 @@
#include "Commands/UnrealMCPProjectCommands.h"
#include "Commands/UnrealMCPCommonUtils.h"
#include "GameFramework/InputSettings.h"
FUnrealMCPProjectCommands::FUnrealMCPProjectCommands()
{
}
TSharedPtr<FJsonObject> FUnrealMCPProjectCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
{
if (CommandType == TEXT("create_input_mapping"))
{
return HandleCreateInputMapping(Params);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown project command: %s"), *CommandType));
}
TSharedPtr<FJsonObject> FUnrealMCPProjectCommands::HandleCreateInputMapping(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString ActionName;
if (!Params->TryGetStringField(TEXT("action_name"), ActionName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'action_name' parameter"));
}
FString Key;
if (!Params->TryGetStringField(TEXT("key"), Key))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'key' parameter"));
}
// Get the input settings
UInputSettings* InputSettings = GetMutableDefault<UInputSettings>();
if (!InputSettings)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get input settings"));
}
// Create the input action mapping
FInputActionKeyMapping ActionMapping;
ActionMapping.ActionName = FName(*ActionName);
ActionMapping.Key = FKey(*Key);
// Add modifiers if provided
if (Params->HasField(TEXT("shift")))
{
ActionMapping.bShift = Params->GetBoolField(TEXT("shift"));
}
if (Params->HasField(TEXT("ctrl")))
{
ActionMapping.bCtrl = Params->GetBoolField(TEXT("ctrl"));
}
if (Params->HasField(TEXT("alt")))
{
ActionMapping.bAlt = Params->GetBoolField(TEXT("alt"));
}
if (Params->HasField(TEXT("cmd")))
{
ActionMapping.bCmd = Params->GetBoolField(TEXT("cmd"));
}
// Add the mapping
InputSettings->AddActionMapping(ActionMapping);
InputSettings->SaveConfig();
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("action_name"), ActionName);
ResultObj->SetStringField(TEXT("key"), Key);
return ResultObj;
}

View File

@ -0,0 +1,544 @@
#include "Commands/UnrealMCPUMGCommands.h"
#include "Commands/UnrealMCPCommonUtils.h"
#include "Editor.h"
#include "EditorAssetLibrary.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h"
#include "WidgetBlueprint.h"
// We'll create widgets using regular Factory classes
#include "Factories/Factory.h"
// Remove problematic includes that don't exist in UE 5.5
// #include "UMGEditorSubsystem.h"
// #include "WidgetBlueprintFactory.h"
#include "WidgetBlueprintEditor.h"
#include "Blueprint/WidgetTree.h"
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
#include "JsonObjectConverter.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Components/Button.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_CallFunction.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "K2Node_Event.h"
FUnrealMCPUMGCommands::FUnrealMCPUMGCommands()
{
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleCommand(const FString& CommandName, const TSharedPtr<FJsonObject>& Params)
{
if (CommandName == TEXT("create_umg_widget_blueprint"))
{
return HandleCreateUMGWidgetBlueprint(Params);
}
else if (CommandName == TEXT("add_text_block_to_widget"))
{
return HandleAddTextBlockToWidget(Params);
}
else if (CommandName == TEXT("add_widget_to_viewport"))
{
return HandleAddWidgetToViewport(Params);
}
else if (CommandName == TEXT("add_button_to_widget"))
{
return HandleAddButtonToWidget(Params);
}
else if (CommandName == TEXT("bind_widget_event"))
{
return HandleBindWidgetEvent(Params);
}
else if (CommandName == TEXT("set_text_block_binding"))
{
return HandleSetTextBlockBinding(Params);
}
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown UMG command: %s"), *CommandName));
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleCreateUMGWidgetBlueprint(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
}
// Create the full asset path
FString PackagePath = TEXT("/Game/Widgets/");
FString AssetName = BlueprintName;
FString FullPath = PackagePath + AssetName;
// Check if asset already exists
if (UEditorAssetLibrary::DoesAssetExist(FullPath))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' already exists"), *BlueprintName));
}
// Create package
UPackage* Package = CreatePackage(*FullPath);
if (!Package)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create package"));
}
// Create Widget Blueprint using KismetEditorUtilities
UBlueprint* NewBlueprint = FKismetEditorUtilities::CreateBlueprint(
UUserWidget::StaticClass(), // Parent class
Package, // Outer package
FName(*AssetName), // Blueprint name
BPTYPE_Normal, // Blueprint type
UBlueprint::StaticClass(), // Blueprint class
UBlueprintGeneratedClass::StaticClass(), // Generated class
FName("CreateUMGWidget") // Creation method name
);
// Make sure the Blueprint was created successfully
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(NewBlueprint);
if (!WidgetBlueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create Widget Blueprint"));
}
// Add a default Canvas Panel if one doesn't exist
if (!WidgetBlueprint->WidgetTree->RootWidget)
{
UCanvasPanel* RootCanvas = WidgetBlueprint->WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass());
WidgetBlueprint->WidgetTree->RootWidget = RootCanvas;
}
// Mark the package dirty and notify asset registry
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(WidgetBlueprint);
// Compile the blueprint
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
// Create success response
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("name"), BlueprintName);
ResultObj->SetStringField(TEXT("path"), FullPath);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddTextBlockToWidget(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
FString WidgetName;
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'widget_name' parameter"));
}
// Find the Widget Blueprint
FString FullPath = TEXT("/Game/Widgets/") + BlueprintName;
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(FullPath));
if (!WidgetBlueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' not found"), *BlueprintName));
}
// Get optional parameters
FString InitialText = TEXT("New Text Block");
Params->TryGetStringField(TEXT("text"), InitialText);
FVector2D Position(0.0f, 0.0f);
if (Params->HasField(TEXT("position")))
{
const TArray<TSharedPtr<FJsonValue>>* PosArray;
if (Params->TryGetArrayField(TEXT("position"), PosArray) && PosArray->Num() >= 2)
{
Position.X = (*PosArray)[0]->AsNumber();
Position.Y = (*PosArray)[1]->AsNumber();
}
}
// Create Text Block widget
UTextBlock* TextBlock = WidgetBlueprint->WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), *WidgetName);
if (!TextBlock)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create Text Block widget"));
}
// Set initial text
TextBlock->SetText(FText::FromString(InitialText));
// Add to canvas panel
UCanvasPanel* RootCanvas = Cast<UCanvasPanel>(WidgetBlueprint->WidgetTree->RootWidget);
if (!RootCanvas)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Root Canvas Panel not found"));
}
UCanvasPanelSlot* PanelSlot = RootCanvas->AddChildToCanvas(TextBlock);
PanelSlot->SetPosition(Position);
// Mark the package dirty and compile
WidgetBlueprint->MarkPackageDirty();
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
// Create success response
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("widget_name"), WidgetName);
ResultObj->SetStringField(TEXT("text"), InitialText);
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddWidgetToViewport(const TSharedPtr<FJsonObject>& Params)
{
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
}
// Find the Widget Blueprint
FString FullPath = TEXT("/Game/Widgets/") + BlueprintName;
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(FullPath));
if (!WidgetBlueprint)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Widget Blueprint '%s' not found"), *BlueprintName));
}
// Get optional Z-order parameter
int32 ZOrder = 0;
Params->TryGetNumberField(TEXT("z_order"), ZOrder);
// Create widget instance
UClass* WidgetClass = WidgetBlueprint->GeneratedClass;
if (!WidgetClass)
{
return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get widget class"));
}
// Note: This creates the widget but doesn't add it to viewport
// The actual addition to viewport should be done through Blueprint nodes
// as it requires a game context
// Create success response with instructions
TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
ResultObj->SetStringField(TEXT("blueprint_name"), BlueprintName);
ResultObj->SetStringField(TEXT("class_path"), WidgetClass->GetPathName());
ResultObj->SetNumberField(TEXT("z_order"), ZOrder);
ResultObj->SetStringField(TEXT("note"), TEXT("Widget class ready. Use CreateWidget and AddToViewport nodes in Blueprint to display in game."));
return ResultObj;
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleAddButtonToWidget(const TSharedPtr<FJsonObject>& Params)
{
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
return Response;
}
FString WidgetName;
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
return Response;
}
FString ButtonText;
if (!Params->TryGetStringField(TEXT("text"), ButtonText))
{
Response->SetStringField(TEXT("error"), TEXT("Missing text parameter"));
return Response;
}
// Load the Widget Blueprint
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
if (!WidgetBlueprint)
{
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
return Response;
}
// Create Button widget
UButton* Button = NewObject<UButton>(WidgetBlueprint->GeneratedClass->GetDefaultObject(), UButton::StaticClass(), *WidgetName);
if (!Button)
{
Response->SetStringField(TEXT("error"), TEXT("Failed to create Button widget"));
return Response;
}
// Set button text
UTextBlock* ButtonTextBlock = NewObject<UTextBlock>(Button, UTextBlock::StaticClass(), *(WidgetName + TEXT("_Text")));
if (ButtonTextBlock)
{
ButtonTextBlock->SetText(FText::FromString(ButtonText));
Button->AddChild(ButtonTextBlock);
}
// Get canvas panel and add button
UCanvasPanel* RootCanvas = Cast<UCanvasPanel>(WidgetBlueprint->WidgetTree->RootWidget);
if (!RootCanvas)
{
Response->SetStringField(TEXT("error"), TEXT("Root widget is not a Canvas Panel"));
return Response;
}
// Add to canvas and set position
UCanvasPanelSlot* ButtonSlot = RootCanvas->AddChildToCanvas(Button);
if (ButtonSlot)
{
const TArray<TSharedPtr<FJsonValue>>* Position;
if (Params->TryGetArrayField(TEXT("position"), Position) && Position->Num() >= 2)
{
FVector2D Pos(
(*Position)[0]->AsNumber(),
(*Position)[1]->AsNumber()
);
ButtonSlot->SetPosition(Pos);
}
}
// Save the Widget Blueprint
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
Response->SetBoolField(TEXT("success"), true);
Response->SetStringField(TEXT("widget_name"), WidgetName);
return Response;
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleBindWidgetEvent(const TSharedPtr<FJsonObject>& Params)
{
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
return Response;
}
FString WidgetName;
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
return Response;
}
FString EventName;
if (!Params->TryGetStringField(TEXT("event_name"), EventName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing event_name parameter"));
return Response;
}
// Load the Widget Blueprint
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
if (!WidgetBlueprint)
{
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
return Response;
}
// Create the event graph if it doesn't exist
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(WidgetBlueprint);
if (!EventGraph)
{
Response->SetStringField(TEXT("error"), TEXT("Failed to find or create event graph"));
return Response;
}
// Find the widget in the blueprint
UWidget* Widget = WidgetBlueprint->WidgetTree->FindWidget(*WidgetName);
if (!Widget)
{
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to find widget: %s"), *WidgetName));
return Response;
}
// Create the event node (e.g., OnClicked for buttons)
UK2Node_Event* EventNode = nullptr;
// Find existing nodes first
TArray<UK2Node_Event*> AllEventNodes;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(WidgetBlueprint, AllEventNodes);
for (UK2Node_Event* Node : AllEventNodes)
{
if (Node->CustomFunctionName == FName(*EventName) && Node->EventReference.GetMemberParentClass() == Widget->GetClass())
{
EventNode = Node;
break;
}
}
// If no existing node, create a new one
if (!EventNode)
{
// Calculate position - place it below existing nodes
float MaxHeight = 0.0f;
for (UEdGraphNode* Node : EventGraph->Nodes)
{
MaxHeight = FMath::Max(MaxHeight, Node->NodePosY);
}
const FVector2D NodePos(200, MaxHeight + 200);
// Call CreateNewBoundEventForClass, which returns void, so we can't capture the return value directly
// We'll need to find the node after creating it
FKismetEditorUtilities::CreateNewBoundEventForClass(
Widget->GetClass(),
FName(*EventName),
WidgetBlueprint,
nullptr // We don't need a specific property binding
);
// Now find the newly created node
TArray<UK2Node_Event*> UpdatedEventNodes;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(WidgetBlueprint, UpdatedEventNodes);
for (UK2Node_Event* Node : UpdatedEventNodes)
{
if (Node->CustomFunctionName == FName(*EventName) && Node->EventReference.GetMemberParentClass() == Widget->GetClass())
{
EventNode = Node;
// Set position of the node
EventNode->NodePosX = NodePos.X;
EventNode->NodePosY = NodePos.Y;
break;
}
}
}
if (!EventNode)
{
Response->SetStringField(TEXT("error"), TEXT("Failed to create event node"));
return Response;
}
// Save the Widget Blueprint
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
Response->SetBoolField(TEXT("success"), true);
Response->SetStringField(TEXT("event_name"), EventName);
return Response;
}
TSharedPtr<FJsonObject> FUnrealMCPUMGCommands::HandleSetTextBlockBinding(const TSharedPtr<FJsonObject>& Params)
{
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
// Get required parameters
FString BlueprintName;
if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing blueprint_name parameter"));
return Response;
}
FString WidgetName;
if (!Params->TryGetStringField(TEXT("widget_name"), WidgetName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing widget_name parameter"));
return Response;
}
FString BindingName;
if (!Params->TryGetStringField(TEXT("binding_name"), BindingName))
{
Response->SetStringField(TEXT("error"), TEXT("Missing binding_name parameter"));
return Response;
}
// Load the Widget Blueprint
const FString BlueprintPath = FString::Printf(TEXT("/Game/Widgets/%s.%s"), *BlueprintName, *BlueprintName);
UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(UEditorAssetLibrary::LoadAsset(BlueprintPath));
if (!WidgetBlueprint)
{
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load Widget Blueprint: %s"), *BlueprintPath));
return Response;
}
// Create a variable for binding if it doesn't exist
FBlueprintEditorUtils::AddMemberVariable(
WidgetBlueprint,
FName(*BindingName),
FEdGraphPinType(UEdGraphSchema_K2::PC_Text, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType())
);
// Find the TextBlock widget
UTextBlock* TextBlock = Cast<UTextBlock>(WidgetBlueprint->WidgetTree->FindWidget(FName(*WidgetName)));
if (!TextBlock)
{
Response->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to find TextBlock widget: %s"), *WidgetName));
return Response;
}
// Create binding function
const FString FunctionName = FString::Printf(TEXT("Get%s"), *BindingName);
UEdGraph* FuncGraph = FBlueprintEditorUtils::CreateNewGraph(
WidgetBlueprint,
FName(*FunctionName),
UEdGraph::StaticClass(),
UEdGraphSchema_K2::StaticClass()
);
if (FuncGraph)
{
// Add the function to the blueprint with proper template parameter
// Template requires null for last parameter when not using a signature-source
FBlueprintEditorUtils::AddFunctionGraph<UClass>(WidgetBlueprint, FuncGraph, false, nullptr);
// Create entry node
UK2Node_FunctionEntry* EntryNode = nullptr;
// Create entry node - use the API that exists in UE 5.5
EntryNode = NewObject<UK2Node_FunctionEntry>(FuncGraph);
FuncGraph->AddNode(EntryNode, false, false);
EntryNode->NodePosX = 0;
EntryNode->NodePosY = 0;
EntryNode->FunctionReference.SetExternalMember(FName(*FunctionName), WidgetBlueprint->GeneratedClass);
EntryNode->AllocateDefaultPins();
// Create get variable node
UK2Node_VariableGet* GetVarNode = NewObject<UK2Node_VariableGet>(FuncGraph);
GetVarNode->VariableReference.SetSelfMember(FName(*BindingName));
FuncGraph->AddNode(GetVarNode, false, false);
GetVarNode->NodePosX = 200;
GetVarNode->NodePosY = 0;
GetVarNode->AllocateDefaultPins();
// Connect nodes
UEdGraphPin* EntryThenPin = EntryNode->FindPin(UEdGraphSchema_K2::PN_Then);
UEdGraphPin* GetVarOutPin = GetVarNode->FindPin(UEdGraphSchema_K2::PN_ReturnValue);
if (EntryThenPin && GetVarOutPin)
{
EntryThenPin->MakeLinkTo(GetVarOutPin);
}
}
// Save the Widget Blueprint
FKismetEditorUtilities::CompileBlueprint(WidgetBlueprint);
UEditorAssetLibrary::SaveAsset(BlueprintPath, false);
Response->SetBoolField(TEXT("success"), true);
Response->SetStringField(TEXT("binding_name"), BindingName);
return Response;
}

View File

@ -0,0 +1,321 @@
#include "MCPServerRunnable.h"
#include "UnrealMCPBridge.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
#include "Interfaces/IPv4/IPv4Address.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonReader.h"
#include "JsonObjectConverter.h"
#include "Misc/ScopeLock.h"
#include "HAL/PlatformTime.h"
// Buffer size for receiving data
const int32 BufferSize = 8192;
FMCPServerRunnable::FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket)
: Bridge(InBridge)
, ListenerSocket(InListenerSocket)
, bRunning(true)
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Created server runnable"));
}
FMCPServerRunnable::~FMCPServerRunnable()
{
// Note: We don't delete the sockets here as they're owned by the bridge
}
bool FMCPServerRunnable::Init()
{
return true;
}
uint32 FMCPServerRunnable::Run()
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread starting..."));
while (bRunning)
{
// UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Waiting for client connection..."));
bool bPending = false;
if (ListenerSocket->HasPendingConnection(bPending) && bPending)
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection pending, accepting..."));
ClientSocket = MakeShareable(ListenerSocket->Accept(TEXT("MCPClient")));
if (ClientSocket.IsValid())
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection accepted"));
// Set socket options to improve connection stability
ClientSocket->SetNoDelay(true);
int32 SocketBufferSize = 65536; // 64KB buffer
ClientSocket->SetSendBufferSize(SocketBufferSize, SocketBufferSize);
ClientSocket->SetReceiveBufferSize(SocketBufferSize, SocketBufferSize);
uint8 Buffer[8192];
while (bRunning)
{
int32 BytesRead = 0;
if (ClientSocket->Recv(Buffer, sizeof(Buffer), BytesRead))
{
if (BytesRead == 0)
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client disconnected (zero bytes)"));
break;
}
// Convert received data to string
Buffer[BytesRead] = '\0';
FString ReceivedText = UTF8_TO_TCHAR(Buffer);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received: %s"), *ReceivedText);
// Parse JSON
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ReceivedText);
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
// Get command type
FString CommandType;
if (JsonObject->TryGetStringField(TEXT("type"), CommandType))
{
// Execute command
FString Response = Bridge->ExecuteCommand(CommandType, JsonObject->GetObjectField(TEXT("params")));
// Log response for debugging
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
// Send response
int32 BytesSent = 0;
if (!ClientSocket->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response"));
}
else {
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Response sent successfully, bytes: %d"), BytesSent);
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Missing 'type' field in command"));
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse JSON from: %s"), *ReceivedText);
}
}
else
{
int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
// Don't break the connection for WouldBlock error, which is normal for non-blocking sockets
bool bShouldBreak = true;
// Check for "would block" error which isn't a real error for non-blocking sockets
if (LastError == SE_EWOULDBLOCK)
{
UE_LOG(LogTemp, Verbose, TEXT("MCPServerRunnable: Socket would block, continuing..."));
bShouldBreak = false;
// Small sleep to prevent tight loop when no data
FPlatformProcess::Sleep(0.01f);
}
// Check for other transient errors we might want to tolerate
else if (LastError == SE_EINTR) // Interrupted system call
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket read interrupted, continuing..."));
bShouldBreak = false;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Client disconnected or error. Last error code: %d"), LastError);
}
if (bShouldBreak)
{
break;
}
}
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to accept client connection"));
}
}
// Small sleep to prevent tight loop
FPlatformProcess::Sleep(0.1f);
}
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread stopping"));
return 0;
}
void FMCPServerRunnable::Stop()
{
bRunning = false;
}
void FMCPServerRunnable::Exit()
{
}
void FMCPServerRunnable::HandleClientConnection(TSharedPtr<FSocket> InClientSocket)
{
if (!InClientSocket.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Invalid client socket passed to HandleClientConnection"));
return;
}
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting to handle client connection"));
// Set socket options for better connection stability
InClientSocket->SetNonBlocking(false);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Set socket to blocking mode"));
// Properly read full message with timeout
const int32 MaxBufferSize = 4096;
uint8 Buffer[MaxBufferSize];
FString MessageBuffer;
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting message receive loop"));
while (bRunning && InClientSocket.IsValid())
{
// Log socket state
bool bIsConnected = InClientSocket->GetConnectionState() == SCS_Connected;
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Socket state - Connected: %s"),
bIsConnected ? TEXT("true") : TEXT("false"));
// Log pending data status before receive
uint32 PendingDataSize = 0;
bool HasPendingData = InClientSocket->HasPendingData(PendingDataSize);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Before Recv - HasPendingData=%s, Size=%d"),
HasPendingData ? TEXT("true") : TEXT("false"), PendingDataSize);
// Try to receive data with timeout
int32 BytesRead = 0;
bool bReadSuccess = false;
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Attempting to receive data..."));
bReadSuccess = InClientSocket->Recv(Buffer, MaxBufferSize, BytesRead, ESocketReceiveFlags::None);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Recv attempt complete - Success=%s, BytesRead=%d"),
bReadSuccess ? TEXT("true") : TEXT("false"), BytesRead);
if (BytesRead > 0)
{
// Log raw data for debugging
FString HexData;
for (int32 i = 0; i < FMath::Min(BytesRead, 50); ++i)
{
HexData += FString::Printf(TEXT("%02X "), Buffer[i]);
}
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Raw data (first 50 bytes hex): %s%s"),
*HexData, BytesRead > 50 ? TEXT("...") : TEXT(""));
// Convert and log received data
Buffer[BytesRead] = 0; // Null terminate
FString ReceivedData = UTF8_TO_TCHAR(Buffer);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received data as string: '%s'"), *ReceivedData);
// Append to message buffer
MessageBuffer.Append(ReceivedData);
// Process complete messages (messages are terminated with newline)
if (MessageBuffer.Contains(TEXT("\n")))
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Newline detected in buffer, processing messages"));
TArray<FString> Messages;
MessageBuffer.ParseIntoArray(Messages, TEXT("\n"), true);
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Found %d message(s) in buffer"), Messages.Num());
// Process all complete messages
for (int32 i = 0; i < Messages.Num() - 1; ++i)
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message %d: '%s'"),
i + 1, *Messages[i]);
ProcessMessage(InClientSocket, Messages[i]);
}
// Keep any incomplete message in the buffer
MessageBuffer = Messages.Last();
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Remaining buffer after processing: %s"),
*MessageBuffer);
}
else
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: No complete message yet (no newline detected)"));
}
}
else if (!bReadSuccess)
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Connection closed or error occurred - Last error: %d"),
(int32)ISocketSubsystem::Get()->GetLastErrorCode());
break;
}
// Small sleep to prevent tight loop
FPlatformProcess::Sleep(0.01f);
}
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Exited message receive loop"));
}
void FMCPServerRunnable::ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message)
{
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message: %s"), *Message);
// Parse message as JSON
TSharedPtr<FJsonObject> JsonMessage;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
if (!FJsonSerializer::Deserialize(Reader, JsonMessage) || !JsonMessage.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse message as JSON"));
return;
}
// Extract command type and parameters using MCP protocol format
FString CommandType;
TSharedPtr<FJsonObject> Params = MakeShareable(new FJsonObject());
if (!JsonMessage->TryGetStringField(TEXT("command"), CommandType))
{
UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Message missing 'command' field"));
return;
}
// Parameters are optional in MCP protocol
if (JsonMessage->HasField(TEXT("params")))
{
TSharedPtr<FJsonValue> ParamsValue = JsonMessage->TryGetField(TEXT("params"));
if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
{
Params = ParamsValue->AsObject();
}
}
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Executing command: %s"), *CommandType);
// Execute command
FString Response = Bridge->ExecuteCommand(CommandType, Params);
// Send response with newline terminator
Response += TEXT("\n");
int32 BytesSent = 0;
UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
if (!Client->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
{
UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Failed to send response"));
}
}

View File

@ -0,0 +1,332 @@
#include "UnrealMCPBridge.h"
#include "MCPServerRunnable.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
#include "HAL/RunnableThread.h"
#include "Interfaces/IPv4/IPv4Address.h"
#include "Interfaces/IPv4/IPv4Endpoint.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonWriter.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/DirectionalLight.h"
#include "Engine/PointLight.h"
#include "Engine/SpotLight.h"
#include "Camera/CameraActor.h"
#include "EditorAssetLibrary.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "JsonObjectConverter.h"
#include "GameFramework/Actor.h"
#include "Engine/Selection.h"
#include "Kismet/GameplayStatics.h"
#include "Async/Async.h"
// Add Blueprint related includes
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Factories/BlueprintFactory.h"
#include "EdGraphSchema_K2.h"
#include "K2Node_Event.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Components/StaticMeshComponent.h"
#include "Components/BoxComponent.h"
#include "Components/SphereComponent.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
// UE5.5 correct includes
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "UObject/Field.h"
#include "UObject/FieldPath.h"
// Blueprint Graph specific includes
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_CallFunction.h"
#include "K2Node_InputAction.h"
#include "K2Node_Self.h"
#include "GameFramework/InputSettings.h"
#include "EditorSubsystem.h"
#include "Subsystems/EditorActorSubsystem.h"
// Include our new command handler classes
#include "Commands/UnrealMCPEditorCommands.h"
#include "Commands/UnrealMCPBlueprintCommands.h"
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
#include "Commands/UnrealMCPProjectCommands.h"
#include "Commands/UnrealMCPCommonUtils.h"
#include "Commands/UnrealMCPUMGCommands.h"
// Default settings
#define MCP_SERVER_HOST "127.0.0.1"
#define MCP_SERVER_PORT 55557
UUnrealMCPBridge::UUnrealMCPBridge()
{
EditorCommands = MakeShared<FUnrealMCPEditorCommands>();
BlueprintCommands = MakeShared<FUnrealMCPBlueprintCommands>();
BlueprintNodeCommands = MakeShared<FUnrealMCPBlueprintNodeCommands>();
ProjectCommands = MakeShared<FUnrealMCPProjectCommands>();
UMGCommands = MakeShared<FUnrealMCPUMGCommands>();
}
UUnrealMCPBridge::~UUnrealMCPBridge()
{
EditorCommands.Reset();
BlueprintCommands.Reset();
BlueprintNodeCommands.Reset();
ProjectCommands.Reset();
UMGCommands.Reset();
}
// Initialize subsystem
void UUnrealMCPBridge::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Initializing"));
bIsRunning = false;
ListenerSocket = nullptr;
ConnectionSocket = nullptr;
ServerThread = nullptr;
Port = MCP_SERVER_PORT;
FIPv4Address::Parse(MCP_SERVER_HOST, ServerAddress);
// Start the server automatically
StartServer();
}
// Clean up resources when subsystem is destroyed
void UUnrealMCPBridge::Deinitialize()
{
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Shutting down"));
StopServer();
}
// Start the MCP server
void UUnrealMCPBridge::StartServer()
{
if (bIsRunning)
{
UE_LOG(LogTemp, Warning, TEXT("UnrealMCPBridge: Server is already running"));
return;
}
// Create socket subsystem
ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
if (!SocketSubsystem)
{
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to get socket subsystem"));
return;
}
// Create listener socket
TSharedPtr<FSocket> NewListenerSocket = MakeShareable(SocketSubsystem->CreateSocket(NAME_Stream, TEXT("UnrealMCPListener"), false));
if (!NewListenerSocket.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create listener socket"));
return;
}
// Allow address reuse for quick restarts
NewListenerSocket->SetReuseAddr(true);
NewListenerSocket->SetNonBlocking(true);
// Bind to address
FIPv4Endpoint Endpoint(ServerAddress, Port);
if (!NewListenerSocket->Bind(*Endpoint.ToInternetAddr()))
{
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to bind listener socket to %s:%d"), *ServerAddress.ToString(), Port);
return;
}
// Start listening
if (!NewListenerSocket->Listen(5))
{
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to start listening"));
return;
}
ListenerSocket = NewListenerSocket;
bIsRunning = true;
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server started on %s:%d"), *ServerAddress.ToString(), Port);
// Start server thread
ServerThread = FRunnableThread::Create(
new FMCPServerRunnable(this, ListenerSocket),
TEXT("UnrealMCPServerThread"),
0, TPri_Normal
);
if (!ServerThread)
{
UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create server thread"));
StopServer();
return;
}
}
// Stop the MCP server
void UUnrealMCPBridge::StopServer()
{
if (!bIsRunning)
{
return;
}
bIsRunning = false;
// Clean up thread
if (ServerThread)
{
ServerThread->Kill(true);
delete ServerThread;
ServerThread = nullptr;
}
// Close sockets
if (ConnectionSocket.IsValid())
{
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket.Get());
ConnectionSocket.Reset();
}
if (ListenerSocket.IsValid())
{
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket.Get());
ListenerSocket.Reset();
}
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server stopped"));
}
// Execute a command received from a client
FString UUnrealMCPBridge::ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
{
UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Executing command: %s"), *CommandType);
// Create a promise to wait for the result
TPromise<FString> Promise;
TFuture<FString> Future = Promise.GetFuture();
// Queue execution on Game Thread
AsyncTask(ENamedThreads::GameThread, [this, CommandType, Params, Promise = MoveTemp(Promise)]() mutable
{
TSharedPtr<FJsonObject> ResponseJson = MakeShareable(new FJsonObject);
try
{
TSharedPtr<FJsonObject> ResultJson;
if (CommandType == TEXT("ping"))
{
ResultJson = MakeShareable(new FJsonObject);
ResultJson->SetStringField(TEXT("message"), TEXT("pong"));
}
// Editor Commands (including actor manipulation)
else if (CommandType == TEXT("get_actors_in_level") ||
CommandType == TEXT("find_actors_by_name") ||
CommandType == TEXT("spawn_actor") ||
CommandType == TEXT("create_actor") ||
CommandType == TEXT("delete_actor") ||
CommandType == TEXT("set_actor_transform") ||
CommandType == TEXT("get_actor_properties") ||
CommandType == TEXT("set_actor_property") ||
CommandType == TEXT("spawn_blueprint_actor") ||
CommandType == TEXT("focus_viewport") ||
CommandType == TEXT("take_screenshot"))
{
ResultJson = EditorCommands->HandleCommand(CommandType, Params);
}
// Blueprint Commands
else if (CommandType == TEXT("create_blueprint") ||
CommandType == TEXT("add_component_to_blueprint") ||
CommandType == TEXT("set_component_property") ||
CommandType == TEXT("set_physics_properties") ||
CommandType == TEXT("compile_blueprint") ||
CommandType == TEXT("set_blueprint_property") ||
CommandType == TEXT("set_static_mesh_properties") ||
CommandType == TEXT("set_pawn_properties"))
{
ResultJson = BlueprintCommands->HandleCommand(CommandType, Params);
}
// Blueprint Node Commands
else if (CommandType == TEXT("connect_blueprint_nodes") ||
CommandType == TEXT("add_blueprint_get_self_component_reference") ||
CommandType == TEXT("add_blueprint_self_reference") ||
CommandType == TEXT("find_blueprint_nodes") ||
CommandType == TEXT("add_blueprint_event_node") ||
CommandType == TEXT("add_blueprint_input_action_node") ||
CommandType == TEXT("add_blueprint_function_node") ||
CommandType == TEXT("add_blueprint_get_component_node") ||
CommandType == TEXT("add_blueprint_variable"))
{
ResultJson = BlueprintNodeCommands->HandleCommand(CommandType, Params);
}
// Project Commands
else if (CommandType == TEXT("create_input_mapping"))
{
ResultJson = ProjectCommands->HandleCommand(CommandType, Params);
}
// UMG Commands
else if (CommandType == TEXT("create_umg_widget_blueprint") ||
CommandType == TEXT("add_text_block_to_widget") ||
CommandType == TEXT("add_button_to_widget") ||
CommandType == TEXT("bind_widget_event") ||
CommandType == TEXT("set_text_block_binding") ||
CommandType == TEXT("add_widget_to_viewport"))
{
ResultJson = UMGCommands->HandleCommand(CommandType, Params);
}
else
{
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
ResponseJson->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command: %s"), *CommandType));
FString ResultString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
Promise.SetValue(ResultString);
return;
}
// Check if the result contains an error
bool bSuccess = true;
FString ErrorMessage;
if (ResultJson->HasField(TEXT("success")))
{
bSuccess = ResultJson->GetBoolField(TEXT("success"));
if (!bSuccess && ResultJson->HasField(TEXT("error")))
{
ErrorMessage = ResultJson->GetStringField(TEXT("error"));
}
}
if (bSuccess)
{
// Set success status and include the result
ResponseJson->SetStringField(TEXT("status"), TEXT("success"));
ResponseJson->SetObjectField(TEXT("result"), ResultJson);
}
else
{
// Set error status and include the error message
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
ResponseJson->SetStringField(TEXT("error"), ErrorMessage);
}
}
catch (const std::exception& e)
{
ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
ResponseJson->SetStringField(TEXT("error"), UTF8_TO_TCHAR(e.what()));
}
FString ResultString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
Promise.SetValue(ResultString);
});
return Future.Get();
}

View File

@ -0,0 +1,21 @@
#include "UnrealMCPModule.h"
#include "UnrealMCPBridge.h"
#include "Modules/ModuleManager.h"
#include "EditorSubsystem.h"
#include "Editor.h"
#define LOCTEXT_NAMESPACE "FUnrealMCPModule"
void FUnrealMCPModule::StartupModule()
{
UE_LOG(LogTemp, Display, TEXT("Unreal MCP Module has started"));
}
void FUnrealMCPModule::ShutdownModule()
{
UE_LOG(LogTemp, Display, TEXT("Unreal MCP Module has shut down"));
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FUnrealMCPModule, UnrealMCP)

View File

@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
/**
* Handler class for Blueprint-related MCP commands
*/
class UNREALMCP_API FUnrealMCPBlueprintCommands
{
public:
FUnrealMCPBlueprintCommands();
// Handle blueprint commands
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
// Specific blueprint command handlers
TSharedPtr<FJsonObject> HandleCreateBlueprint(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddComponentToBlueprint(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetComponentProperty(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetPhysicsProperties(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleCompileBlueprint(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetBlueprintProperty(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetStaticMeshProperties(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetPawnProperties(const TSharedPtr<FJsonObject>& Params);
// Helper functions
TSharedPtr<FJsonObject> AddComponentToBlueprint(const FString& BlueprintName, const FString& ComponentType,
const FString& ComponentName, const FString& MeshType,
const TArray<float>& Location, const TArray<float>& Rotation,
const TArray<float>& Scale, const TSharedPtr<FJsonObject>& ComponentProperties);
};

View File

@ -0,0 +1,27 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
/**
* Handler class for Blueprint Node-related MCP commands
*/
class UNREALMCP_API FUnrealMCPBlueprintNodeCommands
{
public:
FUnrealMCPBlueprintNodeCommands();
// Handle blueprint node commands
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
// Specific blueprint node command handlers
TSharedPtr<FJsonObject> HandleConnectBlueprintNodes(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintGetSelfComponentReference(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintEvent(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintFunctionCall(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintVariable(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintInputActionNode(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleAddBlueprintSelfReference(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleFindBlueprintNodes(const TSharedPtr<FJsonObject>& Params);
};

View File

@ -0,0 +1,59 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
// Forward declarations
class AActor;
class UBlueprint;
class UEdGraph;
class UEdGraphNode;
class UEdGraphPin;
class UK2Node_Event;
class UK2Node_CallFunction;
class UK2Node_VariableGet;
class UK2Node_VariableSet;
class UK2Node_InputAction;
class UK2Node_Self;
class UFunction;
/**
* Common utilities for UnrealMCP commands
*/
class UNREALMCP_API FUnrealMCPCommonUtils
{
public:
// JSON utilities
static TSharedPtr<FJsonObject> CreateErrorResponse(const FString& Message);
static TSharedPtr<FJsonObject> CreateSuccessResponse(const TSharedPtr<FJsonObject>& Data = nullptr);
static void GetIntArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<int32>& OutArray);
static void GetFloatArrayFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName, TArray<float>& OutArray);
static FVector2D GetVector2DFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
static FVector GetVectorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
static FRotator GetRotatorFromJson(const TSharedPtr<FJsonObject>& JsonObject, const FString& FieldName);
// Actor utilities
static TSharedPtr<FJsonValue> ActorToJson(AActor* Actor);
static TSharedPtr<FJsonObject> ActorToJsonObject(AActor* Actor, bool bDetailed = false);
// Blueprint utilities
static UBlueprint* FindBlueprint(const FString& BlueprintName);
static UBlueprint* FindBlueprintByName(const FString& BlueprintName);
static UEdGraph* FindOrCreateEventGraph(UBlueprint* Blueprint);
// Blueprint node utilities
static UK2Node_Event* CreateEventNode(UEdGraph* Graph, const FString& EventName, const FVector2D& Position);
static UK2Node_CallFunction* CreateFunctionCallNode(UEdGraph* Graph, UFunction* Function, const FVector2D& Position);
static UK2Node_VariableGet* CreateVariableGetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position);
static UK2Node_VariableSet* CreateVariableSetNode(UEdGraph* Graph, UBlueprint* Blueprint, const FString& VariableName, const FVector2D& Position);
static UK2Node_InputAction* CreateInputActionNode(UEdGraph* Graph, const FString& ActionName, const FVector2D& Position);
static UK2Node_Self* CreateSelfReferenceNode(UEdGraph* Graph, const FVector2D& Position);
static bool ConnectGraphNodes(UEdGraph* Graph, UEdGraphNode* SourceNode, const FString& SourcePinName,
UEdGraphNode* TargetNode, const FString& TargetPinName);
static UEdGraphPin* FindPin(UEdGraphNode* Node, const FString& PinName, EEdGraphPinDirection Direction = EGPD_MAX);
static UK2Node_Event* FindExistingEventNode(UEdGraph* Graph, const FString& EventName);
// Property utilities
static bool SetObjectProperty(UObject* Object, const FString& PropertyName,
const TSharedPtr<FJsonValue>& Value, FString& OutErrorMessage);
};

View File

@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
/**
* Handler class for Editor-related MCP commands
* Handles viewport control, actor manipulation, and level management
*/
class UNREALMCP_API FUnrealMCPEditorCommands
{
public:
FUnrealMCPEditorCommands();
// Handle editor commands
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
// Actor manipulation commands
TSharedPtr<FJsonObject> HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSpawnActor(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleDeleteActor(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params);
// Blueprint actor spawning
TSharedPtr<FJsonObject> HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params);
// Editor viewport commands
TSharedPtr<FJsonObject> HandleFocusViewport(const TSharedPtr<FJsonObject>& Params);
TSharedPtr<FJsonObject> HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params);
};

View File

@ -0,0 +1,20 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
/**
* Handler class for Project-wide MCP commands
*/
class UNREALMCP_API FUnrealMCPProjectCommands
{
public:
FUnrealMCPProjectCommands();
// Handle project commands
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
// Specific project command handlers
TSharedPtr<FJsonObject> HandleCreateInputMapping(const TSharedPtr<FJsonObject>& Params);
};

View File

@ -0,0 +1,82 @@
#pragma once
#include "CoreMinimal.h"
#include "Json.h"
/**
* Handles UMG (Widget Blueprint) related MCP commands
* Responsible for creating and modifying UMG Widget Blueprints,
* adding widget components, and managing widget instances in the viewport.
*/
class UNREALMCP_API FUnrealMCPUMGCommands
{
public:
FUnrealMCPUMGCommands();
/**
* Handle UMG-related commands
* @param CommandType - The type of command to handle
* @param Params - JSON parameters for the command
* @return JSON response with results or error
*/
TSharedPtr<FJsonObject> HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
/**
* Create a new UMG Widget Blueprint
* @param Params - Must include "name" for the blueprint name
* @return JSON response with the created blueprint details
*/
TSharedPtr<FJsonObject> HandleCreateUMGWidgetBlueprint(const TSharedPtr<FJsonObject>& Params);
/**
* Add a Text Block widget to a UMG Widget Blueprint
* @param Params - Must include:
* "blueprint_name" - Name of the target Widget Blueprint
* "widget_name" - Name for the new Text Block
* "text" - Initial text content (optional)
* "position" - [X, Y] position in the canvas (optional)
* @return JSON response with the added widget details
*/
TSharedPtr<FJsonObject> HandleAddTextBlockToWidget(const TSharedPtr<FJsonObject>& Params);
/**
* Add a widget instance to the game viewport
* @param Params - Must include:
* "blueprint_name" - Name of the Widget Blueprint to instantiate
* "z_order" - Z-order for widget display (optional)
* @return JSON response with the widget instance details
*/
TSharedPtr<FJsonObject> HandleAddWidgetToViewport(const TSharedPtr<FJsonObject>& Params);
/**
* Add a Button widget to a UMG Widget Blueprint
* @param Params - Must include:
* "blueprint_name" - Name of the target Widget Blueprint
* "widget_name" - Name for the new Button
* "text" - Button text
* "position" - [X, Y] position in the canvas
* @return JSON response with the added widget details
*/
TSharedPtr<FJsonObject> HandleAddButtonToWidget(const TSharedPtr<FJsonObject>& Params);
/**
* Bind an event to a widget (e.g. button click)
* @param Params - Must include:
* "blueprint_name" - Name of the target Widget Blueprint
* "widget_name" - Name of the widget to bind
* "event_name" - Name of the event to bind
* @return JSON response with the binding details
*/
TSharedPtr<FJsonObject> HandleBindWidgetEvent(const TSharedPtr<FJsonObject>& Params);
/**
* Set up text block binding for dynamic updates
* @param Params - Must include:
* "blueprint_name" - Name of the target Widget Blueprint
* "widget_name" - Name of the widget to bind
* "binding_name" - Name of the binding to set up
* @return JSON response with the binding details
*/
TSharedPtr<FJsonObject> HandleSetTextBlockBinding(const TSharedPtr<FJsonObject>& Params);
};

View File

@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "HAL/Runnable.h"
#include "Sockets.h"
#include "Interfaces/IPv4/IPv4Address.h"
class UUnrealMCPBridge;
/**
* Runnable class for the MCP server thread
*/
class FMCPServerRunnable : public FRunnable
{
public:
FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket);
virtual ~FMCPServerRunnable();
// FRunnable interface
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
protected:
void HandleClientConnection(TSharedPtr<FSocket> ClientSocket);
void ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message);
private:
UUnrealMCPBridge* Bridge;
TSharedPtr<FSocket> ListenerSocket;
TSharedPtr<FSocket> ClientSocket;
bool bRunning;
};

View File

@ -0,0 +1,64 @@
#pragma once
#include "CoreMinimal.h"
#include "EditorSubsystem.h"
#include "Sockets.h"
#include "SocketSubsystem.h"
#include "Http.h"
#include "Json.h"
#include "Interfaces/IPv4/IPv4Address.h"
#include "Interfaces/IPv4/IPv4Endpoint.h"
#include "Commands/UnrealMCPEditorCommands.h"
#include "Commands/UnrealMCPBlueprintCommands.h"
#include "Commands/UnrealMCPBlueprintNodeCommands.h"
#include "Commands/UnrealMCPProjectCommands.h"
#include "Commands/UnrealMCPUMGCommands.h"
#include "UnrealMCPBridge.generated.h"
class FMCPServerRunnable;
/**
* Editor subsystem for MCP Bridge
* Handles communication between external tools and the Unreal Editor
* through a TCP socket connection. Commands are received as JSON and
* routed to appropriate command handlers.
*/
UCLASS()
class UNREALMCP_API UUnrealMCPBridge : public UEditorSubsystem
{
GENERATED_BODY()
public:
UUnrealMCPBridge();
virtual ~UUnrealMCPBridge();
// UEditorSubsystem implementation
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// Server functions
void StartServer();
void StopServer();
bool IsRunning() const { return bIsRunning; }
// Command execution
FString ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params);
private:
// Server state
bool bIsRunning;
TSharedPtr<FSocket> ListenerSocket;
TSharedPtr<FSocket> ConnectionSocket;
FRunnableThread* ServerThread;
// Server configuration
FIPv4Address ServerAddress;
uint16 Port;
// Command handler instances
TSharedPtr<FUnrealMCPEditorCommands> EditorCommands;
TSharedPtr<FUnrealMCPBlueprintCommands> BlueprintCommands;
TSharedPtr<FUnrealMCPBlueprintNodeCommands> BlueprintNodeCommands;
TSharedPtr<FUnrealMCPProjectCommands> ProjectCommands;
TSharedPtr<FUnrealMCPUMGCommands> UMGCommands;
};

View File

@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FUnrealMCPModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
static inline FUnrealMCPModule& Get()
{
return FModuleManager::LoadModuleChecked<FUnrealMCPModule>("UnrealMCP");
}
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("UnrealMCP");
}
};

View File

@ -0,0 +1,78 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class UnrealMCP : ModuleRules
{
public UnrealMCP(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
// Use IWYUSupport instead of the deprecated bEnforceIWYU in UE5.5
IWYUSupport = IWYUSupport.Full;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"Networking",
"Sockets",
"HTTP",
"Json",
"JsonUtilities",
"DeveloperSettings"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"UnrealEd",
"EditorScriptingUtilities",
"EditorSubsystem",
"Slate",
"SlateCore",
"UMG",
"Kismet",
"KismetCompiler",
"BlueprintGraph",
"Projects",
"AssetRegistry"
}
);
if (Target.bBuildEditor == true)
{
PrivateDependencyModuleNames.AddRange(
new string[]
{
"PropertyEditor", // For widget property editing
"ToolMenus", // For editor UI
"BlueprintEditorLibrary", // For Blueprint utilities
"UMGEditor" // For WidgetBlueprint.h and other UMG editor functionality
}
);
}
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@ -0,0 +1,35 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "UnrealMCP",
"Description": "Model Context Protocol implementation for Unreal Engine",
"Category": "Editor",
"CreatedBy": "Your Name",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "UnrealMCP",
"Type": "Editor",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64",
"Mac",
"Linux"
]
}
],
"Plugins": [
{
"Name": "EditorScriptingUtilities",
"Enabled": true
}
]
}

View File

@ -0,0 +1,8 @@
[CrashReportClient]
bHideLogFilesOption=false
bIsAllowedToCloseWithoutSending=true
CrashConfigPurgeDays=2
Stall.RecordDump=false
Ensure.RecordDump=true
bAgreeToCrashUpload=false

View File

@ -1349,7 +1349,7 @@ SlateFileDialogs.TimeStamp=2025.02.05-21.36.32
SlateFileDialogs.LastCompileMethod=Unknown
[AssetEditorSubsystem]
CleanShutdown=True
CleanShutdown=False
DebuggerAttached=False
[PluginBrowser]