flockbay 0.10.49 → 0.10.52

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 (74) hide show
  1. package/README.md +60 -60
  2. package/bin/flockbay-mcp.mjs +62 -62
  3. package/bin/flockbay.mjs +95 -95
  4. package/dist/{index-r3VTdgFI.cjs → index-IntvnrmZ.cjs} +71 -14
  5. package/dist/{index-D8AeVbvv.mjs → index-wkeSi0iC.mjs} +70 -13
  6. package/dist/index.cjs +2 -2
  7. package/dist/index.mjs +2 -2
  8. package/dist/lib.cjs +1 -1
  9. package/dist/lib.mjs +1 -1
  10. package/dist/{migratePlugin-CEvGPul8.mjs → migratePlugin-C71hAoc5.mjs} +12 -2
  11. package/dist/{migratePlugin-wFK-k3Wk.cjs → migratePlugin-D1XjjWFq.cjs} +12 -2
  12. package/dist/{runCodex-DuyuYqoB.cjs → runCodex-B7i_ICoQ.cjs} +34 -23
  13. package/dist/{runCodex-ZfUyhHF6.mjs → runCodex-BSx-hCu6.mjs} +34 -23
  14. package/dist/{runGemini-DBU1mMdp.mjs → runGemini-Dwyh7_WI.mjs} +2 -2
  15. package/dist/{runGemini-D7j5Y5TF.cjs → runGemini-gBKR3rdL.cjs} +2 -2
  16. package/dist/{types-DNr0xwSy.cjs → types-5z8UADAi.cjs} +224 -15
  17. package/dist/{types-BRJuZQj_.mjs → types-Bgzap7qR.mjs} +224 -16
  18. package/package.json +131 -131
  19. package/scripts/claude_local_launcher.cjs +72 -72
  20. package/scripts/claude_remote_launcher.cjs +15 -15
  21. package/scripts/claude_version_utils.cjs +445 -445
  22. package/scripts/ripgrep_launcher.cjs +32 -32
  23. package/scripts/run-with-root-env.mjs +37 -32
  24. package/scripts/session_hook_forwarder.cjs +49 -49
  25. package/scripts/test-codex-abort-history.mjs +77 -77
  26. package/scripts/unpack-tools.cjs +244 -244
  27. package/tools/licenses/difftastic-LICENSE +21 -21
  28. package/tools/licenses/ripgrep-LICENSE +3 -3
  29. package/tools/unreal-mcp/UPSTREAM_VERSION.md +8 -8
  30. package/tools/unreal-mcp/upstream/Docs/README.md +8 -8
  31. package/tools/unreal-mcp/upstream/Docs/Tools/README.md +6 -6
  32. package/tools/unreal-mcp/upstream/Docs/Tools/actor_tools.md +183 -183
  33. package/tools/unreal-mcp/upstream/Docs/Tools/blueprint_tools.md +268 -268
  34. package/tools/unreal-mcp/upstream/Docs/Tools/editor_tools.md +104 -104
  35. package/tools/unreal-mcp/upstream/Docs/Tools/node_tools.md +274 -274
  36. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Config/FilterPlugin.ini +8 -8
  37. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/FlockbayMCP.uplugin +36 -36
  38. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/FlockbayMCP.Build.cs +82 -82
  39. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPBlueprintCommands.cpp +1364 -1364
  40. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPBlueprintNodeCommands.cpp +1310 -1310
  41. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPCommandSchema.cpp +756 -756
  42. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPCommonUtils.cpp +1177 -1177
  43. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPEditorCommands.cpp +2618 -2618
  44. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPProjectCommands.cpp +71 -71
  45. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/Commands/UnrealMCPUMGCommands.cpp +543 -543
  46. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/MCPServerRunnable.cpp +372 -372
  47. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/UnrealMCPBridge.cpp +457 -457
  48. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Private/UnrealMCPModule.cpp +21 -21
  49. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPBlueprintCommands.h +35 -35
  50. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPBlueprintNodeCommands.h +30 -30
  51. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPCommandSchema.h +41 -41
  52. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPCommonUtils.h +59 -59
  53. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPEditorCommands.h +61 -61
  54. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPProjectCommands.h +20 -20
  55. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/Commands/UnrealMCPUMGCommands.h +82 -82
  56. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/MCPServerRunnable.h +33 -33
  57. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/UnrealMCPBridge.h +64 -64
  58. package/tools/unreal-mcp/upstream/MCPGameProject/Plugins/FlockbayMCP/Source/FlockbayMCP/Public/UnrealMCPModule.h +22 -22
  59. package/tools/unreal-mcp/upstream/Python/README.md +39 -39
  60. package/tools/unreal-mcp/upstream/Python/pyproject.toml +22 -22
  61. package/tools/unreal-mcp/upstream/Python/scripts/actors/test_cube.py +202 -202
  62. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_blueprints_with_different_components.py +496 -496
  63. package/tools/unreal-mcp/upstream/Python/scripts/blueprints/test_create_and_spawn_cube_blueprint.py +193 -193
  64. package/tools/unreal-mcp/upstream/Python/scripts/node/test_component_reference.py +266 -266
  65. package/tools/unreal-mcp/upstream/Python/scripts/node/test_create_bird_blueprint_with_input_and_camera.py +617 -617
  66. package/tools/unreal-mcp/upstream/Python/scripts/node/test_input_mapping.py +365 -365
  67. package/tools/unreal-mcp/upstream/Python/scripts/node/test_physics_variables.py +389 -389
  68. package/tools/unreal-mcp/upstream/Python/tools/blueprint_tools.py +419 -419
  69. package/tools/unreal-mcp/upstream/Python/tools/editor_tools.py +369 -369
  70. package/tools/unreal-mcp/upstream/Python/tools/node_tools.py +429 -429
  71. package/tools/unreal-mcp/upstream/Python/tools/project_tools.py +63 -63
  72. package/tools/unreal-mcp/upstream/Python/tools/umg_tools.py +332 -332
  73. package/tools/unreal-mcp/upstream/Python/unreal_mcp_server.py +398 -398
  74. package/tools/unreal-mcp/upstream/Python/uv.lock +521 -521
@@ -1,457 +1,457 @@
1
- #include "UnrealMCPBridge.h"
2
- #include "MCPServerRunnable.h"
3
- #include "Sockets.h"
4
- #include "SocketSubsystem.h"
5
- #include "HAL/RunnableThread.h"
6
- #include "Interfaces/IPv4/IPv4Address.h"
7
- #include "Interfaces/IPv4/IPv4Endpoint.h"
8
- #include "Dom/JsonObject.h"
9
- #include "Dom/JsonValue.h"
10
- #include "Serialization/JsonSerializer.h"
11
- #include "Serialization/JsonReader.h"
12
- #include "Serialization/JsonWriter.h"
13
- #include "Engine/StaticMeshActor.h"
14
- #include "Engine/DirectionalLight.h"
15
- #include "Engine/PointLight.h"
16
- #include "Engine/SpotLight.h"
17
- #include "Camera/CameraActor.h"
18
- #include "EditorAssetLibrary.h"
19
- #include "AssetRegistry/AssetRegistryModule.h"
20
- #include "JsonObjectConverter.h"
21
- #include "GameFramework/Actor.h"
22
- #include "Engine/Selection.h"
23
- #include "Kismet/GameplayStatics.h"
24
- #include "Async/Async.h"
25
- // Add Blueprint related includes
26
- #include "Engine/Blueprint.h"
27
- #include "Engine/BlueprintGeneratedClass.h"
28
- #include "Factories/BlueprintFactory.h"
29
- #include "EdGraphSchema_K2.h"
30
- #include "K2Node_Event.h"
31
- #include "K2Node_VariableGet.h"
32
- #include "K2Node_VariableSet.h"
33
- #include "Components/StaticMeshComponent.h"
34
- #include "Components/BoxComponent.h"
35
- #include "Components/SphereComponent.h"
36
- #include "Kismet2/BlueprintEditorUtils.h"
37
- #include "Kismet2/KismetEditorUtilities.h"
38
- // UE5.5 correct includes
39
- #include "Engine/SimpleConstructionScript.h"
40
- #include "Engine/SCS_Node.h"
41
- #include "UObject/Field.h"
42
- #include "UObject/FieldPath.h"
43
- // Blueprint Graph specific includes
44
- #include "EdGraph/EdGraph.h"
45
- #include "EdGraph/EdGraphNode.h"
46
- #include "EdGraph/EdGraphPin.h"
47
- #include "K2Node_CallFunction.h"
48
- #include "K2Node_InputAction.h"
49
- #include "K2Node_Self.h"
50
- #include "GameFramework/InputSettings.h"
51
- #include "EditorSubsystem.h"
52
- #include "Subsystems/EditorActorSubsystem.h"
53
- // Include our new command handler classes
54
- #include "Commands/UnrealMCPEditorCommands.h"
55
- #include "Commands/UnrealMCPBlueprintCommands.h"
56
- #include "Commands/UnrealMCPBlueprintNodeCommands.h"
57
- #include "Commands/UnrealMCPProjectCommands.h"
58
- #include "Commands/UnrealMCPCommonUtils.h"
59
- #include "Commands/UnrealMCPUMGCommands.h"
60
- #include "Commands/UnrealMCPCommandSchema.h"
61
- #include "Interfaces/IPluginManager.h"
62
-
63
- // Default settings
64
- #define MCP_SERVER_HOST "127.0.0.1"
65
- #define MCP_SERVER_PORT 55557
66
-
67
- UUnrealMCPBridge::UUnrealMCPBridge()
68
- {
69
- EditorCommands = MakeShared<FUnrealMCPEditorCommands>();
70
- BlueprintCommands = MakeShared<FUnrealMCPBlueprintCommands>();
71
- BlueprintNodeCommands = MakeShared<FUnrealMCPBlueprintNodeCommands>();
72
- ProjectCommands = MakeShared<FUnrealMCPProjectCommands>();
73
- UMGCommands = MakeShared<FUnrealMCPUMGCommands>();
74
- }
75
-
76
- UUnrealMCPBridge::~UUnrealMCPBridge()
77
- {
78
- EditorCommands.Reset();
79
- BlueprintCommands.Reset();
80
- BlueprintNodeCommands.Reset();
81
- ProjectCommands.Reset();
82
- UMGCommands.Reset();
83
- }
84
-
85
- // Initialize subsystem
86
- void UUnrealMCPBridge::Initialize(FSubsystemCollectionBase& Collection)
87
- {
88
- UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Initializing"));
89
-
90
- bIsRunning = false;
91
- ListenerSocket = nullptr;
92
- ConnectionSocket = nullptr;
93
- ServerThread = nullptr;
94
- Port = MCP_SERVER_PORT;
95
- FIPv4Address::Parse(MCP_SERVER_HOST, ServerAddress);
96
-
97
- // Start the server automatically
98
- StartServer();
99
- }
100
-
101
- // Clean up resources when subsystem is destroyed
102
- void UUnrealMCPBridge::Deinitialize()
103
- {
104
- UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Shutting down"));
105
- StopServer();
106
- }
107
-
108
- // Start the MCP server
109
- void UUnrealMCPBridge::StartServer()
110
- {
111
- if (bIsRunning)
112
- {
113
- UE_LOG(LogTemp, Warning, TEXT("UnrealMCPBridge: Server is already running"));
114
- return;
115
- }
116
-
117
- // Create socket subsystem
118
- ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
119
- if (!SocketSubsystem)
120
- {
121
- UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to get socket subsystem"));
122
- return;
123
- }
124
-
125
- // Create listener socket
126
- TSharedPtr<FSocket> NewListenerSocket = MakeShareable(SocketSubsystem->CreateSocket(NAME_Stream, TEXT("UnrealMCPListener"), false));
127
- if (!NewListenerSocket.IsValid())
128
- {
129
- UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create listener socket"));
130
- return;
131
- }
132
-
133
- // Allow address reuse for quick restarts
134
- NewListenerSocket->SetReuseAddr(true);
135
- NewListenerSocket->SetNonBlocking(true);
136
-
137
- // Bind to address
138
- FIPv4Endpoint Endpoint(ServerAddress, Port);
139
- if (!NewListenerSocket->Bind(*Endpoint.ToInternetAddr()))
140
- {
141
- UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to bind listener socket to %s:%d"), *ServerAddress.ToString(), Port);
142
- return;
143
- }
144
-
145
- // Start listening
146
- if (!NewListenerSocket->Listen(5))
147
- {
148
- UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to start listening"));
149
- return;
150
- }
151
-
152
- ListenerSocket = NewListenerSocket;
153
- bIsRunning = true;
154
- UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server started on %s:%d"), *ServerAddress.ToString(), Port);
155
-
156
- // Start server thread
157
- ServerThread = FRunnableThread::Create(
158
- new FMCPServerRunnable(this, ListenerSocket),
159
- TEXT("UnrealMCPServerThread"),
160
- 0, TPri_Normal
161
- );
162
-
163
- if (!ServerThread)
164
- {
165
- UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create server thread"));
166
- StopServer();
167
- return;
168
- }
169
- }
170
-
171
- // Stop the MCP server
172
- void UUnrealMCPBridge::StopServer()
173
- {
174
- if (!bIsRunning)
175
- {
176
- return;
177
- }
178
-
179
- bIsRunning = false;
180
-
181
- // Clean up thread
182
- if (ServerThread)
183
- {
184
- ServerThread->Kill(true);
185
- delete ServerThread;
186
- ServerThread = nullptr;
187
- }
188
-
189
- // Close sockets
190
- if (ConnectionSocket.IsValid())
191
- {
192
- ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket.Get());
193
- ConnectionSocket.Reset();
194
- }
195
-
196
- if (ListenerSocket.IsValid())
197
- {
198
- ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket.Get());
199
- ListenerSocket.Reset();
200
- }
201
-
202
- UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server stopped"));
203
- }
204
-
205
- // Execute a command received from a client
206
- FString UUnrealMCPBridge::ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
207
- {
208
- UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Executing command: %s"), *CommandType);
209
-
210
- // Create a promise to wait for the result
211
- TPromise<FString> Promise;
212
- TFuture<FString> Future = Promise.GetFuture();
213
-
214
- // Queue execution on Game Thread
215
- AsyncTask(ENamedThreads::GameThread, [this, CommandType, Params, Promise = MoveTemp(Promise)]() mutable
216
- {
217
- TSharedPtr<FJsonObject> ResponseJson = MakeShareable(new FJsonObject);
218
-
219
- try
220
- {
221
- TSharedPtr<FJsonObject> ResultJson;
222
-
223
- // Bridge-level helpers (keep these in the bridge so they always match the running plugin build).
224
- if (CommandType == TEXT("list_capabilities"))
225
- {
226
- ResultJson = FUnrealMCPCommandSchemaRegistry::GetCapabilitiesResponse();
227
- }
228
- else if (CommandType == TEXT("get_command_schema"))
229
- {
230
- FString Requested;
231
- if (Params.IsValid())
232
- {
233
- Params->TryGetStringField(TEXT("command"), Requested);
234
- Params->TryGetStringField(TEXT("type"), Requested);
235
- }
236
- Requested = Requested.TrimStartAndEnd();
237
- ResultJson = FUnrealMCPCommandSchemaRegistry::GetSchemaResponse(Requested);
238
- }
239
- else if (CommandType == TEXT("ping"))
240
- {
241
- ResultJson = MakeShareable(new FJsonObject);
242
- ResultJson->SetStringField(TEXT("message"), TEXT("pong"));
243
- }
244
- else if (CommandType == TEXT("get_plugin_info"))
245
- {
246
- TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("FlockbayMCP"));
247
- if (!Plugin.IsValid())
248
- {
249
- ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
250
- ResponseJson->SetStringField(TEXT("error"), TEXT("Plugin not found: FlockbayMCP"));
251
-
252
- FString ResultString;
253
- TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
254
- FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
255
- Promise.SetValue(ResultString);
256
- return;
257
- }
258
-
259
- const FPluginDescriptor& Descriptor = Plugin->GetDescriptor();
260
-
261
- ResultJson = MakeShareable(new FJsonObject);
262
- ResultJson->SetStringField(TEXT("name"), Plugin->GetName());
263
- ResultJson->SetStringField(TEXT("baseDir"), Plugin->GetBaseDir());
264
- ResultJson->SetStringField(TEXT("friendlyName"), Descriptor.FriendlyName);
265
- ResultJson->SetStringField(TEXT("createdBy"), Descriptor.CreatedBy);
266
- ResultJson->SetStringField(TEXT("versionName"), Descriptor.VersionName);
267
- ResultJson->SetNumberField(TEXT("version"), Descriptor.Version);
268
- ResultJson->SetBoolField(TEXT("installed"), Descriptor.bInstalled);
269
- ResultJson->SetNumberField(TEXT("schemaVersion"), FUnrealMCPCommandSchemaRegistry::SchemaVersion());
270
-
271
- // Keep this list explicit so it always matches the exact build running in the editor.
272
- TArray<TSharedPtr<FJsonValue>> Commands;
273
- for (const FString& Cmd : FUnrealMCPCommandSchemaRegistry::GetSupportedCommands())
274
- {
275
- Commands.Add(MakeShareable(new FJsonValueString(Cmd)));
276
- }
277
-
278
- ResultJson->SetArrayField(TEXT("commands"), Commands);
279
- }
280
- // Editor Commands (including actor manipulation)
281
- else if (CommandType == TEXT("get_actors_in_level") ||
282
- CommandType == TEXT("find_actors_by_name") ||
283
- CommandType == TEXT("spawn_actor") ||
284
- CommandType == TEXT("create_landscape") ||
285
- CommandType == TEXT("create_actor") ||
286
- CommandType == TEXT("delete_actor") ||
287
- CommandType == TEXT("set_actor_transform") ||
288
- CommandType == TEXT("get_actor_properties") ||
289
- CommandType == TEXT("set_actor_property") ||
290
- CommandType == TEXT("spawn_blueprint_actor") ||
291
- CommandType == TEXT("focus_viewport") ||
292
- CommandType == TEXT("take_screenshot") ||
293
- CommandType == TEXT("save_all") ||
294
- CommandType == TEXT("get_play_in_editor_status") ||
295
- CommandType == TEXT("play_in_editor") ||
296
- CommandType == TEXT("play_in_editor_windowed") ||
297
- CommandType == TEXT("stop_play_in_editor") ||
298
- CommandType == TEXT("search_assets") ||
299
- CommandType == TEXT("get_asset_info") ||
300
- CommandType == TEXT("list_asset_packs") ||
301
- CommandType == TEXT("place_asset") ||
302
- CommandType == TEXT("map_check") ||
303
- CommandType == TEXT("get_editor_context") ||
304
- CommandType == TEXT("get_player_context") ||
305
- CommandType == TEXT("raycast_from_camera") ||
306
- CommandType == TEXT("raycast_down") ||
307
- CommandType == TEXT("get_actor_transform") ||
308
- CommandType == TEXT("get_actor_bounds"))
309
- {
310
- TSharedPtr<FJsonObject> NormalizedParams;
311
- TSharedPtr<FJsonObject> SchemaError;
312
- if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
313
- {
314
- ResultJson = SchemaError;
315
- }
316
- else
317
- {
318
- ResultJson = EditorCommands->HandleCommand(CommandType, NormalizedParams);
319
- }
320
- }
321
- // Blueprint Commands
322
- else if (CommandType == TEXT("create_blueprint") ||
323
- CommandType == TEXT("add_component_to_blueprint") ||
324
- CommandType == TEXT("set_component_property") ||
325
- CommandType == TEXT("set_physics_properties") ||
326
- CommandType == TEXT("compile_blueprint") ||
327
- CommandType == TEXT("compile_blueprints_all") ||
328
- CommandType == TEXT("set_blueprint_property") ||
329
- CommandType == TEXT("set_static_mesh_properties") ||
330
- CommandType == TEXT("set_pawn_properties"))
331
- {
332
- TSharedPtr<FJsonObject> NormalizedParams;
333
- TSharedPtr<FJsonObject> SchemaError;
334
- if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
335
- {
336
- ResultJson = SchemaError;
337
- }
338
- else
339
- {
340
- ResultJson = BlueprintCommands->HandleCommand(CommandType, NormalizedParams);
341
- }
342
- }
343
- // Blueprint Node Commands
344
- else if (CommandType == TEXT("connect_blueprint_nodes") ||
345
- CommandType == TEXT("add_blueprint_get_self_component_reference") ||
346
- CommandType == TEXT("add_blueprint_self_reference") ||
347
- CommandType == TEXT("find_blueprint_nodes") ||
348
- CommandType == TEXT("add_blueprint_event_node") ||
349
- CommandType == TEXT("add_blueprint_input_action_node") ||
350
- CommandType == TEXT("add_blueprint_function_node") ||
351
- CommandType == TEXT("add_blueprint_get_component_node") ||
352
- CommandType == TEXT("add_blueprint_variable") ||
353
- CommandType == TEXT("search_blueprint_functions") ||
354
- CommandType == TEXT("resolve_blueprint_function"))
355
- {
356
- TSharedPtr<FJsonObject> NormalizedParams;
357
- TSharedPtr<FJsonObject> SchemaError;
358
- if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
359
- {
360
- ResultJson = SchemaError;
361
- }
362
- else
363
- {
364
- ResultJson = BlueprintNodeCommands->HandleCommand(CommandType, NormalizedParams);
365
- }
366
- }
367
- // Project Commands
368
- else if (CommandType == TEXT("create_input_mapping"))
369
- {
370
- TSharedPtr<FJsonObject> NormalizedParams;
371
- TSharedPtr<FJsonObject> SchemaError;
372
- if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
373
- {
374
- ResultJson = SchemaError;
375
- }
376
- else
377
- {
378
- ResultJson = ProjectCommands->HandleCommand(CommandType, NormalizedParams);
379
- }
380
- }
381
- // UMG Commands
382
- else if (CommandType == TEXT("create_umg_widget_blueprint") ||
383
- CommandType == TEXT("add_text_block_to_widget") ||
384
- CommandType == TEXT("add_button_to_widget") ||
385
- CommandType == TEXT("bind_widget_event") ||
386
- CommandType == TEXT("set_text_block_binding") ||
387
- CommandType == TEXT("add_widget_to_viewport"))
388
- {
389
- TSharedPtr<FJsonObject> NormalizedParams;
390
- TSharedPtr<FJsonObject> SchemaError;
391
- if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
392
- {
393
- ResultJson = SchemaError;
394
- }
395
- else
396
- {
397
- ResultJson = UMGCommands->HandleCommand(CommandType, NormalizedParams);
398
- }
399
- }
400
- else
401
- {
402
- ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
403
- ResponseJson->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command: %s"), *CommandType));
404
-
405
- FString ResultString;
406
- TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
407
- FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
408
- Promise.SetValue(ResultString);
409
- return;
410
- }
411
-
412
- // Check if the result contains an error
413
- bool bSuccess = true;
414
- FString ErrorMessage;
415
-
416
- if (ResultJson->HasField(TEXT("success")))
417
- {
418
- bSuccess = ResultJson->GetBoolField(TEXT("success"));
419
- if (!bSuccess && ResultJson->HasField(TEXT("error")))
420
- {
421
- ErrorMessage = ResultJson->GetStringField(TEXT("error"));
422
- }
423
- }
424
-
425
- if (bSuccess)
426
- {
427
- // Set success status and include the result
428
- ResponseJson->SetStringField(TEXT("status"), TEXT("success"));
429
- ResponseJson->SetObjectField(TEXT("result"), ResultJson);
430
- }
431
- else
432
- {
433
- // Set error status and include the error message
434
- ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
435
- ResponseJson->SetStringField(TEXT("error"), ErrorMessage);
436
-
437
- // Preserve structured error details when present.
438
- if (ResultJson.IsValid() && ResultJson->HasTypedField<EJson::Object>(TEXT("details")))
439
- {
440
- ResponseJson->SetObjectField(TEXT("details"), ResultJson->GetObjectField(TEXT("details")));
441
- }
442
- }
443
- }
444
- catch (const std::exception& e)
445
- {
446
- ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
447
- ResponseJson->SetStringField(TEXT("error"), UTF8_TO_TCHAR(e.what()));
448
- }
449
-
450
- FString ResultString;
451
- TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
452
- FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
453
- Promise.SetValue(ResultString);
454
- });
455
-
456
- return Future.Get();
457
- }
1
+ #include "UnrealMCPBridge.h"
2
+ #include "MCPServerRunnable.h"
3
+ #include "Sockets.h"
4
+ #include "SocketSubsystem.h"
5
+ #include "HAL/RunnableThread.h"
6
+ #include "Interfaces/IPv4/IPv4Address.h"
7
+ #include "Interfaces/IPv4/IPv4Endpoint.h"
8
+ #include "Dom/JsonObject.h"
9
+ #include "Dom/JsonValue.h"
10
+ #include "Serialization/JsonSerializer.h"
11
+ #include "Serialization/JsonReader.h"
12
+ #include "Serialization/JsonWriter.h"
13
+ #include "Engine/StaticMeshActor.h"
14
+ #include "Engine/DirectionalLight.h"
15
+ #include "Engine/PointLight.h"
16
+ #include "Engine/SpotLight.h"
17
+ #include "Camera/CameraActor.h"
18
+ #include "EditorAssetLibrary.h"
19
+ #include "AssetRegistry/AssetRegistryModule.h"
20
+ #include "JsonObjectConverter.h"
21
+ #include "GameFramework/Actor.h"
22
+ #include "Engine/Selection.h"
23
+ #include "Kismet/GameplayStatics.h"
24
+ #include "Async/Async.h"
25
+ // Add Blueprint related includes
26
+ #include "Engine/Blueprint.h"
27
+ #include "Engine/BlueprintGeneratedClass.h"
28
+ #include "Factories/BlueprintFactory.h"
29
+ #include "EdGraphSchema_K2.h"
30
+ #include "K2Node_Event.h"
31
+ #include "K2Node_VariableGet.h"
32
+ #include "K2Node_VariableSet.h"
33
+ #include "Components/StaticMeshComponent.h"
34
+ #include "Components/BoxComponent.h"
35
+ #include "Components/SphereComponent.h"
36
+ #include "Kismet2/BlueprintEditorUtils.h"
37
+ #include "Kismet2/KismetEditorUtilities.h"
38
+ // UE5.5 correct includes
39
+ #include "Engine/SimpleConstructionScript.h"
40
+ #include "Engine/SCS_Node.h"
41
+ #include "UObject/Field.h"
42
+ #include "UObject/FieldPath.h"
43
+ // Blueprint Graph specific includes
44
+ #include "EdGraph/EdGraph.h"
45
+ #include "EdGraph/EdGraphNode.h"
46
+ #include "EdGraph/EdGraphPin.h"
47
+ #include "K2Node_CallFunction.h"
48
+ #include "K2Node_InputAction.h"
49
+ #include "K2Node_Self.h"
50
+ #include "GameFramework/InputSettings.h"
51
+ #include "EditorSubsystem.h"
52
+ #include "Subsystems/EditorActorSubsystem.h"
53
+ // Include our new command handler classes
54
+ #include "Commands/UnrealMCPEditorCommands.h"
55
+ #include "Commands/UnrealMCPBlueprintCommands.h"
56
+ #include "Commands/UnrealMCPBlueprintNodeCommands.h"
57
+ #include "Commands/UnrealMCPProjectCommands.h"
58
+ #include "Commands/UnrealMCPCommonUtils.h"
59
+ #include "Commands/UnrealMCPUMGCommands.h"
60
+ #include "Commands/UnrealMCPCommandSchema.h"
61
+ #include "Interfaces/IPluginManager.h"
62
+
63
+ // Default settings
64
+ #define MCP_SERVER_HOST "127.0.0.1"
65
+ #define MCP_SERVER_PORT 55557
66
+
67
+ UUnrealMCPBridge::UUnrealMCPBridge()
68
+ {
69
+ EditorCommands = MakeShared<FUnrealMCPEditorCommands>();
70
+ BlueprintCommands = MakeShared<FUnrealMCPBlueprintCommands>();
71
+ BlueprintNodeCommands = MakeShared<FUnrealMCPBlueprintNodeCommands>();
72
+ ProjectCommands = MakeShared<FUnrealMCPProjectCommands>();
73
+ UMGCommands = MakeShared<FUnrealMCPUMGCommands>();
74
+ }
75
+
76
+ UUnrealMCPBridge::~UUnrealMCPBridge()
77
+ {
78
+ EditorCommands.Reset();
79
+ BlueprintCommands.Reset();
80
+ BlueprintNodeCommands.Reset();
81
+ ProjectCommands.Reset();
82
+ UMGCommands.Reset();
83
+ }
84
+
85
+ // Initialize subsystem
86
+ void UUnrealMCPBridge::Initialize(FSubsystemCollectionBase& Collection)
87
+ {
88
+ UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Initializing"));
89
+
90
+ bIsRunning = false;
91
+ ListenerSocket = nullptr;
92
+ ConnectionSocket = nullptr;
93
+ ServerThread = nullptr;
94
+ Port = MCP_SERVER_PORT;
95
+ FIPv4Address::Parse(MCP_SERVER_HOST, ServerAddress);
96
+
97
+ // Start the server automatically
98
+ StartServer();
99
+ }
100
+
101
+ // Clean up resources when subsystem is destroyed
102
+ void UUnrealMCPBridge::Deinitialize()
103
+ {
104
+ UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Shutting down"));
105
+ StopServer();
106
+ }
107
+
108
+ // Start the MCP server
109
+ void UUnrealMCPBridge::StartServer()
110
+ {
111
+ if (bIsRunning)
112
+ {
113
+ UE_LOG(LogTemp, Warning, TEXT("UnrealMCPBridge: Server is already running"));
114
+ return;
115
+ }
116
+
117
+ // Create socket subsystem
118
+ ISocketSubsystem* SocketSubsystem = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
119
+ if (!SocketSubsystem)
120
+ {
121
+ UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to get socket subsystem"));
122
+ return;
123
+ }
124
+
125
+ // Create listener socket
126
+ TSharedPtr<FSocket> NewListenerSocket = MakeShareable(SocketSubsystem->CreateSocket(NAME_Stream, TEXT("UnrealMCPListener"), false));
127
+ if (!NewListenerSocket.IsValid())
128
+ {
129
+ UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create listener socket"));
130
+ return;
131
+ }
132
+
133
+ // Allow address reuse for quick restarts
134
+ NewListenerSocket->SetReuseAddr(true);
135
+ NewListenerSocket->SetNonBlocking(true);
136
+
137
+ // Bind to address
138
+ FIPv4Endpoint Endpoint(ServerAddress, Port);
139
+ if (!NewListenerSocket->Bind(*Endpoint.ToInternetAddr()))
140
+ {
141
+ UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to bind listener socket to %s:%d"), *ServerAddress.ToString(), Port);
142
+ return;
143
+ }
144
+
145
+ // Start listening
146
+ if (!NewListenerSocket->Listen(5))
147
+ {
148
+ UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to start listening"));
149
+ return;
150
+ }
151
+
152
+ ListenerSocket = NewListenerSocket;
153
+ bIsRunning = true;
154
+ UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server started on %s:%d"), *ServerAddress.ToString(), Port);
155
+
156
+ // Start server thread
157
+ ServerThread = FRunnableThread::Create(
158
+ new FMCPServerRunnable(this, ListenerSocket),
159
+ TEXT("UnrealMCPServerThread"),
160
+ 0, TPri_Normal
161
+ );
162
+
163
+ if (!ServerThread)
164
+ {
165
+ UE_LOG(LogTemp, Error, TEXT("UnrealMCPBridge: Failed to create server thread"));
166
+ StopServer();
167
+ return;
168
+ }
169
+ }
170
+
171
+ // Stop the MCP server
172
+ void UUnrealMCPBridge::StopServer()
173
+ {
174
+ if (!bIsRunning)
175
+ {
176
+ return;
177
+ }
178
+
179
+ bIsRunning = false;
180
+
181
+ // Clean up thread
182
+ if (ServerThread)
183
+ {
184
+ ServerThread->Kill(true);
185
+ delete ServerThread;
186
+ ServerThread = nullptr;
187
+ }
188
+
189
+ // Close sockets
190
+ if (ConnectionSocket.IsValid())
191
+ {
192
+ ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ConnectionSocket.Get());
193
+ ConnectionSocket.Reset();
194
+ }
195
+
196
+ if (ListenerSocket.IsValid())
197
+ {
198
+ ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket.Get());
199
+ ListenerSocket.Reset();
200
+ }
201
+
202
+ UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Server stopped"));
203
+ }
204
+
205
+ // Execute a command received from a client
206
+ FString UUnrealMCPBridge::ExecuteCommand(const FString& CommandType, const TSharedPtr<FJsonObject>& Params)
207
+ {
208
+ UE_LOG(LogTemp, Display, TEXT("UnrealMCPBridge: Executing command: %s"), *CommandType);
209
+
210
+ // Create a promise to wait for the result
211
+ TPromise<FString> Promise;
212
+ TFuture<FString> Future = Promise.GetFuture();
213
+
214
+ // Queue execution on Game Thread
215
+ AsyncTask(ENamedThreads::GameThread, [this, CommandType, Params, Promise = MoveTemp(Promise)]() mutable
216
+ {
217
+ TSharedPtr<FJsonObject> ResponseJson = MakeShareable(new FJsonObject);
218
+
219
+ try
220
+ {
221
+ TSharedPtr<FJsonObject> ResultJson;
222
+
223
+ // Bridge-level helpers (keep these in the bridge so they always match the running plugin build).
224
+ if (CommandType == TEXT("list_capabilities"))
225
+ {
226
+ ResultJson = FUnrealMCPCommandSchemaRegistry::GetCapabilitiesResponse();
227
+ }
228
+ else if (CommandType == TEXT("get_command_schema"))
229
+ {
230
+ FString Requested;
231
+ if (Params.IsValid())
232
+ {
233
+ Params->TryGetStringField(TEXT("command"), Requested);
234
+ Params->TryGetStringField(TEXT("type"), Requested);
235
+ }
236
+ Requested = Requested.TrimStartAndEnd();
237
+ ResultJson = FUnrealMCPCommandSchemaRegistry::GetSchemaResponse(Requested);
238
+ }
239
+ else if (CommandType == TEXT("ping"))
240
+ {
241
+ ResultJson = MakeShareable(new FJsonObject);
242
+ ResultJson->SetStringField(TEXT("message"), TEXT("pong"));
243
+ }
244
+ else if (CommandType == TEXT("get_plugin_info"))
245
+ {
246
+ TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(TEXT("FlockbayMCP"));
247
+ if (!Plugin.IsValid())
248
+ {
249
+ ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
250
+ ResponseJson->SetStringField(TEXT("error"), TEXT("Plugin not found: FlockbayMCP"));
251
+
252
+ FString ResultString;
253
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
254
+ FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
255
+ Promise.SetValue(ResultString);
256
+ return;
257
+ }
258
+
259
+ const FPluginDescriptor& Descriptor = Plugin->GetDescriptor();
260
+
261
+ ResultJson = MakeShareable(new FJsonObject);
262
+ ResultJson->SetStringField(TEXT("name"), Plugin->GetName());
263
+ ResultJson->SetStringField(TEXT("baseDir"), Plugin->GetBaseDir());
264
+ ResultJson->SetStringField(TEXT("friendlyName"), Descriptor.FriendlyName);
265
+ ResultJson->SetStringField(TEXT("createdBy"), Descriptor.CreatedBy);
266
+ ResultJson->SetStringField(TEXT("versionName"), Descriptor.VersionName);
267
+ ResultJson->SetNumberField(TEXT("version"), Descriptor.Version);
268
+ ResultJson->SetBoolField(TEXT("installed"), Descriptor.bInstalled);
269
+ ResultJson->SetNumberField(TEXT("schemaVersion"), FUnrealMCPCommandSchemaRegistry::SchemaVersion());
270
+
271
+ // Keep this list explicit so it always matches the exact build running in the editor.
272
+ TArray<TSharedPtr<FJsonValue>> Commands;
273
+ for (const FString& Cmd : FUnrealMCPCommandSchemaRegistry::GetSupportedCommands())
274
+ {
275
+ Commands.Add(MakeShareable(new FJsonValueString(Cmd)));
276
+ }
277
+
278
+ ResultJson->SetArrayField(TEXT("commands"), Commands);
279
+ }
280
+ // Editor Commands (including actor manipulation)
281
+ else if (CommandType == TEXT("get_actors_in_level") ||
282
+ CommandType == TEXT("find_actors_by_name") ||
283
+ CommandType == TEXT("spawn_actor") ||
284
+ CommandType == TEXT("create_landscape") ||
285
+ CommandType == TEXT("create_actor") ||
286
+ CommandType == TEXT("delete_actor") ||
287
+ CommandType == TEXT("set_actor_transform") ||
288
+ CommandType == TEXT("get_actor_properties") ||
289
+ CommandType == TEXT("set_actor_property") ||
290
+ CommandType == TEXT("spawn_blueprint_actor") ||
291
+ CommandType == TEXT("focus_viewport") ||
292
+ CommandType == TEXT("take_screenshot") ||
293
+ CommandType == TEXT("save_all") ||
294
+ CommandType == TEXT("get_play_in_editor_status") ||
295
+ CommandType == TEXT("play_in_editor") ||
296
+ CommandType == TEXT("play_in_editor_windowed") ||
297
+ CommandType == TEXT("stop_play_in_editor") ||
298
+ CommandType == TEXT("search_assets") ||
299
+ CommandType == TEXT("get_asset_info") ||
300
+ CommandType == TEXT("list_asset_packs") ||
301
+ CommandType == TEXT("place_asset") ||
302
+ CommandType == TEXT("map_check") ||
303
+ CommandType == TEXT("get_editor_context") ||
304
+ CommandType == TEXT("get_player_context") ||
305
+ CommandType == TEXT("raycast_from_camera") ||
306
+ CommandType == TEXT("raycast_down") ||
307
+ CommandType == TEXT("get_actor_transform") ||
308
+ CommandType == TEXT("get_actor_bounds"))
309
+ {
310
+ TSharedPtr<FJsonObject> NormalizedParams;
311
+ TSharedPtr<FJsonObject> SchemaError;
312
+ if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
313
+ {
314
+ ResultJson = SchemaError;
315
+ }
316
+ else
317
+ {
318
+ ResultJson = EditorCommands->HandleCommand(CommandType, NormalizedParams);
319
+ }
320
+ }
321
+ // Blueprint Commands
322
+ else if (CommandType == TEXT("create_blueprint") ||
323
+ CommandType == TEXT("add_component_to_blueprint") ||
324
+ CommandType == TEXT("set_component_property") ||
325
+ CommandType == TEXT("set_physics_properties") ||
326
+ CommandType == TEXT("compile_blueprint") ||
327
+ CommandType == TEXT("compile_blueprints_all") ||
328
+ CommandType == TEXT("set_blueprint_property") ||
329
+ CommandType == TEXT("set_static_mesh_properties") ||
330
+ CommandType == TEXT("set_pawn_properties"))
331
+ {
332
+ TSharedPtr<FJsonObject> NormalizedParams;
333
+ TSharedPtr<FJsonObject> SchemaError;
334
+ if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
335
+ {
336
+ ResultJson = SchemaError;
337
+ }
338
+ else
339
+ {
340
+ ResultJson = BlueprintCommands->HandleCommand(CommandType, NormalizedParams);
341
+ }
342
+ }
343
+ // Blueprint Node Commands
344
+ else if (CommandType == TEXT("connect_blueprint_nodes") ||
345
+ CommandType == TEXT("add_blueprint_get_self_component_reference") ||
346
+ CommandType == TEXT("add_blueprint_self_reference") ||
347
+ CommandType == TEXT("find_blueprint_nodes") ||
348
+ CommandType == TEXT("add_blueprint_event_node") ||
349
+ CommandType == TEXT("add_blueprint_input_action_node") ||
350
+ CommandType == TEXT("add_blueprint_function_node") ||
351
+ CommandType == TEXT("add_blueprint_get_component_node") ||
352
+ CommandType == TEXT("add_blueprint_variable") ||
353
+ CommandType == TEXT("search_blueprint_functions") ||
354
+ CommandType == TEXT("resolve_blueprint_function"))
355
+ {
356
+ TSharedPtr<FJsonObject> NormalizedParams;
357
+ TSharedPtr<FJsonObject> SchemaError;
358
+ if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
359
+ {
360
+ ResultJson = SchemaError;
361
+ }
362
+ else
363
+ {
364
+ ResultJson = BlueprintNodeCommands->HandleCommand(CommandType, NormalizedParams);
365
+ }
366
+ }
367
+ // Project Commands
368
+ else if (CommandType == TEXT("create_input_mapping"))
369
+ {
370
+ TSharedPtr<FJsonObject> NormalizedParams;
371
+ TSharedPtr<FJsonObject> SchemaError;
372
+ if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
373
+ {
374
+ ResultJson = SchemaError;
375
+ }
376
+ else
377
+ {
378
+ ResultJson = ProjectCommands->HandleCommand(CommandType, NormalizedParams);
379
+ }
380
+ }
381
+ // UMG Commands
382
+ else if (CommandType == TEXT("create_umg_widget_blueprint") ||
383
+ CommandType == TEXT("add_text_block_to_widget") ||
384
+ CommandType == TEXT("add_button_to_widget") ||
385
+ CommandType == TEXT("bind_widget_event") ||
386
+ CommandType == TEXT("set_text_block_binding") ||
387
+ CommandType == TEXT("add_widget_to_viewport"))
388
+ {
389
+ TSharedPtr<FJsonObject> NormalizedParams;
390
+ TSharedPtr<FJsonObject> SchemaError;
391
+ if (!FUnrealMCPCommandSchemaRegistry::NormalizeParams(CommandType, Params, NormalizedParams, SchemaError))
392
+ {
393
+ ResultJson = SchemaError;
394
+ }
395
+ else
396
+ {
397
+ ResultJson = UMGCommands->HandleCommand(CommandType, NormalizedParams);
398
+ }
399
+ }
400
+ else
401
+ {
402
+ ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
403
+ ResponseJson->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown command: %s"), *CommandType));
404
+
405
+ FString ResultString;
406
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
407
+ FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
408
+ Promise.SetValue(ResultString);
409
+ return;
410
+ }
411
+
412
+ // Check if the result contains an error
413
+ bool bSuccess = true;
414
+ FString ErrorMessage;
415
+
416
+ if (ResultJson->HasField(TEXT("success")))
417
+ {
418
+ bSuccess = ResultJson->GetBoolField(TEXT("success"));
419
+ if (!bSuccess && ResultJson->HasField(TEXT("error")))
420
+ {
421
+ ErrorMessage = ResultJson->GetStringField(TEXT("error"));
422
+ }
423
+ }
424
+
425
+ if (bSuccess)
426
+ {
427
+ // Set success status and include the result
428
+ ResponseJson->SetStringField(TEXT("status"), TEXT("success"));
429
+ ResponseJson->SetObjectField(TEXT("result"), ResultJson);
430
+ }
431
+ else
432
+ {
433
+ // Set error status and include the error message
434
+ ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
435
+ ResponseJson->SetStringField(TEXT("error"), ErrorMessage);
436
+
437
+ // Preserve structured error details when present.
438
+ if (ResultJson.IsValid() && ResultJson->HasTypedField<EJson::Object>(TEXT("details")))
439
+ {
440
+ ResponseJson->SetObjectField(TEXT("details"), ResultJson->GetObjectField(TEXT("details")));
441
+ }
442
+ }
443
+ }
444
+ catch (const std::exception& e)
445
+ {
446
+ ResponseJson->SetStringField(TEXT("status"), TEXT("error"));
447
+ ResponseJson->SetStringField(TEXT("error"), UTF8_TO_TCHAR(e.what()));
448
+ }
449
+
450
+ FString ResultString;
451
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&ResultString);
452
+ FJsonSerializer::Serialize(ResponseJson.ToSharedRef(), Writer);
453
+ Promise.SetValue(ResultString);
454
+ });
455
+
456
+ return Future.Get();
457
+ }