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,372 +1,372 @@
1
- #include "MCPServerRunnable.h"
2
- #include "UnrealMCPBridge.h"
3
- #include "Sockets.h"
4
- #include "SocketSubsystem.h"
5
- #include "Interfaces/IPv4/IPv4Address.h"
6
- #include "Dom/JsonObject.h"
7
- #include "Dom/JsonValue.h"
8
- #include "Serialization/JsonSerializer.h"
9
- #include "Serialization/JsonReader.h"
10
- #include "JsonObjectConverter.h"
11
- #include "Misc/ScopeLock.h"
12
- #include "HAL/PlatformTime.h"
13
-
14
- // Buffer size for receiving data
15
- const int32 RecvBufferSizeBytes = 8192;
16
-
17
- static bool SendAll(FSocket* Socket, const uint8* Data, int32 TotalBytes, int32& OutTotalBytesSent)
18
- {
19
- OutTotalBytesSent = 0;
20
- if (!Socket || !Data || TotalBytes <= 0)
21
- {
22
- return false;
23
- }
24
-
25
- int32 RetryCount = 0;
26
- while (OutTotalBytesSent < TotalBytes)
27
- {
28
- int32 BytesSentNow = 0;
29
- const int32 Remaining = TotalBytes - OutTotalBytesSent;
30
- if (Socket->Send(Data + OutTotalBytesSent, Remaining, BytesSentNow))
31
- {
32
- if (BytesSentNow <= 0)
33
- {
34
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket send returned %d bytes (unexpected)."), BytesSentNow);
35
- return false;
36
- }
37
-
38
- OutTotalBytesSent += BytesSentNow;
39
- RetryCount = 0;
40
- continue;
41
- }
42
-
43
- const int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
44
- if (LastError == SE_EWOULDBLOCK || LastError == SE_EINTR)
45
- {
46
- RetryCount++;
47
- if (RetryCount > 200)
48
- {
49
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: SendAll exceeded retry budget (lastError=%d, sent=%d/%d)."), LastError, OutTotalBytesSent, TotalBytes);
50
- return false;
51
- }
52
- FPlatformProcess::Sleep(0.01f);
53
- continue;
54
- }
55
-
56
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response chunk (lastError=%d, sent=%d/%d)."), LastError, OutTotalBytesSent, TotalBytes);
57
- return false;
58
- }
59
-
60
- return true;
61
- }
62
-
63
- FMCPServerRunnable::FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket)
64
- : Bridge(InBridge)
65
- , ListenerSocket(InListenerSocket)
66
- , bRunning(true)
67
- {
68
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Created server runnable"));
69
- }
70
-
71
- FMCPServerRunnable::~FMCPServerRunnable()
72
- {
73
- // Note: We don't delete the sockets here as they're owned by the bridge
74
- }
75
-
76
- bool FMCPServerRunnable::Init()
77
- {
78
- return true;
79
- }
80
-
81
- uint32 FMCPServerRunnable::Run()
82
- {
83
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread starting..."));
84
-
85
- while (bRunning)
86
- {
87
- // UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Waiting for client connection..."));
88
-
89
- bool bPending = false;
90
- if (ListenerSocket->HasPendingConnection(bPending) && bPending)
91
- {
92
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection pending, accepting..."));
93
-
94
- ClientSocket = MakeShareable(ListenerSocket->Accept(TEXT("MCPClient")));
95
- if (ClientSocket.IsValid())
96
- {
97
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection accepted"));
98
-
99
- // Set socket options to improve connection stability
100
- ClientSocket->SetNoDelay(true);
101
- int32 SocketBufferSize = 65536; // 64KB buffer
102
- ClientSocket->SetSendBufferSize(SocketBufferSize, SocketBufferSize);
103
- ClientSocket->SetReceiveBufferSize(SocketBufferSize, SocketBufferSize);
104
-
105
- uint8 Buffer[RecvBufferSizeBytes + 1];
106
- while (bRunning)
107
- {
108
- int32 BytesRead = 0;
109
- if (ClientSocket->Recv(Buffer, sizeof(Buffer), BytesRead))
110
- {
111
- if (BytesRead == 0)
112
- {
113
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client disconnected (zero bytes)"));
114
- break;
115
- }
116
-
117
- // Convert received data to string
118
- // Null-terminate safely (Recv can fill the entire buffer).
119
- const int32 SafeIndex = FMath::Clamp(BytesRead, 0, RecvBufferSizeBytes);
120
- Buffer[SafeIndex] = '\0';
121
- FString ReceivedText = UTF8_TO_TCHAR(Buffer);
122
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received: %s"), *ReceivedText);
123
-
124
- // Parse JSON
125
- TSharedPtr<FJsonObject> JsonObject;
126
- TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ReceivedText);
127
-
128
- if (FJsonSerializer::Deserialize(Reader, JsonObject))
129
- {
130
- // Get command type
131
- FString CommandType;
132
- if (JsonObject->TryGetStringField(TEXT("type"), CommandType))
133
- {
134
- // Execute command
135
- FString Response = Bridge->ExecuteCommand(CommandType, JsonObject->GetObjectField(TEXT("params")));
136
-
137
- // Log response for debugging
138
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
139
-
140
- // Send response
141
- FTCHARToUTF8 Utf8(*Response);
142
- const uint8* Data = reinterpret_cast<const uint8*>(Utf8.Get());
143
- const int32 TotalBytes = Utf8.Length();
144
- int32 TotalBytesSent = 0;
145
- if (!SendAll(ClientSocket.Get(), Data, TotalBytes, TotalBytesSent))
146
- {
147
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response"));
148
- }
149
- else {
150
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Response sent successfully, bytes: %d"), TotalBytesSent);
151
- }
152
- }
153
- else
154
- {
155
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Missing 'type' field in command"));
156
- }
157
- }
158
- else
159
- {
160
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse JSON from: %s"), *ReceivedText);
161
- }
162
- }
163
- else
164
- {
165
- int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
166
- // Don't break the connection for WouldBlock error, which is normal for non-blocking sockets
167
- bool bShouldBreak = true;
168
-
169
- // Check for "would block" error which isn't a real error for non-blocking sockets
170
- if (LastError == SE_EWOULDBLOCK)
171
- {
172
- UE_LOG(LogTemp, Verbose, TEXT("MCPServerRunnable: Socket would block, continuing..."));
173
- bShouldBreak = false;
174
- // Small sleep to prevent tight loop when no data
175
- FPlatformProcess::Sleep(0.01f);
176
- }
177
- // Check for other transient errors we might want to tolerate
178
- else if (LastError == SE_EINTR) // Interrupted system call
179
- {
180
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket read interrupted, continuing..."));
181
- bShouldBreak = false;
182
- }
183
- else
184
- {
185
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Client disconnected or error. Last error code: %d"), LastError);
186
- }
187
-
188
- if (bShouldBreak)
189
- {
190
- break;
191
- }
192
- }
193
- }
194
- }
195
- else
196
- {
197
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to accept client connection"));
198
- }
199
- }
200
-
201
- // Small sleep to prevent tight loop
202
- FPlatformProcess::Sleep(0.1f);
203
- }
204
-
205
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread stopping"));
206
- return 0;
207
- }
208
-
209
- void FMCPServerRunnable::Stop()
210
- {
211
- bRunning = false;
212
- }
213
-
214
- void FMCPServerRunnable::Exit()
215
- {
216
- }
217
-
218
- void FMCPServerRunnable::HandleClientConnection(TSharedPtr<FSocket> InClientSocket)
219
- {
220
- if (!InClientSocket.IsValid())
221
- {
222
- UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Invalid client socket passed to HandleClientConnection"));
223
- return;
224
- }
225
-
226
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting to handle client connection"));
227
-
228
- // Set socket options for better connection stability
229
- InClientSocket->SetNonBlocking(false);
230
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Set socket to blocking mode"));
231
-
232
- // Properly read full message with timeout
233
- const int32 MaxBufferSize = 4096;
234
- uint8 Buffer[MaxBufferSize];
235
- FString MessageBuffer;
236
-
237
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting message receive loop"));
238
-
239
- while (bRunning && InClientSocket.IsValid())
240
- {
241
- // Log socket state
242
- bool bIsConnected = InClientSocket->GetConnectionState() == SCS_Connected;
243
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Socket state - Connected: %s"),
244
- bIsConnected ? TEXT("true") : TEXT("false"));
245
-
246
- // Log pending data status before receive
247
- uint32 PendingDataSize = 0;
248
- bool HasPendingData = InClientSocket->HasPendingData(PendingDataSize);
249
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Before Recv - HasPendingData=%s, Size=%d"),
250
- HasPendingData ? TEXT("true") : TEXT("false"), PendingDataSize);
251
-
252
- // Try to receive data with timeout
253
- int32 BytesRead = 0;
254
- bool bReadSuccess = false;
255
-
256
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Attempting to receive data..."));
257
- bReadSuccess = InClientSocket->Recv(Buffer, MaxBufferSize, BytesRead, ESocketReceiveFlags::None);
258
-
259
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Recv attempt complete - Success=%s, BytesRead=%d"),
260
- bReadSuccess ? TEXT("true") : TEXT("false"), BytesRead);
261
-
262
- if (BytesRead > 0)
263
- {
264
- // Log raw data for debugging
265
- FString HexData;
266
- for (int32 i = 0; i < FMath::Min(BytesRead, 50); ++i)
267
- {
268
- HexData += FString::Printf(TEXT("%02X "), Buffer[i]);
269
- }
270
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Raw data (first 50 bytes hex): %s%s"),
271
- *HexData, BytesRead > 50 ? TEXT("...") : TEXT(""));
272
-
273
- // Convert and log received data
274
- Buffer[BytesRead] = 0; // Null terminate
275
- FString ReceivedData = UTF8_TO_TCHAR(Buffer);
276
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received data as string: '%s'"), *ReceivedData);
277
-
278
- // Append to message buffer
279
- MessageBuffer.Append(ReceivedData);
280
-
281
- // Process complete messages (messages are terminated with newline)
282
- if (MessageBuffer.Contains(TEXT("\n")))
283
- {
284
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Newline detected in buffer, processing messages"));
285
-
286
- TArray<FString> Messages;
287
- MessageBuffer.ParseIntoArray(Messages, TEXT("\n"), true);
288
-
289
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Found %d message(s) in buffer"), Messages.Num());
290
-
291
- // Process all complete messages
292
- for (int32 i = 0; i < Messages.Num() - 1; ++i)
293
- {
294
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message %d: '%s'"),
295
- i + 1, *Messages[i]);
296
- ProcessMessage(InClientSocket, Messages[i]);
297
- }
298
-
299
- // Keep any incomplete message in the buffer
300
- MessageBuffer = Messages.Last();
301
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Remaining buffer after processing: %s"),
302
- *MessageBuffer);
303
- }
304
- else
305
- {
306
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: No complete message yet (no newline detected)"));
307
- }
308
- }
309
- else if (!bReadSuccess)
310
- {
311
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Connection closed or error occurred - Last error: %d"),
312
- (int32)ISocketSubsystem::Get()->GetLastErrorCode());
313
- break;
314
- }
315
-
316
- // Small sleep to prevent tight loop
317
- FPlatformProcess::Sleep(0.01f);
318
- }
319
-
320
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Exited message receive loop"));
321
- }
322
-
323
- void FMCPServerRunnable::ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message)
324
- {
325
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message: %s"), *Message);
326
-
327
- // Parse message as JSON
328
- TSharedPtr<FJsonObject> JsonMessage;
329
- TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
330
-
331
- if (!FJsonSerializer::Deserialize(Reader, JsonMessage) || !JsonMessage.IsValid())
332
- {
333
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse message as JSON"));
334
- return;
335
- }
336
-
337
- // Extract command type and parameters using MCP protocol format
338
- FString CommandType;
339
- TSharedPtr<FJsonObject> Params = MakeShareable(new FJsonObject());
340
-
341
- if (!JsonMessage->TryGetStringField(TEXT("command"), CommandType))
342
- {
343
- UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Message missing 'command' field"));
344
- return;
345
- }
346
-
347
- // Parameters are optional in MCP protocol
348
- if (JsonMessage->HasField(TEXT("params")))
349
- {
350
- TSharedPtr<FJsonValue> ParamsValue = JsonMessage->TryGetField(TEXT("params"));
351
- if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
352
- {
353
- Params = ParamsValue->AsObject();
354
- }
355
- }
356
-
357
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Executing command: %s"), *CommandType);
358
-
359
- // Execute command
360
- FString Response = Bridge->ExecuteCommand(CommandType, Params);
361
-
362
- // Send response with newline terminator
363
- Response += TEXT("\n");
364
- int32 BytesSent = 0;
365
-
366
- UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
367
-
368
- if (!Client->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
369
- {
370
- UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Failed to send response"));
371
- }
372
- }
1
+ #include "MCPServerRunnable.h"
2
+ #include "UnrealMCPBridge.h"
3
+ #include "Sockets.h"
4
+ #include "SocketSubsystem.h"
5
+ #include "Interfaces/IPv4/IPv4Address.h"
6
+ #include "Dom/JsonObject.h"
7
+ #include "Dom/JsonValue.h"
8
+ #include "Serialization/JsonSerializer.h"
9
+ #include "Serialization/JsonReader.h"
10
+ #include "JsonObjectConverter.h"
11
+ #include "Misc/ScopeLock.h"
12
+ #include "HAL/PlatformTime.h"
13
+
14
+ // Buffer size for receiving data
15
+ const int32 RecvBufferSizeBytes = 8192;
16
+
17
+ static bool SendAll(FSocket* Socket, const uint8* Data, int32 TotalBytes, int32& OutTotalBytesSent)
18
+ {
19
+ OutTotalBytesSent = 0;
20
+ if (!Socket || !Data || TotalBytes <= 0)
21
+ {
22
+ return false;
23
+ }
24
+
25
+ int32 RetryCount = 0;
26
+ while (OutTotalBytesSent < TotalBytes)
27
+ {
28
+ int32 BytesSentNow = 0;
29
+ const int32 Remaining = TotalBytes - OutTotalBytesSent;
30
+ if (Socket->Send(Data + OutTotalBytesSent, Remaining, BytesSentNow))
31
+ {
32
+ if (BytesSentNow <= 0)
33
+ {
34
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket send returned %d bytes (unexpected)."), BytesSentNow);
35
+ return false;
36
+ }
37
+
38
+ OutTotalBytesSent += BytesSentNow;
39
+ RetryCount = 0;
40
+ continue;
41
+ }
42
+
43
+ const int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
44
+ if (LastError == SE_EWOULDBLOCK || LastError == SE_EINTR)
45
+ {
46
+ RetryCount++;
47
+ if (RetryCount > 200)
48
+ {
49
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: SendAll exceeded retry budget (lastError=%d, sent=%d/%d)."), LastError, OutTotalBytesSent, TotalBytes);
50
+ return false;
51
+ }
52
+ FPlatformProcess::Sleep(0.01f);
53
+ continue;
54
+ }
55
+
56
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response chunk (lastError=%d, sent=%d/%d)."), LastError, OutTotalBytesSent, TotalBytes);
57
+ return false;
58
+ }
59
+
60
+ return true;
61
+ }
62
+
63
+ FMCPServerRunnable::FMCPServerRunnable(UUnrealMCPBridge* InBridge, TSharedPtr<FSocket> InListenerSocket)
64
+ : Bridge(InBridge)
65
+ , ListenerSocket(InListenerSocket)
66
+ , bRunning(true)
67
+ {
68
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Created server runnable"));
69
+ }
70
+
71
+ FMCPServerRunnable::~FMCPServerRunnable()
72
+ {
73
+ // Note: We don't delete the sockets here as they're owned by the bridge
74
+ }
75
+
76
+ bool FMCPServerRunnable::Init()
77
+ {
78
+ return true;
79
+ }
80
+
81
+ uint32 FMCPServerRunnable::Run()
82
+ {
83
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread starting..."));
84
+
85
+ while (bRunning)
86
+ {
87
+ // UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Waiting for client connection..."));
88
+
89
+ bool bPending = false;
90
+ if (ListenerSocket->HasPendingConnection(bPending) && bPending)
91
+ {
92
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection pending, accepting..."));
93
+
94
+ ClientSocket = MakeShareable(ListenerSocket->Accept(TEXT("MCPClient")));
95
+ if (ClientSocket.IsValid())
96
+ {
97
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client connection accepted"));
98
+
99
+ // Set socket options to improve connection stability
100
+ ClientSocket->SetNoDelay(true);
101
+ int32 SocketBufferSize = 65536; // 64KB buffer
102
+ ClientSocket->SetSendBufferSize(SocketBufferSize, SocketBufferSize);
103
+ ClientSocket->SetReceiveBufferSize(SocketBufferSize, SocketBufferSize);
104
+
105
+ uint8 Buffer[RecvBufferSizeBytes + 1];
106
+ while (bRunning)
107
+ {
108
+ int32 BytesRead = 0;
109
+ if (ClientSocket->Recv(Buffer, sizeof(Buffer), BytesRead))
110
+ {
111
+ if (BytesRead == 0)
112
+ {
113
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Client disconnected (zero bytes)"));
114
+ break;
115
+ }
116
+
117
+ // Convert received data to string
118
+ // Null-terminate safely (Recv can fill the entire buffer).
119
+ const int32 SafeIndex = FMath::Clamp(BytesRead, 0, RecvBufferSizeBytes);
120
+ Buffer[SafeIndex] = '\0';
121
+ FString ReceivedText = UTF8_TO_TCHAR(Buffer);
122
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received: %s"), *ReceivedText);
123
+
124
+ // Parse JSON
125
+ TSharedPtr<FJsonObject> JsonObject;
126
+ TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ReceivedText);
127
+
128
+ if (FJsonSerializer::Deserialize(Reader, JsonObject))
129
+ {
130
+ // Get command type
131
+ FString CommandType;
132
+ if (JsonObject->TryGetStringField(TEXT("type"), CommandType))
133
+ {
134
+ // Execute command
135
+ FString Response = Bridge->ExecuteCommand(CommandType, JsonObject->GetObjectField(TEXT("params")));
136
+
137
+ // Log response for debugging
138
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
139
+
140
+ // Send response
141
+ FTCHARToUTF8 Utf8(*Response);
142
+ const uint8* Data = reinterpret_cast<const uint8*>(Utf8.Get());
143
+ const int32 TotalBytes = Utf8.Length();
144
+ int32 TotalBytesSent = 0;
145
+ if (!SendAll(ClientSocket.Get(), Data, TotalBytes, TotalBytesSent))
146
+ {
147
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to send response"));
148
+ }
149
+ else {
150
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Response sent successfully, bytes: %d"), TotalBytesSent);
151
+ }
152
+ }
153
+ else
154
+ {
155
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Missing 'type' field in command"));
156
+ }
157
+ }
158
+ else
159
+ {
160
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse JSON from: %s"), *ReceivedText);
161
+ }
162
+ }
163
+ else
164
+ {
165
+ int32 LastError = (int32)ISocketSubsystem::Get()->GetLastErrorCode();
166
+ // Don't break the connection for WouldBlock error, which is normal for non-blocking sockets
167
+ bool bShouldBreak = true;
168
+
169
+ // Check for "would block" error which isn't a real error for non-blocking sockets
170
+ if (LastError == SE_EWOULDBLOCK)
171
+ {
172
+ UE_LOG(LogTemp, Verbose, TEXT("MCPServerRunnable: Socket would block, continuing..."));
173
+ bShouldBreak = false;
174
+ // Small sleep to prevent tight loop when no data
175
+ FPlatformProcess::Sleep(0.01f);
176
+ }
177
+ // Check for other transient errors we might want to tolerate
178
+ else if (LastError == SE_EINTR) // Interrupted system call
179
+ {
180
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Socket read interrupted, continuing..."));
181
+ bShouldBreak = false;
182
+ }
183
+ else
184
+ {
185
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Client disconnected or error. Last error code: %d"), LastError);
186
+ }
187
+
188
+ if (bShouldBreak)
189
+ {
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ }
195
+ else
196
+ {
197
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to accept client connection"));
198
+ }
199
+ }
200
+
201
+ // Small sleep to prevent tight loop
202
+ FPlatformProcess::Sleep(0.1f);
203
+ }
204
+
205
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Server thread stopping"));
206
+ return 0;
207
+ }
208
+
209
+ void FMCPServerRunnable::Stop()
210
+ {
211
+ bRunning = false;
212
+ }
213
+
214
+ void FMCPServerRunnable::Exit()
215
+ {
216
+ }
217
+
218
+ void FMCPServerRunnable::HandleClientConnection(TSharedPtr<FSocket> InClientSocket)
219
+ {
220
+ if (!InClientSocket.IsValid())
221
+ {
222
+ UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Invalid client socket passed to HandleClientConnection"));
223
+ return;
224
+ }
225
+
226
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting to handle client connection"));
227
+
228
+ // Set socket options for better connection stability
229
+ InClientSocket->SetNonBlocking(false);
230
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Set socket to blocking mode"));
231
+
232
+ // Properly read full message with timeout
233
+ const int32 MaxBufferSize = 4096;
234
+ uint8 Buffer[MaxBufferSize];
235
+ FString MessageBuffer;
236
+
237
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Starting message receive loop"));
238
+
239
+ while (bRunning && InClientSocket.IsValid())
240
+ {
241
+ // Log socket state
242
+ bool bIsConnected = InClientSocket->GetConnectionState() == SCS_Connected;
243
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Socket state - Connected: %s"),
244
+ bIsConnected ? TEXT("true") : TEXT("false"));
245
+
246
+ // Log pending data status before receive
247
+ uint32 PendingDataSize = 0;
248
+ bool HasPendingData = InClientSocket->HasPendingData(PendingDataSize);
249
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Before Recv - HasPendingData=%s, Size=%d"),
250
+ HasPendingData ? TEXT("true") : TEXT("false"), PendingDataSize);
251
+
252
+ // Try to receive data with timeout
253
+ int32 BytesRead = 0;
254
+ bool bReadSuccess = false;
255
+
256
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Attempting to receive data..."));
257
+ bReadSuccess = InClientSocket->Recv(Buffer, MaxBufferSize, BytesRead, ESocketReceiveFlags::None);
258
+
259
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Recv attempt complete - Success=%s, BytesRead=%d"),
260
+ bReadSuccess ? TEXT("true") : TEXT("false"), BytesRead);
261
+
262
+ if (BytesRead > 0)
263
+ {
264
+ // Log raw data for debugging
265
+ FString HexData;
266
+ for (int32 i = 0; i < FMath::Min(BytesRead, 50); ++i)
267
+ {
268
+ HexData += FString::Printf(TEXT("%02X "), Buffer[i]);
269
+ }
270
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Raw data (first 50 bytes hex): %s%s"),
271
+ *HexData, BytesRead > 50 ? TEXT("...") : TEXT(""));
272
+
273
+ // Convert and log received data
274
+ Buffer[BytesRead] = 0; // Null terminate
275
+ FString ReceivedData = UTF8_TO_TCHAR(Buffer);
276
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Received data as string: '%s'"), *ReceivedData);
277
+
278
+ // Append to message buffer
279
+ MessageBuffer.Append(ReceivedData);
280
+
281
+ // Process complete messages (messages are terminated with newline)
282
+ if (MessageBuffer.Contains(TEXT("\n")))
283
+ {
284
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Newline detected in buffer, processing messages"));
285
+
286
+ TArray<FString> Messages;
287
+ MessageBuffer.ParseIntoArray(Messages, TEXT("\n"), true);
288
+
289
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Found %d message(s) in buffer"), Messages.Num());
290
+
291
+ // Process all complete messages
292
+ for (int32 i = 0; i < Messages.Num() - 1; ++i)
293
+ {
294
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message %d: '%s'"),
295
+ i + 1, *Messages[i]);
296
+ ProcessMessage(InClientSocket, Messages[i]);
297
+ }
298
+
299
+ // Keep any incomplete message in the buffer
300
+ MessageBuffer = Messages.Last();
301
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Remaining buffer after processing: %s"),
302
+ *MessageBuffer);
303
+ }
304
+ else
305
+ {
306
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: No complete message yet (no newline detected)"));
307
+ }
308
+ }
309
+ else if (!bReadSuccess)
310
+ {
311
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Connection closed or error occurred - Last error: %d"),
312
+ (int32)ISocketSubsystem::Get()->GetLastErrorCode());
313
+ break;
314
+ }
315
+
316
+ // Small sleep to prevent tight loop
317
+ FPlatformProcess::Sleep(0.01f);
318
+ }
319
+
320
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Exited message receive loop"));
321
+ }
322
+
323
+ void FMCPServerRunnable::ProcessMessage(TSharedPtr<FSocket> Client, const FString& Message)
324
+ {
325
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Processing message: %s"), *Message);
326
+
327
+ // Parse message as JSON
328
+ TSharedPtr<FJsonObject> JsonMessage;
329
+ TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
330
+
331
+ if (!FJsonSerializer::Deserialize(Reader, JsonMessage) || !JsonMessage.IsValid())
332
+ {
333
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Failed to parse message as JSON"));
334
+ return;
335
+ }
336
+
337
+ // Extract command type and parameters using MCP protocol format
338
+ FString CommandType;
339
+ TSharedPtr<FJsonObject> Params = MakeShareable(new FJsonObject());
340
+
341
+ if (!JsonMessage->TryGetStringField(TEXT("command"), CommandType))
342
+ {
343
+ UE_LOG(LogTemp, Warning, TEXT("MCPServerRunnable: Message missing 'command' field"));
344
+ return;
345
+ }
346
+
347
+ // Parameters are optional in MCP protocol
348
+ if (JsonMessage->HasField(TEXT("params")))
349
+ {
350
+ TSharedPtr<FJsonValue> ParamsValue = JsonMessage->TryGetField(TEXT("params"));
351
+ if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
352
+ {
353
+ Params = ParamsValue->AsObject();
354
+ }
355
+ }
356
+
357
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Executing command: %s"), *CommandType);
358
+
359
+ // Execute command
360
+ FString Response = Bridge->ExecuteCommand(CommandType, Params);
361
+
362
+ // Send response with newline terminator
363
+ Response += TEXT("\n");
364
+ int32 BytesSent = 0;
365
+
366
+ UE_LOG(LogTemp, Display, TEXT("MCPServerRunnable: Sending response: %s"), *Response);
367
+
368
+ if (!Client->Send((uint8*)TCHAR_TO_UTF8(*Response), Response.Len(), BytesSent))
369
+ {
370
+ UE_LOG(LogTemp, Error, TEXT("MCPServerRunnable: Failed to send response"));
371
+ }
372
+ }