ue-mcp 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -20,13 +20,15 @@ npx ue-mcp init
|
|
|
20
20
|
|
|
21
21
|
The interactive setup will:
|
|
22
22
|
|
|
23
|
-
1.
|
|
23
|
+
1. Find your `.uproject` (auto-detects in current directory)
|
|
24
24
|
2. Let you choose which tool categories to enable
|
|
25
25
|
3. Deploy the C++ bridge plugin to your project
|
|
26
26
|
4. Enable required UE plugins (Niagara, PCG, GAS, etc.)
|
|
27
27
|
5. Detect and configure your MCP client (Claude Code, Claude Desktop, Cursor)
|
|
28
28
|
|
|
29
|
-
Restart the editor once after setup to load the bridge plugin.
|
|
29
|
+
Restart the editor once after setup to load the bridge plugin. To update later: `npx ue-mcp update`
|
|
30
|
+
|
|
31
|
+
Then ask your AI:
|
|
30
32
|
|
|
31
33
|
```
|
|
32
34
|
project(action="get_status") — verify connection
|
package/dist/tools/blueprint.js
CHANGED
|
@@ -34,9 +34,9 @@ export const blueprintTool = categoryTool("blueprint", "Blueprint reading, autho
|
|
|
34
34
|
- create_function: Create function. Params: assetPath, functionName
|
|
35
35
|
- delete_function: Delete function. Params: assetPath, functionName
|
|
36
36
|
- rename_function: Rename function. Params: assetPath, oldName, newName
|
|
37
|
-
- add_node: Add graph node (
|
|
37
|
+
- add_node: Add graph node (any UEdGraphNode subclass). Aliases: CallFunction, Event, CustomEvent, GetVar, SetVar, Branch. nodeParams: functionName+targetClass for CallFunction, eventName+eventClass? for Event, variableName for GetVar/SetVar. Params: assetPath, graphName?, nodeClass, nodeParams?
|
|
38
38
|
- delete_node: Delete node. Params: assetPath, graphName, nodeName
|
|
39
|
-
- set_node_property: Set node property. Params: assetPath, graphName, nodeName, propertyName, value
|
|
39
|
+
- set_node_property: Set node pin default or struct property (supports dot paths like "Node.IKBone.BoneName"). Params: assetPath, graphName, nodeName, propertyName, value
|
|
40
40
|
- connect_pins: Wire nodes. Params: sourceNode, sourcePin, targetNode, targetPin, assetPath, graphName?
|
|
41
41
|
- add_component: Add BP component. Params: assetPath, componentClass, componentName?
|
|
42
42
|
- compile: Compile Blueprint. Params: assetPath
|
package/package.json
CHANGED
|
@@ -41,6 +41,22 @@
|
|
|
41
41
|
#include "Kismet/KismetStringLibrary.h"
|
|
42
42
|
#include "Kismet/KismetArrayLibrary.h"
|
|
43
43
|
|
|
44
|
+
// Helper: find a UClass by short name (e.g. "AnimInstance" finds UAnimInstance)
|
|
45
|
+
static UClass* FindClassByShortName(const FString& ClassName)
|
|
46
|
+
{
|
|
47
|
+
UClass* PrefixedMatch = nullptr;
|
|
48
|
+
for (TObjectIterator<UClass> It; It; ++It)
|
|
49
|
+
{
|
|
50
|
+
const FString& Name = It->GetName();
|
|
51
|
+
if (Name == ClassName) return *It;
|
|
52
|
+
if (!PrefixedMatch && (Name == TEXT("U") + ClassName || Name == TEXT("A") + ClassName))
|
|
53
|
+
{
|
|
54
|
+
PrefixedMatch = *It;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return PrefixedMatch;
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
void FBlueprintHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
45
61
|
{
|
|
46
62
|
Registry.RegisterHandler(TEXT("create_blueprint"), &CreateBlueprint);
|
|
@@ -996,8 +1012,12 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::AddNode(const TSharedPtr<FJsonObject>
|
|
|
996
1012
|
|
|
997
1013
|
// Resolve short aliases to full class names
|
|
998
1014
|
FString ResolvedClass = NodeClass;
|
|
999
|
-
if (NodeClass == TEXT("CallFunction"))
|
|
1000
|
-
else if (NodeClass == TEXT("Event"))
|
|
1015
|
+
if (NodeClass == TEXT("CallFunction")) ResolvedClass = TEXT("K2Node_CallFunction");
|
|
1016
|
+
else if (NodeClass == TEXT("Event")) ResolvedClass = TEXT("K2Node_Event");
|
|
1017
|
+
else if (NodeClass == TEXT("GetVar")) ResolvedClass = TEXT("K2Node_VariableGet");
|
|
1018
|
+
else if (NodeClass == TEXT("SetVar")) ResolvedClass = TEXT("K2Node_VariableSet");
|
|
1019
|
+
else if (NodeClass == TEXT("Branch")) ResolvedClass = TEXT("K2Node_IfThenElse");
|
|
1020
|
+
else if (NodeClass == TEXT("CustomEvent")) ResolvedClass = TEXT("K2Node_CustomEvent");
|
|
1001
1021
|
|
|
1002
1022
|
// Find the UEdGraphNode subclass by name (works for K2, AnimGraph, and any other graph node types)
|
|
1003
1023
|
UClass* NodeUClass = nullptr;
|
|
@@ -1026,34 +1046,34 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::AddNode(const TSharedPtr<FJsonObject>
|
|
|
1026
1046
|
return MakeShared<FJsonValueObject>(Result);
|
|
1027
1047
|
}
|
|
1028
1048
|
|
|
1029
|
-
// Special-case initialization for known types
|
|
1049
|
+
// Special-case initialization for known types (must happen BEFORE AllocateDefaultPins)
|
|
1030
1050
|
if (UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(NewNode))
|
|
1031
1051
|
{
|
|
1032
1052
|
if (NodeParams)
|
|
1033
1053
|
{
|
|
1034
1054
|
FString FunctionName;
|
|
1035
|
-
if ((*NodeParams)->TryGetStringField(TEXT("functionName"), FunctionName))
|
|
1055
|
+
if (!(*NodeParams)->TryGetStringField(TEXT("functionName"), FunctionName))
|
|
1056
|
+
(*NodeParams)->TryGetStringField(TEXT("memberName"), FunctionName);
|
|
1057
|
+
|
|
1058
|
+
if (!FunctionName.IsEmpty())
|
|
1036
1059
|
{
|
|
1037
1060
|
FString TargetClassName;
|
|
1038
|
-
if ((*NodeParams)->TryGetStringField(TEXT("targetClass"), TargetClassName))
|
|
1061
|
+
if (!(*NodeParams)->TryGetStringField(TEXT("targetClass"), TargetClassName))
|
|
1062
|
+
(*NodeParams)->TryGetStringField(TEXT("memberParent"), TargetClassName);
|
|
1063
|
+
|
|
1064
|
+
UClass* TargetClass = nullptr;
|
|
1065
|
+
if (!TargetClassName.IsEmpty())
|
|
1039
1066
|
{
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (TargetClass)
|
|
1046
|
-
{
|
|
1047
|
-
UFunction* Func = TargetClass->FindFunctionByName(FName(*FunctionName));
|
|
1048
|
-
if (Func)
|
|
1049
|
-
{
|
|
1050
|
-
CallNode->SetFromFunction(Func);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1067
|
+
TargetClass = FindClassByShortName(TargetClassName);
|
|
1068
|
+
}
|
|
1069
|
+
if (!TargetClass)
|
|
1070
|
+
{
|
|
1071
|
+
TargetClass = Blueprint->ParentClass;
|
|
1053
1072
|
}
|
|
1054
|
-
|
|
1073
|
+
|
|
1074
|
+
if (TargetClass)
|
|
1055
1075
|
{
|
|
1056
|
-
UFunction* Func =
|
|
1076
|
+
UFunction* Func = TargetClass->FindFunctionByName(FName(*FunctionName));
|
|
1057
1077
|
if (Func)
|
|
1058
1078
|
{
|
|
1059
1079
|
CallNode->SetFromFunction(Func);
|
|
@@ -1067,9 +1087,58 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::AddNode(const TSharedPtr<FJsonObject>
|
|
|
1067
1087
|
if (NodeParams)
|
|
1068
1088
|
{
|
|
1069
1089
|
FString EventName;
|
|
1070
|
-
if ((*NodeParams)->TryGetStringField(TEXT("eventName"), EventName))
|
|
1090
|
+
if (!(*NodeParams)->TryGetStringField(TEXT("eventName"), EventName))
|
|
1091
|
+
(*NodeParams)->TryGetStringField(TEXT("memberName"), EventName);
|
|
1092
|
+
|
|
1093
|
+
if (!EventName.IsEmpty())
|
|
1094
|
+
{
|
|
1095
|
+
FString EventClassName;
|
|
1096
|
+
if (!(*NodeParams)->TryGetStringField(TEXT("eventClass"), EventClassName))
|
|
1097
|
+
(*NodeParams)->TryGetStringField(TEXT("memberParent"), EventClassName);
|
|
1098
|
+
|
|
1099
|
+
if (!EventClassName.IsEmpty())
|
|
1100
|
+
{
|
|
1101
|
+
// Engine event override — bind via EventReference
|
|
1102
|
+
UClass* EventClass = FindClassByShortName(EventClassName);
|
|
1103
|
+
if (!EventClass) EventClass = Blueprint->ParentClass;
|
|
1104
|
+
|
|
1105
|
+
if (EventClass)
|
|
1106
|
+
{
|
|
1107
|
+
UFunction* EventFunc = EventClass->FindFunctionByName(FName(*EventName));
|
|
1108
|
+
if (EventFunc)
|
|
1109
|
+
{
|
|
1110
|
+
bool bIsSelf = Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(EventClass);
|
|
1111
|
+
EventNode->EventReference.SetFromField<UFunction>(EventFunc, bIsSelf);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
else
|
|
1116
|
+
{
|
|
1117
|
+
// Custom event — just set the name
|
|
1118
|
+
EventNode->CustomFunctionName = FName(*EventName);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
else if (UK2Node_VariableGet* GetNode = Cast<UK2Node_VariableGet>(NewNode))
|
|
1124
|
+
{
|
|
1125
|
+
if (NodeParams)
|
|
1126
|
+
{
|
|
1127
|
+
FString VarName;
|
|
1128
|
+
if ((*NodeParams)->TryGetStringField(TEXT("variableName"), VarName))
|
|
1071
1129
|
{
|
|
1072
|
-
|
|
1130
|
+
GetNode->VariableReference.SetSelfMember(FName(*VarName));
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
else if (UK2Node_VariableSet* SetNode = Cast<UK2Node_VariableSet>(NewNode))
|
|
1135
|
+
{
|
|
1136
|
+
if (NodeParams)
|
|
1137
|
+
{
|
|
1138
|
+
FString VarName;
|
|
1139
|
+
if ((*NodeParams)->TryGetStringField(TEXT("variableName"), VarName))
|
|
1140
|
+
{
|
|
1141
|
+
SetNode->VariableReference.SetSelfMember(FName(*VarName));
|
|
1073
1142
|
}
|
|
1074
1143
|
}
|
|
1075
1144
|
}
|
|
@@ -1986,7 +2055,7 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::SetNodeProperty(const TSharedPtr<FJso
|
|
|
1986
2055
|
return MakeShared<FJsonValueObject>(Result);
|
|
1987
2056
|
}
|
|
1988
2057
|
|
|
1989
|
-
//
|
|
2058
|
+
// First try to find a pin with this name
|
|
1990
2059
|
UEdGraphPin* TargetPin = nullptr;
|
|
1991
2060
|
for (UEdGraphPin* Pin : TargetNode->Pins)
|
|
1992
2061
|
{
|
|
@@ -1997,29 +2066,81 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::SetNodeProperty(const TSharedPtr<FJso
|
|
|
1997
2066
|
}
|
|
1998
2067
|
}
|
|
1999
2068
|
|
|
2000
|
-
|
|
2069
|
+
bool bSetViaPin = false;
|
|
2070
|
+
bool bSetViaProperty = false;
|
|
2071
|
+
|
|
2072
|
+
if (TargetPin)
|
|
2001
2073
|
{
|
|
2002
|
-
//
|
|
2003
|
-
|
|
2004
|
-
|
|
2074
|
+
// Set pin default value using the graph's own schema
|
|
2075
|
+
const UEdGraphSchema* Schema = TargetGraph->GetSchema();
|
|
2076
|
+
if (Schema)
|
|
2005
2077
|
{
|
|
2006
|
-
|
|
2078
|
+
Schema->TrySetDefaultValue(*TargetPin, DefaultValue);
|
|
2079
|
+
TargetNode->PinDefaultValueChanged(TargetPin);
|
|
2080
|
+
bSetViaPin = true;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
if (!bSetViaPin)
|
|
2085
|
+
{
|
|
2086
|
+
// No pin found — try setting as a node property via reflection.
|
|
2087
|
+
// Supports dotted paths like "Node.IKBone.BoneName" for AnimGraph inner structs.
|
|
2088
|
+
TArray<FString> PathParts;
|
|
2089
|
+
PinName.ParseIntoArray(PathParts, TEXT("."));
|
|
2090
|
+
|
|
2091
|
+
UStruct* CurrentStruct = TargetNode->GetClass();
|
|
2092
|
+
void* CurrentContainer = TargetNode;
|
|
2093
|
+
FProperty* FinalProp = nullptr;
|
|
2094
|
+
|
|
2095
|
+
for (int32 i = 0; i < PathParts.Num(); i++)
|
|
2096
|
+
{
|
|
2097
|
+
FProperty* Prop = CurrentStruct->FindPropertyByName(FName(*PathParts[i]));
|
|
2098
|
+
if (!Prop) break;
|
|
2099
|
+
|
|
2100
|
+
if (i < PathParts.Num() - 1)
|
|
2007
2101
|
{
|
|
2008
|
-
|
|
2102
|
+
// Intermediate path segment — drill into struct
|
|
2103
|
+
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
|
2104
|
+
if (!StructProp) break;
|
|
2105
|
+
CurrentContainer = StructProp->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
2106
|
+
CurrentStruct = StructProp->Struct;
|
|
2107
|
+
}
|
|
2108
|
+
else
|
|
2109
|
+
{
|
|
2110
|
+
FinalProp = Prop;
|
|
2009
2111
|
}
|
|
2010
2112
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2113
|
+
|
|
2114
|
+
if (FinalProp)
|
|
2115
|
+
{
|
|
2116
|
+
void* ValuePtr = FinalProp->ContainerPtrToValuePtr<void>(CurrentContainer);
|
|
2117
|
+
FinalProp->ImportText_Direct(*DefaultValue, ValuePtr, nullptr, PPF_None);
|
|
2118
|
+
TargetNode->PostEditChange();
|
|
2119
|
+
bSetViaProperty = true;
|
|
2120
|
+
}
|
|
2015
2121
|
}
|
|
2016
2122
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2123
|
+
if (!bSetViaPin && !bSetViaProperty)
|
|
2124
|
+
{
|
|
2125
|
+
// Neither pin nor property found — build a helpful error
|
|
2126
|
+
TArray<FString> PinNames;
|
|
2127
|
+
for (UEdGraphPin* Pin : TargetNode->Pins)
|
|
2128
|
+
{
|
|
2129
|
+
if (Pin) PinNames.Add(Pin->PinName.ToString());
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
TArray<FString> PropNames;
|
|
2133
|
+
for (TFieldIterator<FProperty> It(TargetNode->GetClass()); It; ++It)
|
|
2134
|
+
{
|
|
2135
|
+
PropNames.Add(It->GetName());
|
|
2136
|
+
}
|
|
2020
2137
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2138
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
2139
|
+
TEXT("'%s' not found as pin or property. Pins: [%s]. Properties: [%s]"),
|
|
2140
|
+
*PinName, *FString::Join(PinNames, TEXT(", ")), *FString::Join(PropNames, TEXT(", "))));
|
|
2141
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
2142
|
+
return MakeShared<FJsonValueObject>(Result);
|
|
2143
|
+
}
|
|
2023
2144
|
|
|
2024
2145
|
// Compile and save
|
|
2025
2146
|
FKismetEditorUtilities::CompileBlueprint(Blueprint);
|
|
@@ -2036,8 +2157,9 @@ TSharedPtr<FJsonValue> FBlueprintHandlers::SetNodeProperty(const TSharedPtr<FJso
|
|
|
2036
2157
|
Result->SetStringField(TEXT("path"), AssetPath);
|
|
2037
2158
|
Result->SetStringField(TEXT("graphName"), GraphName);
|
|
2038
2159
|
Result->SetStringField(TEXT("nodeId"), NodeId);
|
|
2039
|
-
Result->SetStringField(TEXT("
|
|
2040
|
-
Result->SetStringField(TEXT("
|
|
2160
|
+
Result->SetStringField(TEXT("propertyName"), PinName);
|
|
2161
|
+
Result->SetStringField(TEXT("value"), DefaultValue);
|
|
2162
|
+
Result->SetStringField(TEXT("setVia"), bSetViaPin ? TEXT("pin") : TEXT("property"));
|
|
2041
2163
|
Result->SetBoolField(TEXT("success"), true);
|
|
2042
2164
|
|
|
2043
2165
|
return MakeShared<FJsonValueObject>(Result);
|