ue-mcp 1.2.1 → 1.2.2

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.
Files changed (54) hide show
  1. package/dist/context-cli.js +16 -16
  2. package/dist/engine-observer.js +68 -68
  3. package/dist/flow/guard.d.ts +26 -37
  4. package/dist/flow/guard.js +40 -25
  5. package/dist/flow/guard.js.map +1 -1
  6. package/dist/flow/guarded-bridge.d.ts +0 -10
  7. package/dist/flow/guarded-bridge.js +18 -23
  8. package/dist/flow/guarded-bridge.js.map +1 -1
  9. package/dist/flow/schema.d.ts +276 -16
  10. package/dist/flow/task-guards.d.ts +1 -1
  11. package/dist/flow/task-guards.js +44 -68
  12. package/dist/flow/task-guards.js.map +1 -1
  13. package/dist/instructions.js +130 -130
  14. package/dist/plugin-cli.js +299 -299
  15. package/dist/tool-counts.json +2 -2
  16. package/package.json +2 -2
  17. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
  18. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
  19. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
  20. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
  21. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
  22. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
  23. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
  24. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
  25. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
  26. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
  27. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
  28. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
  29. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
  30. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
  31. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
  32. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
  33. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
  34. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
  35. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
  36. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
  37. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
  38. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
  39. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
  40. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
  41. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
  42. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
  43. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
  44. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
  45. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
  46. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
  47. package/dist/manifest-signature.d.ts +0 -1
  48. package/dist/manifest-signature.js +0 -53
  49. package/dist/manifest-signature.js.map +0 -1
  50. package/dist/pie/schema.d.ts +0 -791
  51. package/dist/pie/schema.js +0 -151
  52. package/dist/pie/schema.js.map +0 -1
  53. package/dist/watch-cli.js.map +0 -1
  54. /package/plugin/ue_mcp_bridge/{ue_mcp_bridge.uplugin → UE_MCP_Bridge.uplugin} +0 -0
@@ -1,275 +1,275 @@
1
- // #463: MaterialFunction creation + per-function expression authoring.
2
- // The material tool surface previously only covered UMaterial asset graphs.
3
- // MaterialFunction assets had no native create path or per-function
4
- // expression APIs, forcing execute_python for what is a normal authoring
5
- // workflow (color packs, reusable shading functions, math helpers).
6
- //
7
- // Split into its own TU so the existing MaterialHandlers.cpp doesn't grow.
8
- // All functions are still members of FMaterialHandlers - registration
9
- // happens in MaterialHandlers.cpp::RegisterHandlers.
10
-
11
- #include "MaterialHandlers.h"
12
- #include "HandlerRegistry.h"
13
- #include "HandlerUtils.h"
14
- #include "HandlerAssetCreate.h"
15
- #include "AssetToolsModule.h"
16
- #include "IAssetTools.h"
17
- #include "Factories/MaterialFunctionFactoryNew.h"
18
- #include "Materials/MaterialFunction.h"
19
- #include "Materials/MaterialExpression.h"
20
- #include "Materials/MaterialExpressionFunctionInput.h"
21
- #include "Materials/MaterialExpressionFunctionOutput.h"
22
- #include "MaterialEditingLibrary.h"
23
- #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
24
- #include "UObject/UObjectIterator.h"
25
-
26
- namespace
27
- {
28
- UMaterialFunction* LoadMaterialFunction(const FString& Path)
29
- {
30
- UMaterialFunction* MF = LoadObject<UMaterialFunction>(nullptr, *Path);
31
- if (!MF)
32
- {
33
- MF = Cast<UMaterialFunction>(UEditorAssetLibrary::LoadAsset(Path));
34
- }
35
- return MF;
36
- }
37
-
38
- UClass* ResolveExpressionClass(const FString& InType)
39
- {
40
- FString ClassName = InType;
41
- if (!ClassName.StartsWith(TEXT("MaterialExpression")) && !ClassName.StartsWith(TEXT("UMaterialExpression")))
42
- {
43
- ClassName = TEXT("UMaterialExpression") + ClassName;
44
- }
45
- else if (!ClassName.StartsWith(TEXT("U")))
46
- {
47
- ClassName = TEXT("U") + ClassName;
48
- }
49
- UClass* Result = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass);
50
- if (!Result)
51
- {
52
- Result = FindFirstObject<UClass>(*InType, EFindFirstObjectOptions::ExactClass);
53
- }
54
- if (!Result || !Result->IsChildOf(UMaterialExpression::StaticClass())) return nullptr;
55
- return Result;
56
- }
57
- }
58
-
59
- // material(action="create_function", name, packagePath?, description?, onConflict?)
60
- TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialFunction(const TSharedPtr<FJsonObject>& Params)
61
- {
62
- FString Name;
63
- if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
64
- FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials/Functions"));
65
- const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
66
-
67
- UMaterialFunctionFactoryNew* Factory = NewObject<UMaterialFunctionFactoryNew>();
68
- auto Created = MCPCreateAssetIdempotent<UMaterialFunction>(Name, PackagePath, OnConflict, TEXT("MaterialFunction"), Factory);
69
- if (Created.EarlyReturn) return Created.EarlyReturn;
70
-
71
- UMaterialFunction* MF = Created.Asset;
72
- FString Description;
73
- if (Params->TryGetStringField(TEXT("description"), Description))
74
- {
75
- MF->Description = Description;
76
- }
77
-
78
- UEditorAssetLibrary::SaveAsset(MF->GetPathName());
79
-
80
- auto Result = MCPSuccess();
81
- MCPSetCreated(Result);
82
- Result->SetStringField(TEXT("path"), MF->GetPathName());
83
- Result->SetStringField(TEXT("name"), Name);
84
- Result->SetStringField(TEXT("packagePath"), PackagePath);
85
- MCPSetDeleteAssetRollback(Result, MF->GetPathName());
86
- return MCPResult(Result);
87
- }
88
-
89
- // material(action="add_expression_in_function", functionPath, expressionType,
90
- // positionX?, positionY?, inputName?, outputName?)
91
- TSharedPtr<FJsonValue> FMaterialHandlers::AddMaterialFunctionExpression(const TSharedPtr<FJsonObject>& Params)
92
- {
93
- FString FunctionPath;
94
- if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
95
-
96
- FString ExpressionType;
97
- if (auto Err = RequireString(Params, TEXT("expressionType"), ExpressionType)) return Err;
98
-
99
- UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
100
- if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
101
-
102
- UClass* ExprClass = ResolveExpressionClass(ExpressionType);
103
- if (!ExprClass) return MCPError(FString::Printf(TEXT("Unknown expression type: %s"), *ExpressionType));
104
-
105
- int32 PosX = (int32)OptionalNumber(Params, TEXT("positionX"), 0.0);
106
- int32 PosY = (int32)OptionalNumber(Params, TEXT("positionY"), 0.0);
107
-
108
- UMaterialExpression* NewExpr = UMaterialEditingLibrary::CreateMaterialExpressionInFunction(MF, ExprClass, PosX, PosY);
109
- if (!NewExpr) return MCPError(TEXT("CreateMaterialExpressionInFunction returned null"));
110
-
111
- // Input/Output expressions: name them so callers can reference them by name.
112
- if (UMaterialExpressionFunctionInput* AsInput = Cast<UMaterialExpressionFunctionInput>(NewExpr))
113
- {
114
- FString InputName;
115
- if (Params->TryGetStringField(TEXT("inputName"), InputName) || Params->TryGetStringField(TEXT("name"), InputName))
116
- {
117
- AsInput->InputName = FName(*InputName);
118
- }
119
- FString InputTypeStr;
120
- if (Params->TryGetStringField(TEXT("inputType"), InputTypeStr))
121
- {
122
- static const TMap<FString, EFunctionInputType> Map = {
123
- {TEXT("Scalar"), FunctionInput_Scalar},
124
- {TEXT("Vector2"), FunctionInput_Vector2},
125
- {TEXT("Vector3"), FunctionInput_Vector3},
126
- {TEXT("Vector4"), FunctionInput_Vector4},
127
- {TEXT("Texture2D"), FunctionInput_Texture2D},
128
- {TEXT("TextureCube"), FunctionInput_TextureCube},
129
- {TEXT("StaticBool"), FunctionInput_StaticBool},
130
- {TEXT("MaterialAttributes"), FunctionInput_MaterialAttributes},
131
- };
132
- if (const EFunctionInputType* Found = Map.Find(InputTypeStr))
133
- {
134
- AsInput->InputType = *Found;
135
- }
136
- }
137
- }
138
- if (UMaterialExpressionFunctionOutput* AsOutput = Cast<UMaterialExpressionFunctionOutput>(NewExpr))
139
- {
140
- FString OutputName;
141
- if (Params->TryGetStringField(TEXT("outputName"), OutputName) || Params->TryGetStringField(TEXT("name"), OutputName))
142
- {
143
- AsOutput->OutputName = FName(*OutputName);
144
- }
145
- }
146
-
147
- UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
148
- UEditorAssetLibrary::SaveAsset(MF->GetPathName());
149
-
150
- int32 Index = MF->GetExpressions().IndexOfByKey(NewExpr);
151
-
152
- auto Result = MCPSuccess();
153
- MCPSetCreated(Result);
154
- Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
155
- Result->SetStringField(TEXT("expressionClass"), NewExpr->GetClass()->GetName());
156
- Result->SetNumberField(TEXT("expressionIndex"), Index);
157
- Result->SetStringField(TEXT("nodeId"), FString::FromInt(Index));
158
- return MCPResult(Result);
159
- }
160
-
161
- // material(action="connect_expressions_in_function", functionPath,
162
- // sourceExpression (name or index), sourceOutput?,
163
- // targetExpression (name or index), targetInput?)
164
- TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
165
- {
166
- FString FunctionPath;
167
- if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
168
-
169
- UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
170
- if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
171
-
172
- auto ResolveExpr = [&](const TCHAR* Key) -> UMaterialExpression*
173
- {
174
- // Numeric index?
175
- int32 Idx = -1;
176
- if (Params->TryGetNumberField(Key, Idx))
177
- {
178
- if (Idx >= 0 && Idx < MF->GetExpressions().Num()) return MF->GetExpressions()[Idx];
179
- return nullptr;
180
- }
181
- FString Str;
182
- if (Params->TryGetStringField(Key, Str))
183
- {
184
- // FunctionInput/Output exposes InputName/OutputName; everything else uses Desc.
185
- for (UMaterialExpression* Expr : MF->GetExpressions())
186
- {
187
- if (!Expr) continue;
188
- if (Expr->Desc == Str) return Expr;
189
- if (Expr->GetName() == Str) return Expr;
190
- if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
191
- {
192
- if (In->InputName.ToString() == Str) return Expr;
193
- }
194
- if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
195
- {
196
- if (Out->OutputName.ToString() == Str) return Expr;
197
- }
198
- }
199
- // Numeric in string form
200
- int32 ParsedIdx = FCString::Atoi(*Str);
201
- if (ParsedIdx >= 0 && ParsedIdx < MF->GetExpressions().Num() && Str.IsNumeric())
202
- {
203
- return MF->GetExpressions()[ParsedIdx];
204
- }
205
- }
206
- return nullptr;
207
- };
208
-
209
- UMaterialExpression* From = ResolveExpr(TEXT("sourceExpression"));
210
- UMaterialExpression* To = ResolveExpr(TEXT("targetExpression"));
211
- if (!From) return MCPError(TEXT("sourceExpression not found in function"));
212
- if (!To) return MCPError(TEXT("targetExpression not found in function"));
213
-
214
- FString SourceOutput = OptionalString(Params, TEXT("sourceOutput"));
215
- FString TargetInput = OptionalString(Params, TEXT("targetInput"));
216
-
217
- const bool bOk = UMaterialEditingLibrary::ConnectMaterialExpressions(From, SourceOutput, To, TargetInput);
218
- if (!bOk)
219
- {
220
- return MCPError(FString::Printf(TEXT("ConnectMaterialExpressions failed: '%s' -> '%s' (output='%s' input='%s')"),
221
- *From->GetName(), *To->GetName(), *SourceOutput, *TargetInput));
222
- }
223
-
224
- UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
225
- UEditorAssetLibrary::SaveAsset(MF->GetPathName());
226
-
227
- auto Result = MCPSuccess();
228
- MCPSetCreated(Result);
229
- Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
230
- Result->SetStringField(TEXT("sourceExpression"), From->GetName());
231
- Result->SetStringField(TEXT("targetExpression"), To->GetName());
232
- Result->SetStringField(TEXT("sourceOutput"), SourceOutput);
233
- Result->SetStringField(TEXT("targetInput"), TargetInput);
234
- return MCPResult(Result);
235
- }
236
-
237
- // material(action="list_expressions_in_function", functionPath)
238
- TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
239
- {
240
- FString FunctionPath;
241
- if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
242
-
243
- UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
244
- if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
245
-
246
- TArray<TSharedPtr<FJsonValue>> Arr;
247
- int32 Index = 0;
248
- for (UMaterialExpression* Expr : MF->GetExpressions())
249
- {
250
- if (!Expr) { ++Index; continue; }
251
- TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
252
- Obj->SetNumberField(TEXT("index"), Index);
253
- Obj->SetStringField(TEXT("class"), Expr->GetClass()->GetName());
254
- Obj->SetStringField(TEXT("name"), Expr->GetName());
255
- Obj->SetStringField(TEXT("description"), Expr->Desc);
256
- Obj->SetNumberField(TEXT("positionX"), Expr->MaterialExpressionEditorX);
257
- Obj->SetNumberField(TEXT("positionY"), Expr->MaterialExpressionEditorY);
258
- if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
259
- {
260
- Obj->SetStringField(TEXT("inputName"), In->InputName.ToString());
261
- }
262
- if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
263
- {
264
- Obj->SetStringField(TEXT("outputName"), Out->OutputName.ToString());
265
- }
266
- Arr.Add(MakeShared<FJsonValueObject>(Obj));
267
- ++Index;
268
- }
269
-
270
- auto Result = MCPSuccess();
271
- Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
272
- Result->SetArrayField(TEXT("expressions"), Arr);
273
- Result->SetNumberField(TEXT("count"), Arr.Num());
274
- return MCPResult(Result);
275
- }
1
+ // #463: MaterialFunction creation + per-function expression authoring.
2
+ // The material tool surface previously only covered UMaterial asset graphs.
3
+ // MaterialFunction assets had no native create path or per-function
4
+ // expression APIs, forcing execute_python for what is a normal authoring
5
+ // workflow (color packs, reusable shading functions, math helpers).
6
+ //
7
+ // Split into its own TU so the existing MaterialHandlers.cpp doesn't grow.
8
+ // All functions are still members of FMaterialHandlers - registration
9
+ // happens in MaterialHandlers.cpp::RegisterHandlers.
10
+
11
+ #include "MaterialHandlers.h"
12
+ #include "HandlerRegistry.h"
13
+ #include "HandlerUtils.h"
14
+ #include "HandlerAssetCreate.h"
15
+ #include "AssetToolsModule.h"
16
+ #include "IAssetTools.h"
17
+ #include "Factories/MaterialFunctionFactoryNew.h"
18
+ #include "Materials/MaterialFunction.h"
19
+ #include "Materials/MaterialExpression.h"
20
+ #include "Materials/MaterialExpressionFunctionInput.h"
21
+ #include "Materials/MaterialExpressionFunctionOutput.h"
22
+ #include "MaterialEditingLibrary.h"
23
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
24
+ #include "UObject/UObjectIterator.h"
25
+
26
+ namespace
27
+ {
28
+ UMaterialFunction* LoadMaterialFunction(const FString& Path)
29
+ {
30
+ UMaterialFunction* MF = LoadObject<UMaterialFunction>(nullptr, *Path);
31
+ if (!MF)
32
+ {
33
+ MF = Cast<UMaterialFunction>(UEditorAssetLibrary::LoadAsset(Path));
34
+ }
35
+ return MF;
36
+ }
37
+
38
+ UClass* ResolveExpressionClass(const FString& InType)
39
+ {
40
+ FString ClassName = InType;
41
+ if (!ClassName.StartsWith(TEXT("MaterialExpression")) && !ClassName.StartsWith(TEXT("UMaterialExpression")))
42
+ {
43
+ ClassName = TEXT("UMaterialExpression") + ClassName;
44
+ }
45
+ else if (!ClassName.StartsWith(TEXT("U")))
46
+ {
47
+ ClassName = TEXT("U") + ClassName;
48
+ }
49
+ UClass* Result = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass);
50
+ if (!Result)
51
+ {
52
+ Result = FindFirstObject<UClass>(*InType, EFindFirstObjectOptions::ExactClass);
53
+ }
54
+ if (!Result || !Result->IsChildOf(UMaterialExpression::StaticClass())) return nullptr;
55
+ return Result;
56
+ }
57
+ }
58
+
59
+ // material(action="create_function", name, packagePath?, description?, onConflict?)
60
+ TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialFunction(const TSharedPtr<FJsonObject>& Params)
61
+ {
62
+ FString Name;
63
+ if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
64
+ FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials/Functions"));
65
+ const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
66
+
67
+ UMaterialFunctionFactoryNew* Factory = NewObject<UMaterialFunctionFactoryNew>();
68
+ auto Created = MCPCreateAssetIdempotent<UMaterialFunction>(Name, PackagePath, OnConflict, TEXT("MaterialFunction"), Factory);
69
+ if (Created.EarlyReturn) return Created.EarlyReturn;
70
+
71
+ UMaterialFunction* MF = Created.Asset;
72
+ FString Description;
73
+ if (Params->TryGetStringField(TEXT("description"), Description))
74
+ {
75
+ MF->Description = Description;
76
+ }
77
+
78
+ UEditorAssetLibrary::SaveAsset(MF->GetPathName());
79
+
80
+ auto Result = MCPSuccess();
81
+ MCPSetCreated(Result);
82
+ Result->SetStringField(TEXT("path"), MF->GetPathName());
83
+ Result->SetStringField(TEXT("name"), Name);
84
+ Result->SetStringField(TEXT("packagePath"), PackagePath);
85
+ MCPSetDeleteAssetRollback(Result, MF->GetPathName());
86
+ return MCPResult(Result);
87
+ }
88
+
89
+ // material(action="add_expression_in_function", functionPath, expressionType,
90
+ // positionX?, positionY?, inputName?, outputName?)
91
+ TSharedPtr<FJsonValue> FMaterialHandlers::AddMaterialFunctionExpression(const TSharedPtr<FJsonObject>& Params)
92
+ {
93
+ FString FunctionPath;
94
+ if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
95
+
96
+ FString ExpressionType;
97
+ if (auto Err = RequireString(Params, TEXT("expressionType"), ExpressionType)) return Err;
98
+
99
+ UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
100
+ if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
101
+
102
+ UClass* ExprClass = ResolveExpressionClass(ExpressionType);
103
+ if (!ExprClass) return MCPError(FString::Printf(TEXT("Unknown expression type: %s"), *ExpressionType));
104
+
105
+ int32 PosX = (int32)OptionalNumber(Params, TEXT("positionX"), 0.0);
106
+ int32 PosY = (int32)OptionalNumber(Params, TEXT("positionY"), 0.0);
107
+
108
+ UMaterialExpression* NewExpr = UMaterialEditingLibrary::CreateMaterialExpressionInFunction(MF, ExprClass, PosX, PosY);
109
+ if (!NewExpr) return MCPError(TEXT("CreateMaterialExpressionInFunction returned null"));
110
+
111
+ // Input/Output expressions: name them so callers can reference them by name.
112
+ if (UMaterialExpressionFunctionInput* AsInput = Cast<UMaterialExpressionFunctionInput>(NewExpr))
113
+ {
114
+ FString InputName;
115
+ if (Params->TryGetStringField(TEXT("inputName"), InputName) || Params->TryGetStringField(TEXT("name"), InputName))
116
+ {
117
+ AsInput->InputName = FName(*InputName);
118
+ }
119
+ FString InputTypeStr;
120
+ if (Params->TryGetStringField(TEXT("inputType"), InputTypeStr))
121
+ {
122
+ static const TMap<FString, EFunctionInputType> Map = {
123
+ {TEXT("Scalar"), FunctionInput_Scalar},
124
+ {TEXT("Vector2"), FunctionInput_Vector2},
125
+ {TEXT("Vector3"), FunctionInput_Vector3},
126
+ {TEXT("Vector4"), FunctionInput_Vector4},
127
+ {TEXT("Texture2D"), FunctionInput_Texture2D},
128
+ {TEXT("TextureCube"), FunctionInput_TextureCube},
129
+ {TEXT("StaticBool"), FunctionInput_StaticBool},
130
+ {TEXT("MaterialAttributes"), FunctionInput_MaterialAttributes},
131
+ };
132
+ if (const EFunctionInputType* Found = Map.Find(InputTypeStr))
133
+ {
134
+ AsInput->InputType = *Found;
135
+ }
136
+ }
137
+ }
138
+ if (UMaterialExpressionFunctionOutput* AsOutput = Cast<UMaterialExpressionFunctionOutput>(NewExpr))
139
+ {
140
+ FString OutputName;
141
+ if (Params->TryGetStringField(TEXT("outputName"), OutputName) || Params->TryGetStringField(TEXT("name"), OutputName))
142
+ {
143
+ AsOutput->OutputName = FName(*OutputName);
144
+ }
145
+ }
146
+
147
+ UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
148
+ UEditorAssetLibrary::SaveAsset(MF->GetPathName());
149
+
150
+ int32 Index = MF->GetExpressions().IndexOfByKey(NewExpr);
151
+
152
+ auto Result = MCPSuccess();
153
+ MCPSetCreated(Result);
154
+ Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
155
+ Result->SetStringField(TEXT("expressionClass"), NewExpr->GetClass()->GetName());
156
+ Result->SetNumberField(TEXT("expressionIndex"), Index);
157
+ Result->SetStringField(TEXT("nodeId"), FString::FromInt(Index));
158
+ return MCPResult(Result);
159
+ }
160
+
161
+ // material(action="connect_expressions_in_function", functionPath,
162
+ // sourceExpression (name or index), sourceOutput?,
163
+ // targetExpression (name or index), targetInput?)
164
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
165
+ {
166
+ FString FunctionPath;
167
+ if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
168
+
169
+ UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
170
+ if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
171
+
172
+ auto ResolveExpr = [&](const TCHAR* Key) -> UMaterialExpression*
173
+ {
174
+ // Numeric index?
175
+ int32 Idx = -1;
176
+ if (Params->TryGetNumberField(Key, Idx))
177
+ {
178
+ if (Idx >= 0 && Idx < MF->GetExpressions().Num()) return MF->GetExpressions()[Idx];
179
+ return nullptr;
180
+ }
181
+ FString Str;
182
+ if (Params->TryGetStringField(Key, Str))
183
+ {
184
+ // FunctionInput/Output exposes InputName/OutputName; everything else uses Desc.
185
+ for (UMaterialExpression* Expr : MF->GetExpressions())
186
+ {
187
+ if (!Expr) continue;
188
+ if (Expr->Desc == Str) return Expr;
189
+ if (Expr->GetName() == Str) return Expr;
190
+ if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
191
+ {
192
+ if (In->InputName.ToString() == Str) return Expr;
193
+ }
194
+ if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
195
+ {
196
+ if (Out->OutputName.ToString() == Str) return Expr;
197
+ }
198
+ }
199
+ // Numeric in string form
200
+ int32 ParsedIdx = FCString::Atoi(*Str);
201
+ if (ParsedIdx >= 0 && ParsedIdx < MF->GetExpressions().Num() && Str.IsNumeric())
202
+ {
203
+ return MF->GetExpressions()[ParsedIdx];
204
+ }
205
+ }
206
+ return nullptr;
207
+ };
208
+
209
+ UMaterialExpression* From = ResolveExpr(TEXT("sourceExpression"));
210
+ UMaterialExpression* To = ResolveExpr(TEXT("targetExpression"));
211
+ if (!From) return MCPError(TEXT("sourceExpression not found in function"));
212
+ if (!To) return MCPError(TEXT("targetExpression not found in function"));
213
+
214
+ FString SourceOutput = OptionalString(Params, TEXT("sourceOutput"));
215
+ FString TargetInput = OptionalString(Params, TEXT("targetInput"));
216
+
217
+ const bool bOk = UMaterialEditingLibrary::ConnectMaterialExpressions(From, SourceOutput, To, TargetInput);
218
+ if (!bOk)
219
+ {
220
+ return MCPError(FString::Printf(TEXT("ConnectMaterialExpressions failed: '%s' -> '%s' (output='%s' input='%s')"),
221
+ *From->GetName(), *To->GetName(), *SourceOutput, *TargetInput));
222
+ }
223
+
224
+ UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
225
+ UEditorAssetLibrary::SaveAsset(MF->GetPathName());
226
+
227
+ auto Result = MCPSuccess();
228
+ MCPSetCreated(Result);
229
+ Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
230
+ Result->SetStringField(TEXT("sourceExpression"), From->GetName());
231
+ Result->SetStringField(TEXT("targetExpression"), To->GetName());
232
+ Result->SetStringField(TEXT("sourceOutput"), SourceOutput);
233
+ Result->SetStringField(TEXT("targetInput"), TargetInput);
234
+ return MCPResult(Result);
235
+ }
236
+
237
+ // material(action="list_expressions_in_function", functionPath)
238
+ TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
239
+ {
240
+ FString FunctionPath;
241
+ if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
242
+
243
+ UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
244
+ if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
245
+
246
+ TArray<TSharedPtr<FJsonValue>> Arr;
247
+ int32 Index = 0;
248
+ for (UMaterialExpression* Expr : MF->GetExpressions())
249
+ {
250
+ if (!Expr) { ++Index; continue; }
251
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
252
+ Obj->SetNumberField(TEXT("index"), Index);
253
+ Obj->SetStringField(TEXT("class"), Expr->GetClass()->GetName());
254
+ Obj->SetStringField(TEXT("name"), Expr->GetName());
255
+ Obj->SetStringField(TEXT("description"), Expr->Desc);
256
+ Obj->SetNumberField(TEXT("positionX"), Expr->MaterialExpressionEditorX);
257
+ Obj->SetNumberField(TEXT("positionY"), Expr->MaterialExpressionEditorY);
258
+ if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
259
+ {
260
+ Obj->SetStringField(TEXT("inputName"), In->InputName.ToString());
261
+ }
262
+ if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
263
+ {
264
+ Obj->SetStringField(TEXT("outputName"), Out->OutputName.ToString());
265
+ }
266
+ Arr.Add(MakeShared<FJsonValueObject>(Obj));
267
+ ++Index;
268
+ }
269
+
270
+ auto Result = MCPSuccess();
271
+ Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
272
+ Result->SetArrayField(TEXT("expressions"), Arr);
273
+ Result->SetNumberField(TEXT("count"), Arr.Num());
274
+ return MCPResult(Result);
275
+ }