ultimate-unreal-engine-mcp 0.1.0

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 (124) hide show
  1. package/README.md +729 -0
  2. package/dist/build/error-parser.js +51 -0
  3. package/dist/build/fix-suggester.js +84 -0
  4. package/dist/build/ubt-runner.js +146 -0
  5. package/dist/cli.js +13 -0
  6. package/dist/config.js +8 -0
  7. package/dist/docs/data/ue57-api.js +228 -0
  8. package/dist/docs/doc-index.js +110 -0
  9. package/dist/docs/types.js +4 -0
  10. package/dist/generators/class-generator.js +363 -0
  11. package/dist/generators/file-modifier.js +276 -0
  12. package/dist/generators/uht-validator.js +177 -0
  13. package/dist/index.js +89 -0
  14. package/dist/parsers/cpp-class-index.js +230 -0
  15. package/dist/parsers/cpp-parser.js +369 -0
  16. package/dist/parsers/ini-parser.js +216 -0
  17. package/dist/parsers/uproject-parser.js +130 -0
  18. package/dist/plugin-bridge/client.js +217 -0
  19. package/dist/plugin-bridge/protocol.js +6 -0
  20. package/dist/plugin-bridge/retry.js +23 -0
  21. package/dist/setup.js +209 -0
  22. package/dist/tools/ai-systems/index.js +247 -0
  23. package/dist/tools/ai-systems/types.js +4 -0
  24. package/dist/tools/animation/index.js +241 -0
  25. package/dist/tools/animation/types.js +4 -0
  26. package/dist/tools/audio/index.js +204 -0
  27. package/dist/tools/audio/types.js +4 -0
  28. package/dist/tools/blueprint/index.js +495 -0
  29. package/dist/tools/blueprint/types.js +4 -0
  30. package/dist/tools/build/index.js +163 -0
  31. package/dist/tools/chaos/index.js +230 -0
  32. package/dist/tools/chaos/types.js +4 -0
  33. package/dist/tools/collision-physics/index.js +211 -0
  34. package/dist/tools/config/index.js +288 -0
  35. package/dist/tools/cpp/index.js +305 -0
  36. package/dist/tools/docs/index.js +251 -0
  37. package/dist/tools/editor/index.js +242 -0
  38. package/dist/tools/gas/index.js +222 -0
  39. package/dist/tools/gas/types.js +5 -0
  40. package/dist/tools/import-export/index.js +218 -0
  41. package/dist/tools/input/index.js +146 -0
  42. package/dist/tools/known-issues/index.js +88 -0
  43. package/dist/tools/known-issues/middleware.js +55 -0
  44. package/dist/tools/known-issues/store.js +125 -0
  45. package/dist/tools/livelink/index.js +203 -0
  46. package/dist/tools/livelink/types.js +4 -0
  47. package/dist/tools/material/index.js +190 -0
  48. package/dist/tools/motion-design/index.js +251 -0
  49. package/dist/tools/motion-design/types.js +6 -0
  50. package/dist/tools/movie-render/index.js +220 -0
  51. package/dist/tools/networking/index.js +149 -0
  52. package/dist/tools/pcg/index.js +164 -0
  53. package/dist/tools/selection/index.js +180 -0
  54. package/dist/tools/sequencer/index.js +218 -0
  55. package/dist/tools/validation/index.js +183 -0
  56. package/dist/tools/validation/types.js +4 -0
  57. package/dist/tools/viewport/index.js +310 -0
  58. package/dist/tools/worldpartition/index.js +226 -0
  59. package/dist/tools/worldpartition/types.js +4 -0
  60. package/dist/utils/execFileNoThrow.js +40 -0
  61. package/dist/utils/logger.js +27 -0
  62. package/dist/utils/path-guard.js +26 -0
  63. package/package.json +40 -0
  64. package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
  65. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
  66. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
  67. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
  68. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
  69. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
  70. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
  71. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
  72. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
  73. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
  74. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
  75. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
  76. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
  77. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
  78. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
  79. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
  80. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
  81. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
  82. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
  83. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
  84. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
  85. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
  86. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
  87. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
  88. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
  89. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
  90. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
  91. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
  92. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
  93. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
  94. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
  95. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
  96. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
  97. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
  98. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
  99. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
  100. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
  101. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
  102. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
  103. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
  104. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
  105. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
  106. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
  107. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
  108. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
  109. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
  110. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
  111. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
  112. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
  113. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
  114. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
  115. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
  116. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
  117. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
  118. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
  119. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
  120. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
  121. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
  122. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
  123. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
  124. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
@@ -0,0 +1,23 @@
1
+ // MCPAICommands.h (Plan 22-01)
2
+ // Declares the registration function for all AI system inspection MCP command handlers.
3
+ // Handlers: ai.behaviorTree, ai.stateTree, ai.blackboard, ai.eqs, ai.navmesh
4
+ //
5
+ // Call RegisterAICommands(*Router) in MCPBridgeSubsystem::Initialize()
6
+ // BEFORE the TCP server starts accepting connections.
7
+
8
+ #pragma once
9
+
10
+ #include "MCPCommandRouter.h"
11
+
12
+ /**
13
+ * Register all five AI system command handlers into the given router.
14
+ * Must be called on the game thread before connections arrive.
15
+ *
16
+ * Registered commands:
17
+ * ai.behaviorTree -- inspect Behavior Tree node hierarchy, decorators, services (AI-01)
18
+ * ai.stateTree -- read State Tree states, transitions, and tasks (AI-02)
19
+ * ai.blackboard -- list Blackboard keys with types and default values (AI-03)
20
+ * ai.eqs -- inspect EQS query templates: generators, tests, scoring (AI-04)
21
+ * ai.navmesh -- query NavMesh build status, bounds, point reachability (AI-05)
22
+ */
23
+ void RegisterAICommands(FMCPCommandRouter& Router);
@@ -0,0 +1,415 @@
1
+ // MCPActorCommands.cpp
2
+ // Implements four actor command handlers for the MCP bridge:
3
+ // actor.list -- enumerate all actors in the open level
4
+ // actor.spawn -- spawn an actor by class name at a given location
5
+ // actor.transform -- move/rotate/scale an actor by label
6
+ // actor.delete -- remove an actor from the level
7
+ //
8
+ // All handlers run on the game thread (guaranteed by FMCPCommandRouter::Dispatch).
9
+ // actor.transform and actor.delete call Actor->Modify() before any state change
10
+ // to ensure UE undo/redo history is recorded correctly (Pitfall 5).
11
+
12
+ #include "MCPActorCommands.h"
13
+
14
+ #include "Editor.h"
15
+ #include "Engine/World.h"
16
+ #include "GameFramework/Actor.h"
17
+ #include "EngineUtils.h"
18
+ #include "Serialization/JsonSerializer.h"
19
+ #include "Serialization/JsonWriter.h"
20
+ #include "Dom/JsonObject.h"
21
+ #include "Dom/JsonValue.h"
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Internal helpers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /** Returns a JSON success response string (without trailing newline). */
28
+ static FString BuildActorSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
29
+ {
30
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
31
+ Obj->SetBoolField(TEXT("success"), true);
32
+ Obj->SetStringField(TEXT("correlationId"), CorrId);
33
+ if (Data.IsValid())
34
+ {
35
+ Obj->SetObjectField(TEXT("data"), Data);
36
+ }
37
+
38
+ FString Output;
39
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
40
+ FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
41
+ return Output;
42
+ }
43
+
44
+ /** Returns a JSON error response string (without trailing newline). */
45
+ static FString BuildActorErrorResponse(const FString& CorrId, const FString& Error)
46
+ {
47
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
48
+ Obj->SetBoolField(TEXT("success"), false);
49
+ Obj->SetStringField(TEXT("correlationId"), CorrId);
50
+ Obj->SetStringField(TEXT("error"), Error);
51
+
52
+ FString Output;
53
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
54
+ FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
55
+ return Output;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // RegisterActorCommands
60
+ // ---------------------------------------------------------------------------
61
+
62
+ void RegisterActorCommands(FMCPCommandRouter& Router)
63
+ {
64
+ // -----------------------------------------------------------------------
65
+ // actor.list
66
+ // Enumerates all placed actors in the open level (skips Unreal internal
67
+ // "Default_*" actors). Returns label, class, id, location, rotation, scale.
68
+ // -----------------------------------------------------------------------
69
+ Router.RegisterHandler(TEXT("actor.list"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
70
+ {
71
+ const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
72
+
73
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
74
+ if (!World)
75
+ {
76
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
77
+ return;
78
+ }
79
+
80
+ TArray<TSharedPtr<FJsonValue>> ActorsArray;
81
+
82
+ for (TActorIterator<AActor> It(World); It; ++It)
83
+ {
84
+ AActor* Actor = *It;
85
+ if (!Actor)
86
+ {
87
+ continue;
88
+ }
89
+
90
+ // Skip Unreal internal default actors.
91
+ if (Actor->GetName().StartsWith(TEXT("Default_")))
92
+ {
93
+ continue;
94
+ }
95
+
96
+ const FVector L = Actor->GetActorLocation();
97
+ const FRotator R = Actor->GetActorRotation();
98
+ const FVector S = Actor->GetActorScale3D();
99
+
100
+ // Build location object.
101
+ TSharedPtr<FJsonObject> LocObj = MakeShared<FJsonObject>();
102
+ LocObj->SetNumberField(TEXT("x"), L.X);
103
+ LocObj->SetNumberField(TEXT("y"), L.Y);
104
+ LocObj->SetNumberField(TEXT("z"), L.Z);
105
+
106
+ // Build rotation object.
107
+ TSharedPtr<FJsonObject> RotObj = MakeShared<FJsonObject>();
108
+ RotObj->SetNumberField(TEXT("pitch"), R.Pitch);
109
+ RotObj->SetNumberField(TEXT("yaw"), R.Yaw);
110
+ RotObj->SetNumberField(TEXT("roll"), R.Roll);
111
+
112
+ // Build scale object.
113
+ TSharedPtr<FJsonObject> ScaleObj = MakeShared<FJsonObject>();
114
+ ScaleObj->SetNumberField(TEXT("x"), S.X);
115
+ ScaleObj->SetNumberField(TEXT("y"), S.Y);
116
+ ScaleObj->SetNumberField(TEXT("z"), S.Z);
117
+
118
+ TSharedPtr<FJsonObject> ActorObj = MakeShared<FJsonObject>();
119
+ ActorObj->SetStringField(TEXT("label"), Actor->GetActorLabel());
120
+ ActorObj->SetStringField(TEXT("class"), Actor->GetClass()->GetName());
121
+ ActorObj->SetStringField(TEXT("id"), Actor->GetName());
122
+ ActorObj->SetObjectField(TEXT("location"), LocObj);
123
+ ActorObj->SetObjectField(TEXT("rotation"), RotObj);
124
+ ActorObj->SetObjectField(TEXT("scale"), ScaleObj);
125
+
126
+ ActorsArray.Add(MakeShared<FJsonValueObject>(ActorObj));
127
+ }
128
+
129
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
130
+ Data->SetArrayField(TEXT("actors"), ActorsArray);
131
+ Data->SetNumberField(TEXT("count"), static_cast<double>(ActorsArray.Num()));
132
+
133
+ SendResponse(BuildActorSuccessResponse(CorrId, Data) + TEXT("\n"));
134
+ });
135
+
136
+ // -----------------------------------------------------------------------
137
+ // actor.spawn
138
+ // Creates an actor of a given class at the specified location.
139
+ // Validates the class exists and derives from AActor before spawning.
140
+ // -----------------------------------------------------------------------
141
+ Router.RegisterHandler(TEXT("actor.spawn"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
142
+ {
143
+ const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
144
+
145
+ // Extract payload object.
146
+ TSharedPtr<FJsonObject> Payload;
147
+ const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
148
+ if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
149
+ {
150
+ Payload = (*PayloadVal)->AsObject();
151
+ }
152
+
153
+ FString ClassName;
154
+ if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("class_name"), ClassName) || ClassName.IsEmpty())
155
+ {
156
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("missing_class_name")) + TEXT("\n"));
157
+ return;
158
+ }
159
+
160
+ // Extract optional location (defaults to origin).
161
+ double X = 0.0, Y = 0.0, Z = 0.0;
162
+ {
163
+ const TSharedPtr<FJsonObject>* LocObj;
164
+ if (Payload.IsValid() && Payload->TryGetObjectField(TEXT("location"), LocObj))
165
+ {
166
+ (*LocObj)->TryGetNumberField(TEXT("x"), X);
167
+ (*LocObj)->TryGetNumberField(TEXT("y"), Y);
168
+ (*LocObj)->TryGetNumberField(TEXT("z"), Z);
169
+ }
170
+ }
171
+
172
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
173
+ if (!World)
174
+ {
175
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
176
+ return;
177
+ }
178
+
179
+ // Find actor class by name; try exact name then A-prefixed name.
180
+ UClass* ActorClass = FindObject<UClass>(ANY_PACKAGE, *ClassName);
181
+ if (!ActorClass)
182
+ {
183
+ ActorClass = FindObject<UClass>(ANY_PACKAGE, *(TEXT("A") + ClassName));
184
+ }
185
+ if (!ActorClass)
186
+ {
187
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("class_not_found")) + TEXT("\n"));
188
+ return;
189
+ }
190
+
191
+ // Validate the resolved class is an AActor subclass (T-09-01).
192
+ if (!ActorClass->IsChildOf(AActor::StaticClass()))
193
+ {
194
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("not_an_actor_class")) + TEXT("\n"));
195
+ return;
196
+ }
197
+
198
+ FActorSpawnParameters Params;
199
+ Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
200
+
201
+ const FVector Location(X, Y, Z);
202
+ const FRotator Rotation(0.0, 0.0, 0.0);
203
+
204
+ AActor* Spawned = World->SpawnActor<AActor>(ActorClass, Location, Rotation, Params);
205
+ if (!Spawned)
206
+ {
207
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("spawn_failed")) + TEXT("\n"));
208
+ return;
209
+ }
210
+
211
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
212
+ Data->SetStringField(TEXT("label"), Spawned->GetActorLabel());
213
+ Data->SetStringField(TEXT("id"), Spawned->GetName());
214
+ Data->SetStringField(TEXT("class"), ClassName);
215
+
216
+ SendResponse(BuildActorSuccessResponse(CorrId, Data) + TEXT("\n"));
217
+ });
218
+
219
+ // -----------------------------------------------------------------------
220
+ // actor.transform
221
+ // Moves/rotates/scales an existing actor located by its display label.
222
+ // Calls Actor->Modify() before any change to record the operation in the
223
+ // UE undo stack (Pitfall 5: missing Modify() causes silent data loss).
224
+ // -----------------------------------------------------------------------
225
+ Router.RegisterHandler(TEXT("actor.transform"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
226
+ {
227
+ const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
228
+
229
+ // Extract payload.
230
+ TSharedPtr<FJsonObject> Payload;
231
+ const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
232
+ if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
233
+ {
234
+ Payload = (*PayloadVal)->AsObject();
235
+ }
236
+
237
+ FString ActorLabel;
238
+ if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("actor_label"), ActorLabel) || ActorLabel.IsEmpty())
239
+ {
240
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("missing_actor_label")) + TEXT("\n"));
241
+ return;
242
+ }
243
+
244
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
245
+ if (!World)
246
+ {
247
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
248
+ return;
249
+ }
250
+
251
+ // Find actor by display label.
252
+ AActor* TargetActor = nullptr;
253
+ for (TActorIterator<AActor> It(World); It; ++It)
254
+ {
255
+ if ((*It)->GetActorLabel() == ActorLabel)
256
+ {
257
+ TargetActor = *It;
258
+ break;
259
+ }
260
+ }
261
+
262
+ if (!TargetActor)
263
+ {
264
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("actor_not_found")) + TEXT("\n"));
265
+ return;
266
+ }
267
+
268
+ // Mark the actor for modification BEFORE any property change (Pitfall 5).
269
+ TargetActor->Modify();
270
+
271
+ // Track which fields were applied for the response.
272
+ TSharedPtr<FJsonObject> Applied = MakeShared<FJsonObject>();
273
+
274
+ // Apply location if provided.
275
+ {
276
+ const TSharedPtr<FJsonObject>* LocObj;
277
+ if (Payload->TryGetObjectField(TEXT("location"), LocObj))
278
+ {
279
+ double X = 0.0, Y = 0.0, Z = 0.0;
280
+ (*LocObj)->TryGetNumberField(TEXT("x"), X);
281
+ (*LocObj)->TryGetNumberField(TEXT("y"), Y);
282
+ (*LocObj)->TryGetNumberField(TEXT("z"), Z);
283
+
284
+ TargetActor->SetActorLocation(FVector(X, Y, Z), false, nullptr, ETeleportType::TeleportPhysics);
285
+
286
+ TSharedPtr<FJsonObject> AppliedLoc = MakeShared<FJsonObject>();
287
+ AppliedLoc->SetNumberField(TEXT("x"), X);
288
+ AppliedLoc->SetNumberField(TEXT("y"), Y);
289
+ AppliedLoc->SetNumberField(TEXT("z"), Z);
290
+ Applied->SetObjectField(TEXT("location"), AppliedLoc);
291
+ }
292
+ }
293
+
294
+ // Apply rotation if provided.
295
+ {
296
+ const TSharedPtr<FJsonObject>* RotObj;
297
+ if (Payload->TryGetObjectField(TEXT("rotation"), RotObj))
298
+ {
299
+ double Pitch = 0.0, Yaw = 0.0, Roll = 0.0;
300
+ (*RotObj)->TryGetNumberField(TEXT("pitch"), Pitch);
301
+ (*RotObj)->TryGetNumberField(TEXT("yaw"), Yaw);
302
+ (*RotObj)->TryGetNumberField(TEXT("roll"), Roll);
303
+
304
+ TargetActor->SetActorRotation(FRotator(Pitch, Yaw, Roll), ETeleportType::TeleportPhysics);
305
+
306
+ TSharedPtr<FJsonObject> AppliedRot = MakeShared<FJsonObject>();
307
+ AppliedRot->SetNumberField(TEXT("pitch"), Pitch);
308
+ AppliedRot->SetNumberField(TEXT("yaw"), Yaw);
309
+ AppliedRot->SetNumberField(TEXT("roll"), Roll);
310
+ Applied->SetObjectField(TEXT("rotation"), AppliedRot);
311
+ }
312
+ }
313
+
314
+ // Apply scale if provided.
315
+ {
316
+ const TSharedPtr<FJsonObject>* ScaleObj;
317
+ if (Payload->TryGetObjectField(TEXT("scale"), ScaleObj))
318
+ {
319
+ double X = 1.0, Y = 1.0, Z = 1.0;
320
+ (*ScaleObj)->TryGetNumberField(TEXT("x"), X);
321
+ (*ScaleObj)->TryGetNumberField(TEXT("y"), Y);
322
+ (*ScaleObj)->TryGetNumberField(TEXT("z"), Z);
323
+
324
+ TargetActor->SetActorScale3D(FVector(X, Y, Z));
325
+
326
+ TSharedPtr<FJsonObject> AppliedScale = MakeShared<FJsonObject>();
327
+ AppliedScale->SetNumberField(TEXT("x"), X);
328
+ AppliedScale->SetNumberField(TEXT("y"), Y);
329
+ AppliedScale->SetNumberField(TEXT("z"), Z);
330
+ Applied->SetObjectField(TEXT("scale"), AppliedScale);
331
+ }
332
+ }
333
+
334
+ // Mark the level package dirty so Unreal knows it needs saving.
335
+ if (TargetActor->GetLevel())
336
+ {
337
+ TargetActor->GetLevel()->MarkPackageDirty();
338
+ }
339
+
340
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
341
+ Data->SetStringField(TEXT("label"), ActorLabel);
342
+ Data->SetObjectField(TEXT("applied"), Applied);
343
+
344
+ SendResponse(BuildActorSuccessResponse(CorrId, Data) + TEXT("\n"));
345
+ });
346
+
347
+ // -----------------------------------------------------------------------
348
+ // actor.delete
349
+ // Removes an actor from the level by its display label.
350
+ // Calls Actor->Modify() before destruction to record the deletion in the
351
+ // UE undo stack (Pitfall 5). DestroyActor returns false for indestructible
352
+ // actors (T-09-03: that error is returned to the caller).
353
+ // -----------------------------------------------------------------------
354
+ Router.RegisterHandler(TEXT("actor.delete"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
355
+ {
356
+ const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
357
+
358
+ // Extract payload.
359
+ TSharedPtr<FJsonObject> Payload;
360
+ const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
361
+ if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
362
+ {
363
+ Payload = (*PayloadVal)->AsObject();
364
+ }
365
+
366
+ FString ActorLabel;
367
+ if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("actor_label"), ActorLabel) || ActorLabel.IsEmpty())
368
+ {
369
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("missing_actor_label")) + TEXT("\n"));
370
+ return;
371
+ }
372
+
373
+ UWorld* World = GEditor ? GEditor->GetEditorWorldContext().World() : nullptr;
374
+ if (!World)
375
+ {
376
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
377
+ return;
378
+ }
379
+
380
+ // Find actor by display label.
381
+ AActor* TargetActor = nullptr;
382
+ for (TActorIterator<AActor> It(World); It; ++It)
383
+ {
384
+ if ((*It)->GetActorLabel() == ActorLabel)
385
+ {
386
+ TargetActor = *It;
387
+ break;
388
+ }
389
+ }
390
+
391
+ if (!TargetActor)
392
+ {
393
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("actor_not_found")) + TEXT("\n"));
394
+ return;
395
+ }
396
+
397
+ // Mark the actor for modification BEFORE destruction (Pitfall 5).
398
+ TargetActor->Modify();
399
+
400
+ // DestroyActor: bNetForce=false, bShouldModifyLevel=true (marks level dirty).
401
+ const bool bDestroyed = World->DestroyActor(TargetActor, false, true);
402
+ if (!bDestroyed)
403
+ {
404
+ // Indestructible actor -- return structured error (T-09-03).
405
+ SendResponse(BuildActorErrorResponse(CorrId, TEXT("destroy_failed")) + TEXT("\n"));
406
+ return;
407
+ }
408
+
409
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
410
+ Data->SetStringField(TEXT("label"), ActorLabel);
411
+ Data->SetBoolField(TEXT("destroyed"), true);
412
+
413
+ SendResponse(BuildActorSuccessResponse(CorrId, Data) + TEXT("\n"));
414
+ });
415
+ }
@@ -0,0 +1,16 @@
1
+ // MCPActorCommands.h
2
+ // Declares the registration function for all actor-related MCP command handlers.
3
+ // Handlers: actor.list, actor.spawn, actor.transform, actor.delete
4
+ //
5
+ // Call RegisterActorCommands(*Router) in MCPBridgeSubsystem::Initialize()
6
+ // BEFORE the TCP server starts accepting connections.
7
+
8
+ #pragma once
9
+
10
+ #include "MCPCommandRouter.h"
11
+
12
+ /**
13
+ * Register all four actor command handlers into the given router.
14
+ * Must be called on the game thread before connections arrive.
15
+ */
16
+ void RegisterActorCommands(FMCPCommandRouter& Router);