unreal-engine-mcp-server 0.5.1 → 0.5.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 (75) hide show
  1. package/.github/workflows/publish-mcp.yml +1 -4
  2. package/.github/workflows/release-drafter.yml +2 -1
  3. package/CHANGELOG.md +38 -0
  4. package/dist/automation/bridge.d.ts +1 -2
  5. package/dist/automation/bridge.js +24 -23
  6. package/dist/automation/connection-manager.d.ts +1 -0
  7. package/dist/automation/connection-manager.js +10 -0
  8. package/dist/automation/message-handler.js +5 -4
  9. package/dist/automation/request-tracker.d.ts +4 -0
  10. package/dist/automation/request-tracker.js +11 -3
  11. package/dist/tools/actors.d.ts +19 -1
  12. package/dist/tools/actors.js +15 -5
  13. package/dist/tools/assets.js +1 -1
  14. package/dist/tools/blueprint.d.ts +12 -0
  15. package/dist/tools/blueprint.js +43 -14
  16. package/dist/tools/consolidated-tool-definitions.js +2 -1
  17. package/dist/tools/editor.js +3 -2
  18. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  19. package/dist/tools/handlers/actor-handlers.js +14 -8
  20. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  21. package/dist/tools/handlers/sequence-handlers.js +24 -13
  22. package/dist/tools/introspection.d.ts +1 -1
  23. package/dist/tools/introspection.js +1 -1
  24. package/dist/tools/level.js +3 -3
  25. package/dist/tools/lighting.d.ts +54 -7
  26. package/dist/tools/lighting.js +4 -4
  27. package/dist/tools/materials.d.ts +1 -1
  28. package/dist/types/tool-types.d.ts +2 -0
  29. package/dist/unreal-bridge.js +4 -4
  30. package/dist/utils/command-validator.js +6 -5
  31. package/dist/utils/error-handler.d.ts +24 -2
  32. package/dist/utils/error-handler.js +58 -23
  33. package/dist/utils/normalize.d.ts +7 -4
  34. package/dist/utils/normalize.js +12 -10
  35. package/dist/utils/response-validator.js +88 -73
  36. package/dist/utils/unreal-command-queue.d.ts +2 -0
  37. package/dist/utils/unreal-command-queue.js +8 -1
  38. package/docs/handler-mapping.md +4 -2
  39. package/package.json +1 -1
  40. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  41. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  42. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  43. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  44. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  45. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  46. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  47. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  48. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  49. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  50. package/server.json +3 -3
  51. package/src/automation/bridge.ts +27 -25
  52. package/src/automation/connection-manager.ts +18 -0
  53. package/src/automation/message-handler.ts +33 -8
  54. package/src/automation/request-tracker.ts +39 -7
  55. package/src/server/tool-registry.ts +3 -3
  56. package/src/tools/actors.ts +44 -19
  57. package/src/tools/assets.ts +3 -3
  58. package/src/tools/blueprint.ts +115 -49
  59. package/src/tools/consolidated-tool-definitions.ts +2 -1
  60. package/src/tools/editor.ts +4 -3
  61. package/src/tools/handlers/actor-handlers.ts +14 -9
  62. package/src/tools/handlers/sequence-handlers.ts +86 -63
  63. package/src/tools/introspection.ts +7 -7
  64. package/src/tools/level.ts +6 -6
  65. package/src/tools/lighting.ts +19 -19
  66. package/src/tools/materials.ts +1 -1
  67. package/src/tools/sequence.ts +1 -1
  68. package/src/tools/ui.ts +1 -1
  69. package/src/types/tool-types.ts +4 -0
  70. package/src/unreal-bridge.ts +71 -26
  71. package/src/utils/command-validator.ts +46 -5
  72. package/src/utils/error-handler.ts +128 -45
  73. package/src/utils/normalize.ts +38 -16
  74. package/src/utils/response-validator.ts +103 -87
  75. package/src/utils/unreal-command-queue.ts +13 -1
@@ -5606,6 +5606,104 @@ bool UMcpAutomationBridgeSubsystem::HandleSCSAction(
5606
5606
  TEXT("Object property requires valid object path, got: %s"),
5607
5607
  *PropertyValue);
5608
5608
  }
5609
+ } else if (FStructProperty *StructProp =
5610
+ CastField<FStructProperty>(FoundProperty)) {
5611
+ // Handle struct properties (FVector, FVector2D, FLinearColor, etc.)
5612
+ void *PropAddr =
5613
+ StructProp->ContainerPtrToValuePtr<void>(ComponentTemplate);
5614
+ FString StructName =
5615
+ StructProp->Struct ? StructProp->Struct->GetName() : FString();
5616
+
5617
+ // Try to parse JSON object value from payload
5618
+ const TSharedPtr<FJsonObject> *JsonObjValue = nullptr;
5619
+ if (Payload->TryGetObjectField(TEXT("value"), JsonObjValue) &&
5620
+ JsonObjValue->IsValid()) {
5621
+ // Handle FVector explicitly
5622
+ if (StructName.Equals(TEXT("Vector"), ESearchCase::IgnoreCase)) {
5623
+ FVector *Vec = static_cast<FVector *>(PropAddr);
5624
+ double X = 0, Y = 0, Z = 0;
5625
+ (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
5626
+ (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
5627
+ (*JsonObjValue)->TryGetNumberField(TEXT("Z"), Z);
5628
+ // Also try lowercase
5629
+ if (X == 0 && Y == 0 && Z == 0) {
5630
+ (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
5631
+ (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
5632
+ (*JsonObjValue)->TryGetNumberField(TEXT("z"), Z);
5633
+ }
5634
+ *Vec = FVector(X, Y, Z);
5635
+ bSuccess = true;
5636
+ }
5637
+ // Handle FVector2D
5638
+ else if (StructName.Equals(TEXT("Vector2D"), ESearchCase::IgnoreCase)) {
5639
+ FVector2D *Vec = static_cast<FVector2D *>(PropAddr);
5640
+ double X = 0, Y = 0;
5641
+ (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
5642
+ (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
5643
+ if (X == 0 && Y == 0) {
5644
+ (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
5645
+ (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
5646
+ }
5647
+ *Vec = FVector2D(X, Y);
5648
+ bSuccess = true;
5649
+ }
5650
+ // Handle FLinearColor
5651
+ else if (StructName.Equals(TEXT("LinearColor"),
5652
+ ESearchCase::IgnoreCase)) {
5653
+ FLinearColor *Color = static_cast<FLinearColor *>(PropAddr);
5654
+ double R = 0, G = 0, B = 0, A = 1;
5655
+ (*JsonObjValue)->TryGetNumberField(TEXT("R"), R);
5656
+ (*JsonObjValue)->TryGetNumberField(TEXT("G"), G);
5657
+ (*JsonObjValue)->TryGetNumberField(TEXT("B"), B);
5658
+ (*JsonObjValue)->TryGetNumberField(TEXT("A"), A);
5659
+ if (R == 0 && G == 0 && B == 0) {
5660
+ (*JsonObjValue)->TryGetNumberField(TEXT("r"), R);
5661
+ (*JsonObjValue)->TryGetNumberField(TEXT("g"), G);
5662
+ (*JsonObjValue)->TryGetNumberField(TEXT("b"), B);
5663
+ (*JsonObjValue)->TryGetNumberField(TEXT("a"), A);
5664
+ }
5665
+ *Color = FLinearColor(R, G, B, A);
5666
+ bSuccess = true;
5667
+ }
5668
+ // Handle FRotator
5669
+ else if (StructName.Equals(TEXT("Rotator"), ESearchCase::IgnoreCase)) {
5670
+ FRotator *Rot = static_cast<FRotator *>(PropAddr);
5671
+ double Pitch = 0, Yaw = 0, Roll = 0;
5672
+ (*JsonObjValue)->TryGetNumberField(TEXT("Pitch"), Pitch);
5673
+ (*JsonObjValue)->TryGetNumberField(TEXT("Yaw"), Yaw);
5674
+ (*JsonObjValue)->TryGetNumberField(TEXT("Roll"), Roll);
5675
+ if (Pitch == 0 && Yaw == 0 && Roll == 0) {
5676
+ (*JsonObjValue)->TryGetNumberField(TEXT("pitch"), Pitch);
5677
+ (*JsonObjValue)->TryGetNumberField(TEXT("yaw"), Yaw);
5678
+ (*JsonObjValue)->TryGetNumberField(TEXT("roll"), Roll);
5679
+ }
5680
+ *Rot = FRotator(Pitch, Yaw, Roll);
5681
+ bSuccess = true;
5682
+ }
5683
+ }
5684
+
5685
+ // Fallback: try ImportText for string representation
5686
+ if (!bSuccess && !PropertyValue.IsEmpty() && StructProp->Struct) {
5687
+ const TCHAR *Buffer = *PropertyValue;
5688
+ // Use UScriptStruct::ImportText (not FStructProperty)
5689
+ const TCHAR *Result = StructProp->Struct->ImportText(
5690
+ Buffer, PropAddr, nullptr, PPF_None, GWarn, StructName);
5691
+ bSuccess = (Result != nullptr);
5692
+ if (!bSuccess) {
5693
+ ErrorMessage = FString::Printf(
5694
+ TEXT("Failed to parse struct value '%s' for property '%s' of "
5695
+ "type '%s'. For FVector use {\"X\":val,\"Y\":val,\"Z\":val} "
5696
+ "or string \"(X=val,Y=val,Z=val)\""),
5697
+ *PropertyValue, *PropertyName, *StructName);
5698
+ }
5699
+ }
5700
+
5701
+ if (!bSuccess && ErrorMessage.IsEmpty()) {
5702
+ ErrorMessage = FString::Printf(
5703
+ TEXT("Struct property '%s' of type '%s' requires JSON object "
5704
+ "value like {\"X\":val,\"Y\":val,\"Z\":val}"),
5705
+ *PropertyName, *StructName);
5706
+ }
5609
5707
  } else {
5610
5708
  ErrorMessage =
5611
5709
  FString::Printf(TEXT("Property type '%s' not supported for setting"),
@@ -59,6 +59,7 @@ bool UMcpAutomationBridgeSubsystem::HandleEffectAction(
59
59
  Lower.StartsWith(TEXT("create_effect"));
60
60
  if (!bIsCreateEffect && !Lower.StartsWith(TEXT("spawn_")) &&
61
61
  !Lower.Equals(TEXT("set_niagara_parameter")) &&
62
+ !Lower.Equals(TEXT("list_debug_shapes")) &&
62
63
  !Lower.Equals(TEXT("clear_debug_shapes")))
63
64
  return false;
64
65
 
@@ -72,6 +73,29 @@ bool UMcpAutomationBridgeSubsystem::HandleEffectAction(
72
73
  ErrCode);
73
74
  };
74
75
 
76
+ // Discovery: list available debug shape types
77
+ if (Lower.Equals(TEXT("list_debug_shapes"))) {
78
+ TArray<TSharedPtr<FJsonValue>> Shapes;
79
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("sphere")));
80
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("box")));
81
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("circle")));
82
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("line")));
83
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("point")));
84
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("coordinate")));
85
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("cylinder")));
86
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("cone")));
87
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("capsule")));
88
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("arrow")));
89
+ Shapes.Add(MakeShared<FJsonValueString>(TEXT("plane")));
90
+
91
+ TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
92
+ Resp->SetArrayField(TEXT("shapes"), Shapes);
93
+ Resp->SetNumberField(TEXT("count"), Shapes.Num());
94
+ SendAutomationResponse(RequestingSocket, RequestId, true,
95
+ TEXT("Available debug shape types"), Resp);
96
+ return true;
97
+ }
98
+
75
99
  // Handle create_effect tool with sub-actions
76
100
  if (Lower.Equals(TEXT("clear_debug_shapes"))) {
77
101
  #if WITH_EDITOR
@@ -1311,6 +1311,102 @@ bool UMcpAutomationBridgeSubsystem::HandleInspectAction(
1311
1311
  TEXT("Object property requires valid object path, got: %s"),
1312
1312
  *PropertyValue);
1313
1313
  }
1314
+ } else if (FStructProperty *StructProp =
1315
+ CastField<FStructProperty>(FoundProperty)) {
1316
+ // Handle struct properties (FVector, FVector2D, FLinearColor, etc.)
1317
+ void *PropAddr = StructProp->ContainerPtrToValuePtr<void>(TargetObject);
1318
+ FString StructName =
1319
+ StructProp->Struct ? StructProp->Struct->GetName() : FString();
1320
+
1321
+ // Try to parse JSON object value from payload
1322
+ const TSharedPtr<FJsonObject> *JsonObjValue = nullptr;
1323
+ if (Payload->TryGetObjectField(TEXT("value"), JsonObjValue) &&
1324
+ JsonObjValue->IsValid()) {
1325
+ // Handle FVector explicitly
1326
+ if (StructName.Equals(TEXT("Vector"), ESearchCase::IgnoreCase)) {
1327
+ FVector *Vec = static_cast<FVector *>(PropAddr);
1328
+ double X = 0, Y = 0, Z = 0;
1329
+ (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1330
+ (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1331
+ (*JsonObjValue)->TryGetNumberField(TEXT("Z"), Z);
1332
+ if (X == 0 && Y == 0 && Z == 0) {
1333
+ (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1334
+ (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1335
+ (*JsonObjValue)->TryGetNumberField(TEXT("z"), Z);
1336
+ }
1337
+ *Vec = FVector(X, Y, Z);
1338
+ bSuccess = true;
1339
+ }
1340
+ // Handle FVector2D
1341
+ else if (StructName.Equals(TEXT("Vector2D"), ESearchCase::IgnoreCase)) {
1342
+ FVector2D *Vec = static_cast<FVector2D *>(PropAddr);
1343
+ double X = 0, Y = 0;
1344
+ (*JsonObjValue)->TryGetNumberField(TEXT("X"), X);
1345
+ (*JsonObjValue)->TryGetNumberField(TEXT("Y"), Y);
1346
+ if (X == 0 && Y == 0) {
1347
+ (*JsonObjValue)->TryGetNumberField(TEXT("x"), X);
1348
+ (*JsonObjValue)->TryGetNumberField(TEXT("y"), Y);
1349
+ }
1350
+ *Vec = FVector2D(X, Y);
1351
+ bSuccess = true;
1352
+ }
1353
+ // Handle FLinearColor
1354
+ else if (StructName.Equals(TEXT("LinearColor"),
1355
+ ESearchCase::IgnoreCase)) {
1356
+ FLinearColor *Color = static_cast<FLinearColor *>(PropAddr);
1357
+ double R = 0, G = 0, B = 0, A = 1;
1358
+ (*JsonObjValue)->TryGetNumberField(TEXT("R"), R);
1359
+ (*JsonObjValue)->TryGetNumberField(TEXT("G"), G);
1360
+ (*JsonObjValue)->TryGetNumberField(TEXT("B"), B);
1361
+ (*JsonObjValue)->TryGetNumberField(TEXT("A"), A);
1362
+ if (R == 0 && G == 0 && B == 0) {
1363
+ (*JsonObjValue)->TryGetNumberField(TEXT("r"), R);
1364
+ (*JsonObjValue)->TryGetNumberField(TEXT("g"), G);
1365
+ (*JsonObjValue)->TryGetNumberField(TEXT("b"), B);
1366
+ (*JsonObjValue)->TryGetNumberField(TEXT("a"), A);
1367
+ }
1368
+ *Color = FLinearColor(R, G, B, A);
1369
+ bSuccess = true;
1370
+ }
1371
+ // Handle FRotator
1372
+ else if (StructName.Equals(TEXT("Rotator"), ESearchCase::IgnoreCase)) {
1373
+ FRotator *Rot = static_cast<FRotator *>(PropAddr);
1374
+ double Pitch = 0, Yaw = 0, Roll = 0;
1375
+ (*JsonObjValue)->TryGetNumberField(TEXT("Pitch"), Pitch);
1376
+ (*JsonObjValue)->TryGetNumberField(TEXT("Yaw"), Yaw);
1377
+ (*JsonObjValue)->TryGetNumberField(TEXT("Roll"), Roll);
1378
+ if (Pitch == 0 && Yaw == 0 && Roll == 0) {
1379
+ (*JsonObjValue)->TryGetNumberField(TEXT("pitch"), Pitch);
1380
+ (*JsonObjValue)->TryGetNumberField(TEXT("yaw"), Yaw);
1381
+ (*JsonObjValue)->TryGetNumberField(TEXT("roll"), Roll);
1382
+ }
1383
+ *Rot = FRotator(Pitch, Yaw, Roll);
1384
+ bSuccess = true;
1385
+ }
1386
+ }
1387
+
1388
+ // Fallback: try ImportText for string representation
1389
+ if (!bSuccess && !PropertyValue.IsEmpty() && StructProp->Struct) {
1390
+ const TCHAR *Buffer = *PropertyValue;
1391
+ // Use UScriptStruct::ImportText (not FStructProperty)
1392
+ const TCHAR *ImportResult = StructProp->Struct->ImportText(
1393
+ Buffer, PropAddr, nullptr, PPF_None, GWarn, StructName);
1394
+ bSuccess = (ImportResult != nullptr);
1395
+ if (!bSuccess) {
1396
+ ErrorMessage = FString::Printf(
1397
+ TEXT("Failed to parse struct value '%s' for property '%s' of "
1398
+ "type '%s'. For FVector use {\"X\":val,\"Y\":val,\"Z\":val} "
1399
+ "or string \"(X=val,Y=val,Z=val)\""),
1400
+ *PropertyValue, *PropertyName, *StructName);
1401
+ }
1402
+ }
1403
+
1404
+ if (!bSuccess && ErrorMessage.IsEmpty()) {
1405
+ ErrorMessage = FString::Printf(
1406
+ TEXT("Struct property '%s' of type '%s' requires JSON object "
1407
+ "value like {\"X\":val,\"Y\":val,\"Z\":val}"),
1408
+ *PropertyName, *StructName);
1409
+ }
1314
1410
  } else {
1315
1411
  ErrorMessage =
1316
1412
  FString::Printf(TEXT("Property type '%s' not supported for setting"),
@@ -43,6 +43,7 @@ bool UMcpAutomationBridgeSubsystem::HandleLightingAction(
43
43
  !Lower.StartsWith(TEXT("setup_global_illumination")) &&
44
44
  !Lower.StartsWith(TEXT("configure_shadows")) &&
45
45
  !Lower.StartsWith(TEXT("set_exposure")) &&
46
+ !Lower.StartsWith(TEXT("list_light_types")) &&
46
47
  !Lower.StartsWith(TEXT("set_ambient_occlusion"))) {
47
48
  return false;
48
49
  }
@@ -64,6 +65,38 @@ bool UMcpAutomationBridgeSubsystem::HandleLightingAction(
64
65
  return true;
65
66
  }
66
67
 
68
+ if (Lower == TEXT("list_light_types")) {
69
+ TArray<TSharedPtr<FJsonValue>> Types;
70
+ // Add common shortcuts first
71
+ Types.Add(MakeShared<FJsonValueString>(TEXT("DirectionalLight")));
72
+ Types.Add(MakeShared<FJsonValueString>(TEXT("PointLight")));
73
+ Types.Add(MakeShared<FJsonValueString>(TEXT("SpotLight")));
74
+ Types.Add(MakeShared<FJsonValueString>(TEXT("RectLight")));
75
+
76
+ // Discover all ALight subclasses via reflection
77
+ TSet<FString> AddedNames;
78
+ AddedNames.Add(TEXT("DirectionalLight"));
79
+ AddedNames.Add(TEXT("PointLight"));
80
+ AddedNames.Add(TEXT("SpotLight"));
81
+ AddedNames.Add(TEXT("RectLight"));
82
+
83
+ for (TObjectIterator<UClass> It; It; ++It) {
84
+ if (It->IsChildOf(ALight::StaticClass()) &&
85
+ !It->HasAnyClassFlags(CLASS_Abstract) &&
86
+ !AddedNames.Contains(It->GetName())) {
87
+ Types.Add(MakeShared<FJsonValueString>(It->GetName()));
88
+ AddedNames.Add(It->GetName());
89
+ }
90
+ }
91
+
92
+ TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
93
+ Resp->SetArrayField(TEXT("types"), Types);
94
+ Resp->SetNumberField(TEXT("count"), Types.Num());
95
+ SendAutomationResponse(RequestingSocket, RequestId, true,
96
+ TEXT("Available light types"), Resp);
97
+ return true;
98
+ }
99
+
67
100
  if (Lower == TEXT("spawn_light")) {
68
101
  FString LightClassStr;
69
102
  if (!Payload->TryGetStringField(TEXT("lightClass"), LightClassStr) ||
@@ -84,11 +117,25 @@ bool UMcpAutomationBridgeSubsystem::HandleLightingAction(
84
117
  else if (LightClassStr == TEXT("RectLight"))
85
118
  LightClass = ARectLight::StaticClass();
86
119
  else {
87
- SendAutomationError(
88
- RequestingSocket, RequestId,
89
- FString::Printf(TEXT("Unknown light class: %s"), *LightClassStr),
90
- TEXT("INVALID_ARGUMENT"));
91
- return true;
120
+ // Dynamic fallback: Try to resolve any light class by name
121
+ LightClass = ResolveUClass(LightClassStr);
122
+
123
+ // Try with "A" prefix for actor classes (e.g., "SkyLight" -> "ASkyLight")
124
+ if (!LightClass && !LightClassStr.StartsWith(TEXT("A"))) {
125
+ LightClass =
126
+ ResolveUClass(FString::Printf(TEXT("A%s"), *LightClassStr));
127
+ }
128
+
129
+ // Validate the resolved class is actually a light actor
130
+ if (!LightClass || !LightClass->IsChildOf(ALight::StaticClass())) {
131
+ SendAutomationError(
132
+ RequestingSocket, RequestId,
133
+ FString::Printf(
134
+ TEXT("Light class not found or not a light type: %s"),
135
+ *LightClassStr),
136
+ TEXT("INVALID_ARGUMENT"));
137
+ return true;
138
+ }
92
139
  }
93
140
 
94
141
  FVector Location = FVector::ZeroVector;
@@ -242,274 +242,11 @@ void UMcpAutomationBridgeSubsystem::ProcessAutomationRequest(
242
242
  RequestingSocket);
243
243
  }))
244
244
  return;
245
- // Array manipulation operations
246
- if (HandleAndLog(TEXT("HandleArrayAppend"), [&]() {
247
- return HandleArrayAppend(RequestId, Action, Payload,
248
- RequestingSocket);
249
- }))
250
- return;
251
- if (HandleAndLog(TEXT("HandleArrayRemove"), [&]() {
252
- return HandleArrayRemove(RequestId, Action, Payload,
253
- RequestingSocket);
254
- }))
255
- return;
256
- if (HandleAndLog(TEXT("HandleArrayInsert"), [&]() {
257
- return HandleArrayInsert(RequestId, Action, Payload,
258
- RequestingSocket);
259
- }))
260
- return;
261
- if (HandleAndLog(TEXT("HandleArrayGetElement"), [&]() {
262
- return HandleArrayGetElement(RequestId, Action, Payload,
263
- RequestingSocket);
264
- }))
265
- return;
266
- if (HandleAndLog(TEXT("HandleArraySetElement"), [&]() {
267
- return HandleArraySetElement(RequestId, Action, Payload,
268
- RequestingSocket);
269
- }))
270
- return;
271
- if (HandleAndLog(TEXT("HandleArrayClear"), [&]() {
272
- return HandleArrayClear(RequestId, Action, Payload,
273
- RequestingSocket);
274
- }))
275
- return;
276
- // Map manipulation operations
277
- if (HandleAndLog(TEXT("HandleMapSetValue"), [&]() {
278
- return HandleMapSetValue(RequestId, Action, Payload,
279
- RequestingSocket);
280
- }))
281
- return;
282
- if (HandleAndLog(TEXT("HandleMapGetValue"), [&]() {
283
- return HandleMapGetValue(RequestId, Action, Payload,
284
- RequestingSocket);
285
- }))
286
- return;
287
- if (HandleAndLog(TEXT("HandleMapRemoveKey"), [&]() {
288
- return HandleMapRemoveKey(RequestId, Action, Payload,
289
- RequestingSocket);
290
- }))
291
- return;
292
- if (HandleAndLog(TEXT("HandleMapHasKey"), [&]() {
293
- return HandleMapHasKey(RequestId, Action, Payload,
294
- RequestingSocket);
295
- }))
296
- return;
297
- if (HandleAndLog(TEXT("HandleMapGetKeys"), [&]() {
298
- return HandleMapGetKeys(RequestId, Action, Payload,
299
- RequestingSocket);
300
- }))
301
- return;
302
- if (HandleAndLog(TEXT("HandleMapClear"), [&]() {
303
- return HandleMapClear(RequestId, Action, Payload, RequestingSocket);
304
- }))
305
- return;
306
- // Set manipulation operations
307
- if (HandleAndLog(TEXT("HandleSetAdd"), [&]() {
308
- return HandleSetAdd(RequestId, Action, Payload, RequestingSocket);
309
- }))
310
- return;
311
- if (HandleAndLog(TEXT("HandleSetRemove"), [&]() {
312
- return HandleSetRemove(RequestId, Action, Payload,
313
- RequestingSocket);
314
- }))
315
- return;
316
- if (HandleAndLog(TEXT("HandleSetContains"), [&]() {
317
- return HandleSetContains(RequestId, Action, Payload,
318
- RequestingSocket);
319
- }))
320
- return;
321
- if (HandleAndLog(TEXT("HandleSetClear"), [&]() {
322
- return HandleSetClear(RequestId, Action, Payload, RequestingSocket);
323
- }))
324
- return;
325
- // Asset dependency graph traversal
326
- if (HandleAndLog(TEXT("HandleGetAssetReferences"), [&]() {
327
- return HandleGetAssetReferences(RequestId, Action, Payload,
328
- RequestingSocket);
329
- }))
330
- return;
331
- if (HandleAndLog(TEXT("HandleGetAssetDependencies"), [&]() {
332
- return HandleGetAssetDependencies(RequestId, Action, Payload,
333
- RequestingSocket);
334
- }))
335
- return;
336
- // Asset workflow handlers
337
- if (HandleAndLog(TEXT("HandleFixupRedirectors"), [&]() {
338
- return HandleFixupRedirectors(RequestId, Action, Payload,
339
- RequestingSocket);
340
- }))
341
- return;
342
- if (HandleAndLog(TEXT("HandleSourceControlCheckout"), [&]() {
343
- return HandleSourceControlCheckout(RequestId, Action, Payload,
344
- RequestingSocket);
345
- }))
346
- return;
347
- if (HandleAndLog(TEXT("HandleSourceControlSubmit"), [&]() {
348
- return HandleSourceControlSubmit(RequestId, Action, Payload,
349
- RequestingSocket);
350
- }))
351
- return;
352
- if (HandleAndLog(TEXT("HandleBulkRenameAssets"), [&]() {
353
- return HandleBulkRenameAssets(RequestId, Action, Payload,
354
- RequestingSocket);
355
- }))
356
- return;
357
- if (HandleAndLog(TEXT("HandleBulkDeleteAssets"), [&]() {
358
- return HandleBulkDeleteAssets(RequestId, Action, Payload,
359
- RequestingSocket);
360
- }))
361
- return;
362
- if (HandleAndLog(TEXT("HandleGenerateThumbnail"), [&]() {
363
- return HandleGenerateThumbnail(RequestId, Action, Payload,
364
- RequestingSocket);
365
- }))
366
- return;
367
- // Landscape operations
368
- if (HandleAndLog(TEXT("HandleCreateLandscape"), [&]() {
369
- return HandleCreateLandscape(RequestId, Action, Payload,
370
- RequestingSocket);
371
- }))
372
- return;
373
- if (HandleAndLog(TEXT("HandleCreateProceduralTerrain"), [&]() {
374
- return HandleCreateProceduralTerrain(RequestId, Action, Payload,
375
- RequestingSocket);
376
- }))
377
- return;
378
- if (HandleAndLog(TEXT("HandleCreateLandscapeGrassType"), [&]() {
379
- return HandleCreateLandscapeGrassType(RequestId, Action, Payload,
380
- RequestingSocket);
381
- }))
382
- return;
383
- if (HandleAndLog(TEXT("HandleSculptLandscape"), [&]() {
384
- return HandleSculptLandscape(RequestId, Action, Payload,
385
- RequestingSocket);
386
- }))
387
- return;
388
- if (HandleAndLog(TEXT("HandleSetLandscapeMaterial"), [&]() {
389
- return HandleSetLandscapeMaterial(RequestId, Action, Payload,
390
- RequestingSocket);
391
- }))
392
- return;
393
- if (HandleAndLog(TEXT("HandleEditLandscape"), [&]() {
394
- return HandleEditLandscape(RequestId, Action, Payload,
395
- RequestingSocket);
396
- }))
397
- return;
398
- // Foliage operations
399
- if (HandleAndLog(TEXT("HandleAddFoliageType"), [&]() {
400
- return HandleAddFoliageType(RequestId, Action, Payload,
401
- RequestingSocket);
402
- }))
403
- return;
404
- if (HandleAndLog(TEXT("HandleCreateProceduralFoliage"), [&]() {
405
- return HandleCreateProceduralFoliage(RequestId, Action, Payload,
406
- RequestingSocket);
407
- }))
408
- return;
409
- if (HandleAndLog(TEXT("HandlePaintFoliage"), [&]() {
410
- return HandlePaintFoliage(RequestId, Action, Payload,
411
- RequestingSocket);
412
- }))
413
- return;
414
- if (HandleAndLog(TEXT("HandleAddFoliageInstances"), [&]() {
415
- return HandleAddFoliageInstances(RequestId, Action, Payload,
416
- RequestingSocket);
417
- }))
418
- return;
419
- if (HandleAndLog(TEXT("HandleRemoveFoliage"), [&]() {
420
- return HandleRemoveFoliage(RequestId, Action, Payload,
421
- RequestingSocket);
422
- }))
423
- return;
424
- if (HandleAndLog(TEXT("HandleGetFoliageInstances"), [&]() {
425
- return HandleGetFoliageInstances(RequestId, Action, Payload,
426
- RequestingSocket);
427
- }))
428
- return;
429
- // Niagara operations
430
- if (HandleAndLog(TEXT("HandleCreateNiagaraSystem"), [&]() {
431
- return HandleCreateNiagaraSystem(RequestId, Action, Payload,
432
- RequestingSocket);
433
- }))
434
- return;
435
- if (HandleAndLog(TEXT("HandleCreateNiagaraRibbon"), [&]() {
436
- return HandleCreateNiagaraRibbon(RequestId, Action, Payload,
437
- RequestingSocket);
438
- }))
439
- return;
440
- if (HandleAndLog(TEXT("HandleCreateNiagaraEmitter"), [&]() {
441
- return HandleCreateNiagaraEmitter(RequestId, Action, Payload,
442
- RequestingSocket);
443
- }))
444
- return;
445
- if (HandleAndLog(TEXT("HandleSpawnNiagaraActor"), [&]() {
446
- return HandleSpawnNiagaraActor(RequestId, Action, Payload,
447
- RequestingSocket);
448
- }))
449
- return;
450
- if (HandleAndLog(TEXT("HandleModifyNiagaraParameter"), [&]() {
451
- return HandleModifyNiagaraParameter(RequestId, Action, Payload,
452
- RequestingSocket);
453
- }))
454
- return;
455
- // Animation blueprint operations
456
- if (HandleAndLog(TEXT("HandleCreateAnimBlueprint"), [&]() {
457
- return HandleCreateAnimBlueprint(RequestId, Action, Payload,
458
- RequestingSocket);
459
- }))
460
- return;
461
- if (HandleAndLog(TEXT("HandlePlayAnimMontage"), [&]() {
462
- return HandlePlayAnimMontage(RequestId, Action, Payload,
463
- RequestingSocket);
464
- }))
465
- return;
466
- if (HandleAndLog(TEXT("HandleSetupRagdoll"), [&]() {
467
- return HandleSetupRagdoll(RequestId, Action, Payload,
468
- RequestingSocket);
469
- }))
470
- return;
471
- // Material graph operations
472
- if (HandleAndLog(TEXT("HandleAddMaterialTextureSample"), [&]() {
473
- return HandleAddMaterialTextureSample(RequestId, Action, Payload,
474
- RequestingSocket);
475
- }))
476
- return;
477
- if (HandleAndLog(TEXT("HandleAddMaterialExpression"), [&]() {
478
- return HandleAddMaterialExpression(RequestId, Action, Payload,
479
- RequestingSocket);
480
- }))
481
- return;
482
- if (HandleAndLog(TEXT("HandleCreateMaterialNodes"), [&]() {
483
- return HandleCreateMaterialNodes(RequestId, Action, Payload,
484
- RequestingSocket);
485
- }))
486
- return;
487
- // Sequencer operations
488
- if (HandleAndLog(TEXT("HandleAddSequencerKeyframe"), [&]() {
489
- return HandleAddSequencerKeyframe(RequestId, Action, Payload,
490
- RequestingSocket);
491
- }))
492
- return;
493
- if (HandleAndLog(TEXT("HandleManageSequencerTrack"), [&]() {
494
- return HandleManageSequencerTrack(RequestId, Action, Payload,
495
- RequestingSocket);
496
- }))
497
- return;
498
- if (HandleAndLog(TEXT("HandleAddCameraTrack"), [&]() {
499
- return HandleAddCameraTrack(RequestId, Action, Payload,
500
- RequestingSocket);
501
- }))
502
- return;
503
- if (HandleAndLog(TEXT("HandleAddAnimationTrack"), [&]() {
504
- return HandleAddAnimationTrack(RequestId, Action, Payload,
505
- RequestingSocket);
506
- }))
507
- return;
508
- if (HandleAndLog(TEXT("HandleAddTransformTrack"), [&]() {
509
- return HandleAddTransformTrack(RequestId, Action, Payload,
510
- RequestingSocket);
511
- }))
512
- return;
245
+
246
+ // Specialized actions (Array, Map, Set, Landscape, Foliage, Niagara,
247
+ // Animation, Sequencer, etc.) are now handled by the O(1)
248
+ // AutomationHandlers registry check at the top of this function. The
249
+ // linear checks have been removed for performance.
513
250
 
514
251
  // Delegate asset/control/blueprint/sequence actions to their handlers
515
252
  UE_LOG(LogMcpAutomationBridgeSubsystem, Verbose,
@@ -2375,6 +2375,39 @@ bool UMcpAutomationBridgeSubsystem::HandleSequenceAction(
2375
2375
  if (EffectiveAction == TEXT("sequence_remove_track"))
2376
2376
  return HandleSequenceRemoveTrack(RequestId, LocalPayload, RequestingSocket);
2377
2377
 
2378
+ if (EffectiveAction == TEXT("sequence_list_track_types")) {
2379
+ // Discovery: list available track types
2380
+ TArray<TSharedPtr<FJsonValue>> Types;
2381
+ // Add common shortcuts first
2382
+ Types.Add(MakeShared<FJsonValueString>(TEXT("transform")));
2383
+ Types.Add(MakeShared<FJsonValueString>(TEXT("3dtransform")));
2384
+ Types.Add(MakeShared<FJsonValueString>(TEXT("audio")));
2385
+ Types.Add(MakeShared<FJsonValueString>(TEXT("event")));
2386
+
2387
+ // Discover all UMovieSceneTrack subclasses via reflection
2388
+ TSet<FString> AddedNames;
2389
+ AddedNames.Add(TEXT("transform"));
2390
+ AddedNames.Add(TEXT("3dtransform"));
2391
+ AddedNames.Add(TEXT("audio"));
2392
+ AddedNames.Add(TEXT("event"));
2393
+
2394
+ for (TObjectIterator<UClass> It; It; ++It) {
2395
+ if (It->IsChildOf(UMovieSceneTrack::StaticClass()) &&
2396
+ !It->HasAnyClassFlags(CLASS_Abstract) &&
2397
+ !AddedNames.Contains(It->GetName())) {
2398
+ Types.Add(MakeShared<FJsonValueString>(It->GetName()));
2399
+ AddedNames.Add(It->GetName());
2400
+ }
2401
+ }
2402
+
2403
+ TSharedPtr<FJsonObject> Resp = MakeShared<FJsonObject>();
2404
+ Resp->SetArrayField(TEXT("types"), Types);
2405
+ Resp->SetNumberField(TEXT("count"), Types.Num());
2406
+ SendAutomationResponse(RequestingSocket, RequestId, true,
2407
+ TEXT("Available track types"), Resp);
2408
+ return true;
2409
+ }
2410
+
2378
2411
  if (EffectiveAction == TEXT("sequence_add_track")) {
2379
2412
  // add_track action: Add a track to a binding in a level sequence
2380
2413
  FString SeqPath = ResolveSequencePath(LocalPayload);
@@ -2481,8 +2514,30 @@ bool UMcpAutomationBridgeSubsystem::HandleSequenceAction(
2481
2514
  }
2482
2515
  }
2483
2516
 
2484
- // For master tracks or other track types, would need additional
2485
- // implementation based on available track classes
2517
+ // Dynamic fallback: Try to resolve any track class by name
2518
+ if (!NewTrack) {
2519
+ UClass *TrackClass = ResolveUClass(TrackType);
2520
+
2521
+ // Try with common prefixes
2522
+ if (!TrackClass) {
2523
+ TrackClass = ResolveUClass(
2524
+ FString::Printf(TEXT("UMovieScene%sTrack"), *TrackType));
2525
+ }
2526
+ if (!TrackClass) {
2527
+ TrackClass = ResolveUClass(
2528
+ FString::Printf(TEXT("MovieScene%sTrack"), *TrackType));
2529
+ }
2530
+
2531
+ // Validate it's actually a track class
2532
+ if (TrackClass &&
2533
+ TrackClass->IsChildOf(UMovieSceneTrack::StaticClass())) {
2534
+ if (BindingGuid.IsValid()) {
2535
+ NewTrack = MovieScene->AddTrack(TrackClass, BindingGuid);
2536
+ } else {
2537
+ NewTrack = MovieScene->AddTrack(TrackClass);
2538
+ }
2539
+ }
2540
+ }
2486
2541
 
2487
2542
  if (NewTrack) {
2488
2543
  Sequence->MarkPackageDirty();