flockbay 0.10.15

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 (80) hide show
  1. package/README.md +56 -0
  2. package/bin/flockbay-mcp.mjs +56 -0
  3. package/bin/flockbay.mjs +78 -0
  4. package/dist/codex/flockbayMcpStdioBridge.cjs +383 -0
  5. package/dist/codex/flockbayMcpStdioBridge.d.cts +2 -0
  6. package/dist/codex/flockbayMcpStdioBridge.d.mts +2 -0
  7. package/dist/codex/flockbayMcpStdioBridge.mjs +381 -0
  8. package/dist/flockbayScreenshotGate-DJX3Is5d.mjs +136 -0
  9. package/dist/flockbayScreenshotGate-DkxU24cR.cjs +138 -0
  10. package/dist/index--o4BPz5o.cjs +10311 -0
  11. package/dist/index-CUp3juDS.mjs +10268 -0
  12. package/dist/index.cjs +43 -0
  13. package/dist/index.d.cts +1 -0
  14. package/dist/index.d.mts +1 -0
  15. package/dist/index.mjs +40 -0
  16. package/dist/lib.cjs +33 -0
  17. package/dist/lib.d.cts +957 -0
  18. package/dist/lib.d.mts +957 -0
  19. package/dist/lib.mjs +23 -0
  20. package/dist/runCodex-D3eT-TvB.cjs +3449 -0
  21. package/dist/runCodex-o6PCbHQ7.mjs +3446 -0
  22. package/dist/runGemini-Bt0oEj_g.mjs +3183 -0
  23. package/dist/runGemini-CBxZp6I7.cjs +3185 -0
  24. package/dist/types-C-jnUdn_.cjs +4498 -0
  25. package/dist/types-DGd6ea2Z.mjs +4450 -0
  26. package/kits/kit.open_world/kit.json +59 -0
  27. package/package.json +130 -0
  28. package/scripts/claude_local_launcher.cjs +73 -0
  29. package/scripts/claude_remote_launcher.cjs +16 -0
  30. package/scripts/claude_version_utils.cjs +391 -0
  31. package/scripts/ripgrep_launcher.cjs +33 -0
  32. package/scripts/session_hook_forwarder.cjs +49 -0
  33. package/scripts/test-codex-abort-history.mjs +77 -0
  34. package/scripts/unpack-tools.cjs +222 -0
  35. package/tools/licenses/difftastic-LICENSE +21 -0
  36. package/tools/licenses/ripgrep-LICENSE +3 -0
  37. package/tools/unreal-mcp/UPSTREAM_VERSION.md +8 -0
  38. package/tools/unreal-mcp/upstream/Docs/README.md +8 -0
  39. package/tools/unreal-mcp/upstream/Docs/Tools/README.md +7 -0
  40. package/tools/unreal-mcp/upstream/Docs/Tools/actor_tools.md +184 -0
  41. package/tools/unreal-mcp/upstream/Docs/Tools/blueprint_tools.md +268 -0
  42. package/tools/unreal-mcp/upstream/Docs/Tools/editor_tools.md +104 -0
  43. package/tools/unreal-mcp/upstream/Docs/Tools/node_tools.md +274 -0
  44. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Config/FilterPlugin.ini +8 -0
  45. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +1160 -0
  46. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +924 -0
  47. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPCommonUtils.cpp +709 -0
  48. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPEditorCommands.cpp +896 -0
  49. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPProjectCommands.cpp +72 -0
  50. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/Commands/UnrealMCPUMGCommands.cpp +544 -0
  51. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/MCPServerRunnable.cpp +321 -0
  52. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPBridge.cpp +419 -0
  53. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Private/UnrealMCPModule.cpp +21 -0
  54. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintCommands.h +34 -0
  55. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +27 -0
  56. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPCommonUtils.h +59 -0
  57. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPEditorCommands.h +40 -0
  58. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPProjectCommands.h +20 -0
  59. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/Commands/UnrealMCPUMGCommands.h +82 -0
  60. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/MCPServerRunnable.h +34 -0
  61. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPBridge.h +64 -0
  62. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/Public/UnrealMCPModule.h +22 -0
  63. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/Source/UnrealMCP/UnrealMCP.Build.cs +78 -0
  64. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/UnrealMCP/UnrealMCP.uplugin +36 -0
  65. package/tools/unreal-mcp/upstream/Python/README.md +40 -0
  66. package/tools/unreal-mcp/upstream/Python/pyproject.toml +22 -0
  67. package/tools/unreal-mcp/upstream/Python/scripts/actors/test_cube.py +203 -0
  68. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_blueprints_with_different_components.py +497 -0
  69. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_cube_blueprint.py +194 -0
  70. package/tools/unreal-mcp/upstream/Python/scripts/node/test_component_reference.py +267 -0
  71. package/tools/unreal-mcp/upstream/Python/scripts/node/test_create_bird_blueprint_with_input_and_camera.py +618 -0
  72. package/tools/unreal-mcp/upstream/Python/scripts/node/test_input_mapping.py +366 -0
  73. package/tools/unreal-mcp/upstream/Python/scripts/node/test_physics_variables.py +390 -0
  74. package/tools/unreal-mcp/upstream/Python/tools/blueprint_tools.py +420 -0
  75. package/tools/unreal-mcp/upstream/Python/tools/editor_tools.py +369 -0
  76. package/tools/unreal-mcp/upstream/Python/tools/node_tools.py +430 -0
  77. package/tools/unreal-mcp/upstream/Python/tools/project_tools.py +64 -0
  78. package/tools/unreal-mcp/upstream/Python/tools/umg_tools.py +333 -0
  79. package/tools/unreal-mcp/upstream/Python/unreal_mcp_server.py +398 -0
  80. package/tools/unreal-mcp/upstream/Python/uv.lock +521 -0
@@ -0,0 +1,896 @@
1
+ #include "Commands/UnrealMCPEditorCommands.h"
2
+ #include "Commands/UnrealMCPCommonUtils.h"
3
+ #include "Editor.h"
4
+ #include "EditorViewportClient.h"
5
+ #include "LevelEditorViewport.h"
6
+ #include "IAssetViewport.h"
7
+ #include "ImageUtils.h"
8
+ #include "HighResScreenshot.h"
9
+ #include "Engine/GameViewportClient.h"
10
+ #include "Misc/FileHelper.h"
11
+ #include "Misc/Paths.h"
12
+ #include "HAL/FileManager.h"
13
+ #include "GameFramework/Actor.h"
14
+ #include "Engine/Selection.h"
15
+ #include "Kismet/GameplayStatics.h"
16
+ #include "Engine/StaticMeshActor.h"
17
+ #include "Engine/StaticMesh.h"
18
+ #include "Engine/DirectionalLight.h"
19
+ #include "Engine/PointLight.h"
20
+ #include "Engine/SpotLight.h"
21
+ #include "Camera/CameraActor.h"
22
+ #include "Components/StaticMeshComponent.h"
23
+ #include "EditorSubsystem.h"
24
+ #include "Subsystems/EditorActorSubsystem.h"
25
+ #include "Engine/Blueprint.h"
26
+ #include "Engine/BlueprintGeneratedClass.h"
27
+ #include "UnrealEdGlobals.h"
28
+ #include "Editor/UnrealEdEngine.h"
29
+ #include "UObject/UnrealType.h"
30
+ #include "Modules/ModuleManager.h"
31
+ #include "LevelEditor.h"
32
+ #include "PlayInEditorDataTypes.h"
33
+ #include "Settings/LevelEditorPlaySettings.h"
34
+ #include "Framework/Application/SlateApplication.h"
35
+
36
+ FUnrealMCPEditorCommands::FUnrealMCPEditorCommands()
37
+ {
38
+ }
39
+
40
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
41
+ {
42
+ // Actor manipulation commands
43
+ if (CommandType == TEXT("get_actors_in_level"))
44
+ {
45
+ return HandleGetActorsInLevel(Params);
46
+ }
47
+ else if (CommandType == TEXT("find_actors_by_name"))
48
+ {
49
+ return HandleFindActorsByName(Params);
50
+ }
51
+ else if (CommandType == TEXT("spawn_actor") || CommandType == TEXT("create_actor"))
52
+ {
53
+ if (CommandType == TEXT("create_actor"))
54
+ {
55
+ UE_LOG(LogTemp, Warning, TEXT("'create_actor' command is deprecated and will be removed in a future version. Please use 'spawn_actor' instead."));
56
+ }
57
+ return HandleSpawnActor(Params);
58
+ }
59
+ else if (CommandType == TEXT("delete_actor"))
60
+ {
61
+ return HandleDeleteActor(Params);
62
+ }
63
+ else if (CommandType == TEXT("set_actor_transform"))
64
+ {
65
+ return HandleSetActorTransform(Params);
66
+ }
67
+ else if (CommandType == TEXT("get_actor_properties"))
68
+ {
69
+ return HandleGetActorProperties(Params);
70
+ }
71
+ else if (CommandType == TEXT("set_actor_property"))
72
+ {
73
+ return HandleSetActorProperty(Params);
74
+ }
75
+ // Blueprint actor spawning
76
+ else if (CommandType == TEXT("spawn_blueprint_actor"))
77
+ {
78
+ return HandleSpawnBlueprintActor(Params);
79
+ }
80
+ // Editor viewport commands
81
+ else if (CommandType == TEXT("focus_viewport"))
82
+ {
83
+ return HandleFocusViewport(Params);
84
+ }
85
+ else if (CommandType == TEXT("take_screenshot"))
86
+ {
87
+ return HandleTakeScreenshot(Params);
88
+ }
89
+ else if (CommandType == TEXT("get_play_in_editor_status"))
90
+ {
91
+ return HandleGetPlayInEditorStatus(Params);
92
+ }
93
+ else if (CommandType == TEXT("play_in_editor"))
94
+ {
95
+ return HandlePlayInEditor(Params);
96
+ }
97
+ else if (CommandType == TEXT("play_in_editor_windowed"))
98
+ {
99
+ return HandlePlayInEditorWindowed(Params);
100
+ }
101
+ else if (CommandType == TEXT("stop_play_in_editor"))
102
+ {
103
+ return HandleStopPlayInEditor(Params);
104
+ }
105
+
106
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown editor command: %s"), *CommandType));
107
+ }
108
+
109
+ static TSharedPtr<FJsonObject> CreatePlayStatusResponse()
110
+ {
111
+ if (!GEditor)
112
+ {
113
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
114
+ }
115
+
116
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
117
+ ResultObj->SetBoolField(TEXT("isPlaySessionInProgress"), GEditor->IsPlaySessionInProgress());
118
+ ResultObj->SetBoolField(TEXT("isPlaySessionRequestQueued"), GEditor->IsPlaySessionRequestQueued());
119
+ ResultObj->SetBoolField(TEXT("isPlayingSessionInEditor"), GEditor->IsPlayingSessionInEditor());
120
+ return ResultObj;
121
+ }
122
+
123
+ static bool IsSafeToModifyEditorWorld(FString& OutError)
124
+ {
125
+ if (!GEditor)
126
+ {
127
+ OutError = TEXT("Editor is not available (GEditor is null).");
128
+ return false;
129
+ }
130
+
131
+ if (GEditor->IsPlaySessionInProgress())
132
+ {
133
+ OutError = TEXT("Play-In-Editor is running or queued. Stop PIE before modifying the editor world (use stop_play_in_editor).");
134
+ return false;
135
+ }
136
+
137
+ return true;
138
+ }
139
+
140
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetPlayInEditorStatus(const TSharedPtr<FJsonObject>& Params)
141
+ {
142
+ return CreatePlayStatusResponse();
143
+ }
144
+
145
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandlePlayInEditor(const TSharedPtr<FJsonObject>& Params)
146
+ {
147
+ if (!GEditor)
148
+ {
149
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
150
+ }
151
+
152
+ if (GEditor->IsPlaySessionInProgress())
153
+ {
154
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Play session already running or queued."));
155
+ }
156
+
157
+ // Use the active editor viewport, matching how the LevelEditor subsystem triggers PIE.
158
+ FLevelEditorModule* LevelEditorModule = FModuleManager::LoadModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
159
+ if (!LevelEditorModule)
160
+ {
161
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("LevelEditor module not available."));
162
+ }
163
+
164
+ TSharedPtr<IAssetViewport> ActiveViewport = LevelEditorModule->GetFirstActiveViewport();
165
+ if (!ActiveViewport.IsValid())
166
+ {
167
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("No active level viewport. Click the viewport and retry."));
168
+ }
169
+
170
+ FRequestPlaySessionParams SessionParams;
171
+ SessionParams.WorldType = EPlaySessionWorldType::PlayInEditor;
172
+ SessionParams.DestinationSlateViewport = ActiveViewport;
173
+
174
+ GEditor->RequestPlaySession(SessionParams);
175
+ const bool bQueued = GEditor->IsPlaySessionRequestQueued();
176
+
177
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
178
+ ResultObj->SetBoolField(TEXT("requested"), true);
179
+ ResultObj->SetBoolField(TEXT("queued"), bQueued);
180
+ ResultObj->SetBoolField(TEXT("started"), bQueued);
181
+ ResultObj->SetStringField(TEXT("mode"), TEXT("pie"));
182
+ return ResultObj;
183
+ }
184
+
185
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandlePlayInEditorWindowed(const TSharedPtr<FJsonObject>& Params)
186
+ {
187
+ if (!GEditor)
188
+ {
189
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
190
+ }
191
+
192
+ if (GEditor->IsPlaySessionInProgress())
193
+ {
194
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Play session already running or queued."));
195
+ }
196
+
197
+ // Match the editor's built-in "New Editor Window (PIE)" behavior by setting the play mode type and
198
+ // not specifying a DestinationSlateViewport.
199
+ ULevelEditorPlaySettings* PlaySettings = GetMutableDefault<ULevelEditorPlaySettings>();
200
+ if (PlaySettings)
201
+ {
202
+ PlaySettings->LastExecutedPlayModeType = EPlayModeType::PlayMode_InEditorFloating;
203
+
204
+ if (FProperty* Prop = ULevelEditorPlaySettings::StaticClass()->FindPropertyByName(
205
+ GET_MEMBER_NAME_CHECKED(ULevelEditorPlaySettings, LastExecutedPlayModeType)))
206
+ {
207
+ FPropertyChangedEvent PropChangeEvent(Prop);
208
+ PlaySettings->PostEditChangeProperty(PropChangeEvent);
209
+ }
210
+
211
+ PlaySettings->SaveConfig();
212
+ }
213
+
214
+ const bool bAtPlayerStart =
215
+ PlaySettings && PlaySettings->LastExecutedPlayModeLocation == EPlayModeLocations::PlayLocation_DefaultPlayerStart;
216
+
217
+ FRequestPlaySessionParams SessionParams;
218
+ SessionParams.WorldType = EPlaySessionWorldType::PlayInEditor;
219
+
220
+ // If the user is playing from current camera location, use the active viewport camera as the start transform.
221
+ if (!bAtPlayerStart)
222
+ {
223
+ FLevelEditorModule* LevelEditorModule = FModuleManager::LoadModulePtr<FLevelEditorModule>(TEXT("LevelEditor"));
224
+ if (LevelEditorModule)
225
+ {
226
+ TSharedPtr<IAssetViewport> ActiveViewport = LevelEditorModule->GetFirstActiveViewport();
227
+ if (ActiveViewport.IsValid() && FSlateApplication::IsInitialized() &&
228
+ FSlateApplication::Get().FindWidgetWindow(ActiveViewport->AsWidget()).IsValid())
229
+ {
230
+ SessionParams.StartLocation = ActiveViewport->GetAssetViewportClient().GetViewLocation();
231
+ SessionParams.StartRotation = ActiveViewport->GetAssetViewportClient().GetViewRotation();
232
+ }
233
+ }
234
+ }
235
+
236
+ GEditor->RequestPlaySession(SessionParams);
237
+ const bool bQueued = GEditor->IsPlaySessionRequestQueued();
238
+
239
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
240
+ ResultObj->SetBoolField(TEXT("requested"), true);
241
+ ResultObj->SetBoolField(TEXT("queued"), bQueued);
242
+ ResultObj->SetBoolField(TEXT("started"), bQueued);
243
+ ResultObj->SetStringField(TEXT("mode"), TEXT("pie_new_window"));
244
+ return ResultObj;
245
+ }
246
+
247
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleStopPlayInEditor(const TSharedPtr<FJsonObject>& Params)
248
+ {
249
+ if (!GEditor)
250
+ {
251
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Editor is not available (GEditor is null)."));
252
+ }
253
+
254
+ if (GEditor->IsPlaySessionRequestQueued() && !GEditor->IsPlayingSessionInEditor())
255
+ {
256
+ GEditor->CancelRequestPlaySession();
257
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
258
+ ResultObj->SetBoolField(TEXT("stopped"), true);
259
+ ResultObj->SetBoolField(TEXT("canceled"), true);
260
+ return ResultObj;
261
+ }
262
+
263
+ if (!GEditor->IsPlayingSessionInEditor())
264
+ {
265
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("No play session is running."));
266
+ }
267
+
268
+ GEditor->RequestEndPlayMap();
269
+
270
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
271
+ ResultObj->SetBoolField(TEXT("stopped"), true);
272
+ return ResultObj;
273
+ }
274
+
275
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorsInLevel(const TSharedPtr<FJsonObject>& Params)
276
+ {
277
+ TArray<AActor*> AllActors;
278
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
279
+
280
+ TArray<TSharedPtr<FJsonValue>> ActorArray;
281
+ for (AActor* Actor : AllActors)
282
+ {
283
+ if (Actor)
284
+ {
285
+ ActorArray.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
286
+ }
287
+ }
288
+
289
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
290
+ ResultObj->SetArrayField(TEXT("actors"), ActorArray);
291
+
292
+ return ResultObj;
293
+ }
294
+
295
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFindActorsByName(const TSharedPtr<FJsonObject>& Params)
296
+ {
297
+ FString Pattern;
298
+ if (!Params->TryGetStringField(TEXT("pattern"), Pattern))
299
+ {
300
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'pattern' parameter"));
301
+ }
302
+
303
+ TArray<AActor*> AllActors;
304
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
305
+
306
+ TArray<TSharedPtr<FJsonValue>> MatchingActors;
307
+ for (AActor* Actor : AllActors)
308
+ {
309
+ if (Actor && Actor->GetName().Contains(Pattern))
310
+ {
311
+ MatchingActors.Add(FUnrealMCPCommonUtils::ActorToJson(Actor));
312
+ }
313
+ }
314
+
315
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
316
+ ResultObj->SetArrayField(TEXT("actors"), MatchingActors);
317
+
318
+ return ResultObj;
319
+ }
320
+
321
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnActor(const TSharedPtr<FJsonObject>& Params)
322
+ {
323
+ {
324
+ FString Err;
325
+ if (!IsSafeToModifyEditorWorld(Err))
326
+ {
327
+ return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
328
+ }
329
+ }
330
+
331
+ // Get required parameters
332
+ FString ActorType;
333
+ if (!Params->TryGetStringField(TEXT("type"), ActorType))
334
+ {
335
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'type' parameter"));
336
+ }
337
+
338
+ // Get actor name (required parameter)
339
+ FString ActorName;
340
+ if (!Params->TryGetStringField(TEXT("name"), ActorName))
341
+ {
342
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
343
+ }
344
+
345
+ // Get optional transform parameters
346
+ FVector Location(0.0f, 0.0f, 0.0f);
347
+ FRotator Rotation(0.0f, 0.0f, 0.0f);
348
+ FVector Scale(1.0f, 1.0f, 1.0f);
349
+
350
+ if (Params->HasField(TEXT("location")))
351
+ {
352
+ Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
353
+ }
354
+ if (Params->HasField(TEXT("rotation")))
355
+ {
356
+ Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
357
+ }
358
+ if (Params->HasField(TEXT("scale")))
359
+ {
360
+ Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
361
+ }
362
+
363
+ // Create the actor based on type
364
+ AActor* NewActor = nullptr;
365
+ UWorld* World = GEditor->GetEditorWorldContext().World();
366
+
367
+ if (!World)
368
+ {
369
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
370
+ }
371
+
372
+ // Check if an actor with this name already exists
373
+ TArray<AActor*> AllActors;
374
+ UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), AllActors);
375
+ for (AActor* Actor : AllActors)
376
+ {
377
+ if (Actor && Actor->GetName() == ActorName)
378
+ {
379
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor with name '%s' already exists"), *ActorName));
380
+ }
381
+ }
382
+
383
+ FActorSpawnParameters SpawnParams;
384
+ SpawnParams.Name = *ActorName;
385
+
386
+ if (ActorType == TEXT("StaticMeshActor"))
387
+ {
388
+ NewActor = World->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), Location, Rotation, SpawnParams);
389
+
390
+ // Optional: assign a StaticMesh at spawn time (preferred, since nested component property setting is limited).
391
+ FString StaticMeshPath;
392
+ if (Params->TryGetStringField(TEXT("static_mesh_path"), StaticMeshPath) ||
393
+ Params->TryGetStringField(TEXT("static_mesh"), StaticMeshPath))
394
+ {
395
+ StaticMeshPath = StaticMeshPath.TrimStartAndEnd();
396
+ if (!StaticMeshPath.IsEmpty())
397
+ {
398
+ UStaticMesh* StaticMesh = LoadObject<UStaticMesh>(nullptr, *StaticMeshPath);
399
+ if (!StaticMesh && !StaticMeshPath.StartsWith(TEXT("StaticMesh'")))
400
+ {
401
+ StaticMesh = LoadObject<UStaticMesh>(nullptr, *FString::Printf(TEXT("StaticMesh'%s'"), *StaticMeshPath));
402
+ }
403
+
404
+ if (!StaticMesh)
405
+ {
406
+ return FUnrealMCPCommonUtils::CreateErrorResponse(
407
+ FString::Printf(TEXT("Static mesh not found: %s"), *StaticMeshPath));
408
+ }
409
+
410
+ AStaticMeshActor* StaticMeshActor = Cast<AStaticMeshActor>(NewActor);
411
+ if (!StaticMeshActor || !StaticMeshActor->GetStaticMeshComponent())
412
+ {
413
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Spawned StaticMeshActor has no StaticMeshComponent."));
414
+ }
415
+
416
+ StaticMeshActor->GetStaticMeshComponent()->SetStaticMesh(StaticMesh);
417
+ StaticMeshActor->GetStaticMeshComponent()->MarkRenderStateDirty();
418
+ }
419
+ }
420
+ }
421
+ else if (ActorType == TEXT("PointLight"))
422
+ {
423
+ NewActor = World->SpawnActor<APointLight>(APointLight::StaticClass(), Location, Rotation, SpawnParams);
424
+ }
425
+ else if (ActorType == TEXT("SpotLight"))
426
+ {
427
+ NewActor = World->SpawnActor<ASpotLight>(ASpotLight::StaticClass(), Location, Rotation, SpawnParams);
428
+ }
429
+ else if (ActorType == TEXT("DirectionalLight"))
430
+ {
431
+ NewActor = World->SpawnActor<ADirectionalLight>(ADirectionalLight::StaticClass(), Location, Rotation, SpawnParams);
432
+ }
433
+ else if (ActorType == TEXT("CameraActor"))
434
+ {
435
+ NewActor = World->SpawnActor<ACameraActor>(ACameraActor::StaticClass(), Location, Rotation, SpawnParams);
436
+ }
437
+ else
438
+ {
439
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Unknown actor type: %s"), *ActorType));
440
+ }
441
+
442
+ if (NewActor)
443
+ {
444
+ // Set scale (since SpawnActor only takes location and rotation)
445
+ FTransform Transform = NewActor->GetTransform();
446
+ Transform.SetScale3D(Scale);
447
+ NewActor->SetActorTransform(Transform);
448
+
449
+ // Return the created actor's details
450
+ return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
451
+ }
452
+
453
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to create actor"));
454
+ }
455
+
456
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleDeleteActor(const TSharedPtr<FJsonObject>& Params)
457
+ {
458
+ {
459
+ FString Err;
460
+ if (!IsSafeToModifyEditorWorld(Err))
461
+ {
462
+ return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
463
+ }
464
+ }
465
+
466
+ FString ActorName;
467
+ if (!Params->TryGetStringField(TEXT("name"), ActorName))
468
+ {
469
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
470
+ }
471
+
472
+ TArray<AActor*> AllActors;
473
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
474
+
475
+ for (AActor* Actor : AllActors)
476
+ {
477
+ if (Actor && Actor->GetName() == ActorName)
478
+ {
479
+ // Store actor info before deletion for the response
480
+ TSharedPtr<FJsonObject> ActorInfo = FUnrealMCPCommonUtils::ActorToJsonObject(Actor);
481
+
482
+ // Delete the actor
483
+ Actor->Destroy();
484
+
485
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
486
+ ResultObj->SetObjectField(TEXT("deleted_actor"), ActorInfo);
487
+ return ResultObj;
488
+ }
489
+ }
490
+
491
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
492
+ }
493
+
494
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorTransform(const TSharedPtr<FJsonObject>& Params)
495
+ {
496
+ {
497
+ FString Err;
498
+ if (!IsSafeToModifyEditorWorld(Err))
499
+ {
500
+ return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
501
+ }
502
+ }
503
+
504
+ // Get actor name
505
+ FString ActorName;
506
+ if (!Params->TryGetStringField(TEXT("name"), ActorName))
507
+ {
508
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
509
+ }
510
+
511
+ // Find the actor
512
+ AActor* TargetActor = nullptr;
513
+ TArray<AActor*> AllActors;
514
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
515
+
516
+ for (AActor* Actor : AllActors)
517
+ {
518
+ if (Actor && Actor->GetName() == ActorName)
519
+ {
520
+ TargetActor = Actor;
521
+ break;
522
+ }
523
+ }
524
+
525
+ if (!TargetActor)
526
+ {
527
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
528
+ }
529
+
530
+ // Get transform parameters
531
+ FTransform NewTransform = TargetActor->GetTransform();
532
+
533
+ if (Params->HasField(TEXT("location")))
534
+ {
535
+ NewTransform.SetLocation(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location")));
536
+ }
537
+ if (Params->HasField(TEXT("rotation")))
538
+ {
539
+ NewTransform.SetRotation(FQuat(FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"))));
540
+ }
541
+ if (Params->HasField(TEXT("scale")))
542
+ {
543
+ NewTransform.SetScale3D(FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale")));
544
+ }
545
+
546
+ // Set the new transform
547
+ TargetActor->SetActorTransform(NewTransform);
548
+
549
+ // Return updated actor info
550
+ return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
551
+ }
552
+
553
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleGetActorProperties(const TSharedPtr<FJsonObject>& Params)
554
+ {
555
+ // Get actor name
556
+ FString ActorName;
557
+ if (!Params->TryGetStringField(TEXT("name"), ActorName))
558
+ {
559
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
560
+ }
561
+
562
+ // Find the actor
563
+ AActor* TargetActor = nullptr;
564
+ TArray<AActor*> AllActors;
565
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
566
+
567
+ for (AActor* Actor : AllActors)
568
+ {
569
+ if (Actor && Actor->GetName() == ActorName)
570
+ {
571
+ TargetActor = Actor;
572
+ break;
573
+ }
574
+ }
575
+
576
+ if (!TargetActor)
577
+ {
578
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
579
+ }
580
+
581
+ // Always return detailed properties for this command
582
+ return FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true);
583
+ }
584
+
585
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSetActorProperty(const TSharedPtr<FJsonObject>& Params)
586
+ {
587
+ {
588
+ FString Err;
589
+ if (!IsSafeToModifyEditorWorld(Err))
590
+ {
591
+ return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
592
+ }
593
+ }
594
+
595
+ // Get actor name
596
+ FString ActorName;
597
+ if (!Params->TryGetStringField(TEXT("name"), ActorName))
598
+ {
599
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'name' parameter"));
600
+ }
601
+
602
+ // Find the actor
603
+ AActor* TargetActor = nullptr;
604
+ TArray<AActor*> AllActors;
605
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
606
+
607
+ for (AActor* Actor : AllActors)
608
+ {
609
+ if (Actor && Actor->GetName() == ActorName)
610
+ {
611
+ TargetActor = Actor;
612
+ break;
613
+ }
614
+ }
615
+
616
+ if (!TargetActor)
617
+ {
618
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *ActorName));
619
+ }
620
+
621
+ // Get property name
622
+ FString PropertyName;
623
+ if (!Params->TryGetStringField(TEXT("property_name"), PropertyName))
624
+ {
625
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_name' parameter"));
626
+ }
627
+
628
+ // Get property value
629
+ if (!Params->HasField(TEXT("property_value")))
630
+ {
631
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'property_value' parameter"));
632
+ }
633
+
634
+ TSharedPtr<FJsonValue> PropertyValue = Params->Values.FindRef(TEXT("property_value"));
635
+
636
+ // Set the property using our utility function
637
+ FString ErrorMessage;
638
+ if (FUnrealMCPCommonUtils::SetObjectProperty(TargetActor, PropertyName, PropertyValue, ErrorMessage))
639
+ {
640
+ // Property set successfully
641
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
642
+ ResultObj->SetStringField(TEXT("actor"), ActorName);
643
+ ResultObj->SetStringField(TEXT("property"), PropertyName);
644
+ ResultObj->SetBoolField(TEXT("success"), true);
645
+
646
+ // Also include the full actor details
647
+ ResultObj->SetObjectField(TEXT("actor_details"), FUnrealMCPCommonUtils::ActorToJsonObject(TargetActor, true));
648
+ return ResultObj;
649
+ }
650
+ else
651
+ {
652
+ return FUnrealMCPCommonUtils::CreateErrorResponse(ErrorMessage);
653
+ }
654
+ }
655
+
656
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleSpawnBlueprintActor(const TSharedPtr<FJsonObject>& Params)
657
+ {
658
+ {
659
+ FString Err;
660
+ if (!IsSafeToModifyEditorWorld(Err))
661
+ {
662
+ return FUnrealMCPCommonUtils::CreateErrorResponse(Err);
663
+ }
664
+ }
665
+
666
+ // Get required parameters
667
+ FString BlueprintName;
668
+ if (!Params->TryGetStringField(TEXT("blueprint_name"), BlueprintName))
669
+ {
670
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'blueprint_name' parameter"));
671
+ }
672
+
673
+ FString ActorName;
674
+ if (!Params->TryGetStringField(TEXT("actor_name"), ActorName))
675
+ {
676
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Missing 'actor_name' parameter"));
677
+ }
678
+
679
+ // Find the blueprint
680
+ if (BlueprintName.IsEmpty())
681
+ {
682
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Blueprint name is empty"));
683
+ }
684
+
685
+ FString Root = TEXT("/Game/Blueprints/");
686
+ FString AssetPath = Root + BlueprintName;
687
+
688
+ if (!FPackageName::DoesPackageExist(AssetPath))
689
+ {
690
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint '%s' not found – it must reside under /Game/Blueprints"), *BlueprintName));
691
+ }
692
+
693
+ UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *AssetPath);
694
+ if (!Blueprint)
695
+ {
696
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Blueprint not found: %s"), *BlueprintName));
697
+ }
698
+
699
+ // Get transform parameters
700
+ FVector Location(0.0f, 0.0f, 0.0f);
701
+ FRotator Rotation(0.0f, 0.0f, 0.0f);
702
+ FVector Scale(1.0f, 1.0f, 1.0f);
703
+
704
+ if (Params->HasField(TEXT("location")))
705
+ {
706
+ Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
707
+ }
708
+ if (Params->HasField(TEXT("rotation")))
709
+ {
710
+ Rotation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("rotation"));
711
+ }
712
+ if (Params->HasField(TEXT("scale")))
713
+ {
714
+ Scale = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("scale"));
715
+ }
716
+
717
+ // Spawn the actor
718
+ UWorld* World = GEditor->GetEditorWorldContext().World();
719
+ if (!World)
720
+ {
721
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get editor world"));
722
+ }
723
+
724
+ FTransform SpawnTransform;
725
+ SpawnTransform.SetLocation(Location);
726
+ SpawnTransform.SetRotation(FQuat(Rotation));
727
+ SpawnTransform.SetScale3D(Scale);
728
+
729
+ FActorSpawnParameters SpawnParams;
730
+ SpawnParams.Name = *ActorName;
731
+
732
+ AActor* NewActor = World->SpawnActor<AActor>(Blueprint->GeneratedClass, SpawnTransform, SpawnParams);
733
+ if (NewActor)
734
+ {
735
+ return FUnrealMCPCommonUtils::ActorToJsonObject(NewActor, true);
736
+ }
737
+
738
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to spawn blueprint actor"));
739
+ }
740
+
741
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleFocusViewport(const TSharedPtr<FJsonObject>& Params)
742
+ {
743
+ // Get target actor name if provided
744
+ FString TargetActorName;
745
+ bool HasTargetActor = Params->TryGetStringField(TEXT("target"), TargetActorName);
746
+
747
+ // Get location if provided
748
+ FVector Location(0.0f, 0.0f, 0.0f);
749
+ bool HasLocation = false;
750
+ if (Params->HasField(TEXT("location")))
751
+ {
752
+ Location = FUnrealMCPCommonUtils::GetVectorFromJson(Params, TEXT("location"));
753
+ HasLocation = true;
754
+ }
755
+
756
+ // Get distance
757
+ float Distance = 1000.0f;
758
+ if (Params->HasField(TEXT("distance")))
759
+ {
760
+ Distance = Params->GetNumberField(TEXT("distance"));
761
+ }
762
+
763
+ // Get orientation if provided
764
+ FRotator Orientation(0.0f, 0.0f, 0.0f);
765
+ bool HasOrientation = false;
766
+ if (Params->HasField(TEXT("orientation")))
767
+ {
768
+ Orientation = FUnrealMCPCommonUtils::GetRotatorFromJson(Params, TEXT("orientation"));
769
+ HasOrientation = true;
770
+ }
771
+
772
+ // Get the active viewport
773
+ FLevelEditorViewportClient* ViewportClient = (FLevelEditorViewportClient*)GEditor->GetActiveViewport()->GetClient();
774
+ if (!ViewportClient)
775
+ {
776
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to get active viewport"));
777
+ }
778
+
779
+ // If we have a target actor, focus on it
780
+ if (HasTargetActor)
781
+ {
782
+ // Find the actor
783
+ AActor* TargetActor = nullptr;
784
+ TArray<AActor*> AllActors;
785
+ UGameplayStatics::GetAllActorsOfClass(GWorld, AActor::StaticClass(), AllActors);
786
+
787
+ for (AActor* Actor : AllActors)
788
+ {
789
+ if (Actor && Actor->GetName() == TargetActorName)
790
+ {
791
+ TargetActor = Actor;
792
+ break;
793
+ }
794
+ }
795
+
796
+ if (!TargetActor)
797
+ {
798
+ return FUnrealMCPCommonUtils::CreateErrorResponse(FString::Printf(TEXT("Actor not found: %s"), *TargetActorName));
799
+ }
800
+
801
+ // Focus on the actor
802
+ ViewportClient->SetViewLocation(TargetActor->GetActorLocation() - FVector(Distance, 0.0f, 0.0f));
803
+ }
804
+ // Otherwise use the provided location
805
+ else if (HasLocation)
806
+ {
807
+ ViewportClient->SetViewLocation(Location - FVector(Distance, 0.0f, 0.0f));
808
+ }
809
+ else
810
+ {
811
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Either 'target' or 'location' must be provided"));
812
+ }
813
+
814
+ // Set orientation if provided
815
+ if (HasOrientation)
816
+ {
817
+ ViewportClient->SetViewRotation(Orientation);
818
+ }
819
+
820
+ // Force viewport to redraw
821
+ ViewportClient->Invalidate();
822
+
823
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
824
+ ResultObj->SetBoolField(TEXT("success"), true);
825
+ return ResultObj;
826
+ }
827
+
828
+ TSharedPtr<FJsonObject> FUnrealMCPEditorCommands::HandleTakeScreenshot(const TSharedPtr<FJsonObject>& Params)
829
+ {
830
+ // Optional file path parameter.
831
+ // If omitted, save to <Project>/Saved/Screenshots/Flockbay with an auto-generated filename.
832
+ FString FilePath;
833
+ const bool bHasFilePath = Params->TryGetStringField(TEXT("filepath"), FilePath);
834
+
835
+ FString FileName;
836
+ Params->TryGetStringField(TEXT("filename"), FileName);
837
+
838
+ const FString DefaultDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Screenshots"), TEXT("Flockbay"));
839
+ IFileManager::Get().MakeDirectory(*DefaultDir, /*Tree*/ true);
840
+
841
+ if (bHasFilePath)
842
+ {
843
+ FilePath = FilePath.TrimStartAndEnd();
844
+ if (FilePath.IsEmpty())
845
+ {
846
+ FilePath.Reset();
847
+ }
848
+ else if (!FPaths::IsRelative(FilePath))
849
+ {
850
+ // absolute path: use as-is
851
+ }
852
+ else
853
+ {
854
+ // relative path: resolve under our default screenshots folder
855
+ FilePath = FPaths::Combine(DefaultDir, FilePath);
856
+ }
857
+ }
858
+
859
+ if (FilePath.IsEmpty())
860
+ {
861
+ if (FileName.IsEmpty())
862
+ {
863
+ FileName = FString::Printf(TEXT("mcp_screenshot_%s"), *FDateTime::Now().ToString(TEXT("%Y%m%d_%H%M%S")));
864
+ }
865
+ FilePath = FPaths::Combine(DefaultDir, FileName);
866
+ }
867
+
868
+ // Ensure the file path has a proper extension
869
+ if (!FilePath.EndsWith(TEXT(".png")))
870
+ {
871
+ FilePath += TEXT(".png");
872
+ }
873
+
874
+ // Get the active viewport
875
+ if (GEditor && GEditor->GetActiveViewport())
876
+ {
877
+ FViewport* Viewport = GEditor->GetActiveViewport();
878
+ TArray<FColor> Bitmap;
879
+ FIntRect ViewportRect(0, 0, Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y);
880
+
881
+ if (Viewport->ReadPixels(Bitmap, FReadSurfaceDataFlags(), ViewportRect))
882
+ {
883
+ TArray<uint8> CompressedBitmap;
884
+ FImageUtils::CompressImageArray(Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, Bitmap, CompressedBitmap);
885
+
886
+ if (FFileHelper::SaveArrayToFile(CompressedBitmap, *FilePath))
887
+ {
888
+ TSharedPtr<FJsonObject> ResultObj = MakeShared<FJsonObject>();
889
+ ResultObj->SetStringField(TEXT("filepath"), FilePath);
890
+ return ResultObj;
891
+ }
892
+ }
893
+ }
894
+
895
+ return FUnrealMCPCommonUtils::CreateErrorResponse(TEXT("Failed to take screenshot"));
896
+ }