ue-mcp 0.4.0-alpha

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,746 @@
1
+ #include "BridgeServer.h"
2
+ #include "UE_MCP_BridgeModule.h"
3
+ #include "Dom/JsonObject.h"
4
+ #include "Dom/JsonValue.h"
5
+ #include "Serialization/JsonSerializer.h"
6
+ #include "Serialization/JsonWriter.h"
7
+ #include "HAL/PlatformProcess.h"
8
+ #include "Misc/DateTime.h"
9
+ #include "Misc/Timespan.h"
10
+ #include "Async/Async.h"
11
+ #include "Handlers/EditorHandlers.h"
12
+ #include "Handlers/AssetHandlers.h"
13
+ #include "Handlers/BlueprintHandlers.h"
14
+ #include "Handlers/LevelHandlers.h"
15
+ #include "Handlers/ReflectionHandlers.h"
16
+ #include "Handlers/GasHandlers.h"
17
+ #include "Handlers/GameplayHandlers.h"
18
+ #include "Handlers/DialogHandlers.h"
19
+ #include "Handlers/MaterialHandlers.h"
20
+ #include "Handlers/AnimationHandlers.h"
21
+ #include "Handlers/AudioHandlers.h"
22
+ #include "Handlers/WidgetHandlers.h"
23
+ #include "Handlers/FoliageHandlers.h"
24
+ #include "Handlers/LandscapeHandlers.h"
25
+ #include "Handlers/NetworkingHandlers.h"
26
+ #include "Handlers/NiagaraHandlers.h"
27
+ #include "Handlers/PCGHandlers.h"
28
+ #include "Handlers/SequencerHandlers.h"
29
+ #include "Handlers/SplineHandlers.h"
30
+ #include "Handlers/PhysicsHandlers.h"
31
+ #include "Handlers/DemoHandlers.h"
32
+
33
+ // Platform-specific socket includes
34
+ #if PLATFORM_WINDOWS
35
+ #include "Windows/AllowWindowsPlatformTypes.h"
36
+ #include <winsock2.h>
37
+ #include <ws2tcpip.h>
38
+ #include "Windows/HideWindowsPlatformTypes.h"
39
+ #pragma comment(lib, "ws2_32.lib")
40
+ #elif PLATFORM_LINUX || PLATFORM_MAC
41
+ #include <sys/socket.h>
42
+ #include <netinet/in.h>
43
+ #include <arpa/inet.h>
44
+ #include <unistd.h>
45
+ #include <sys/select.h>
46
+ #endif
47
+
48
+ #include "Misc/Base64.h"
49
+ #if PLATFORM_WINDOWS
50
+ #include "Windows/AllowWindowsPlatformTypes.h"
51
+ #include <wincrypt.h>
52
+ #include "Windows/HideWindowsPlatformTypes.h"
53
+ #pragma comment(lib, "advapi32.lib")
54
+ #endif
55
+
56
+ FMCPBridgeServer::FMCPBridgeServer(int32 Port)
57
+ : ServerPort(Port)
58
+ , ServerThread(nullptr)
59
+ , bShouldStop(false)
60
+ , bIsRunning(false)
61
+ , ServerSocket(nullptr)
62
+ {
63
+ // Register core handlers
64
+ FEditorHandlers::RegisterHandlers(HandlerRegistry);
65
+ FAssetHandlers::RegisterHandlers(HandlerRegistry);
66
+ FBlueprintHandlers::RegisterHandlers(HandlerRegistry);
67
+ FLevelHandlers::RegisterHandlers(HandlerRegistry);
68
+ FReflectionHandlers::RegisterHandlers(HandlerRegistry);
69
+ FGasHandlers::RegisterHandlers(HandlerRegistry);
70
+ FGameplayHandlers::RegisterHandlers(HandlerRegistry);
71
+ FDialogHandlers::RegisterHandlers(HandlerRegistry);
72
+ FMaterialHandlers::RegisterHandlers(HandlerRegistry);
73
+ FAnimationHandlers::RegisterHandlers(HandlerRegistry);
74
+ FAudioHandlers::RegisterHandlers(HandlerRegistry);
75
+ FWidgetHandlers::RegisterHandlers(HandlerRegistry);
76
+ FFoliageHandlers::RegisterHandlers(HandlerRegistry);
77
+ FLandscapeHandlers::RegisterHandlers(HandlerRegistry);
78
+ FNetworkingHandlers::RegisterHandlers(HandlerRegistry);
79
+ FNiagaraHandlers::RegisterHandlers(HandlerRegistry);
80
+ FPCGHandlers::RegisterHandlers(HandlerRegistry);
81
+ FSequencerHandlers::RegisterHandlers(HandlerRegistry);
82
+ FSplineHandlers::RegisterHandlers(HandlerRegistry);
83
+ FPhysicsHandlers::RegisterHandlers(HandlerRegistry);
84
+ FDemoHandlers::RegisterHandlers(HandlerRegistry);
85
+ }
86
+
87
+ FMCPBridgeServer::~FMCPBridgeServer()
88
+ {
89
+ Shutdown();
90
+ }
91
+
92
+ bool FMCPBridgeServer::Start()
93
+ {
94
+ if (bIsRunning)
95
+ {
96
+ return false;
97
+ }
98
+
99
+ bShouldStop = false;
100
+ ServerThread = FRunnableThread::Create(this, TEXT("MCPBridgeServer"), 0, TPri_Normal);
101
+ return ServerThread != nullptr;
102
+ }
103
+
104
+ void FMCPBridgeServer::Shutdown()
105
+ {
106
+ if (!bIsRunning)
107
+ {
108
+ return;
109
+ }
110
+
111
+ bShouldStop = true;
112
+
113
+ if (ServerThread)
114
+ {
115
+ ServerThread->WaitForCompletion();
116
+ delete ServerThread;
117
+ ServerThread = nullptr;
118
+ }
119
+
120
+ bIsRunning = false;
121
+ }
122
+
123
+ bool FMCPBridgeServer::Init()
124
+ {
125
+ bIsRunning = true;
126
+ return true;
127
+ }
128
+
129
+ uint32 FMCPBridgeServer::Run()
130
+ {
131
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Bridge server thread started on port %d"), ServerPort);
132
+
133
+ // Initialize platform sockets
134
+ #if PLATFORM_WINDOWS
135
+ WSADATA WsaData;
136
+ if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0)
137
+ {
138
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to initialize Winsock"));
139
+ return 1;
140
+ }
141
+ #endif
142
+
143
+ // Create server socket
144
+ #if PLATFORM_WINDOWS
145
+ SOCKET ServerSocketFD = socket(AF_INET, SOCK_STREAM, 0);
146
+ if (ServerSocketFD == INVALID_SOCKET)
147
+ #else
148
+ int32 ServerSocketFD = socket(AF_INET, SOCK_STREAM, 0);
149
+ if (ServerSocketFD < 0)
150
+ #endif
151
+ {
152
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to create socket"));
153
+ #if PLATFORM_WINDOWS
154
+ WSACleanup();
155
+ #endif
156
+ return 1;
157
+ }
158
+
159
+ // Set socket options
160
+ int32 ReuseAddr = 1;
161
+ setsockopt(ServerSocketFD, SOL_SOCKET, SO_REUSEADDR, (char*)&ReuseAddr, sizeof(ReuseAddr));
162
+
163
+ // Set TCP_NODELAY for immediate send (disable Nagle's algorithm)
164
+ int32 NoDelay = 1;
165
+ setsockopt(ServerSocketFD, IPPROTO_TCP, TCP_NODELAY, (char*)&NoDelay, sizeof(NoDelay));
166
+
167
+ // Bind socket
168
+ sockaddr_in ServerAddr;
169
+ FMemory::Memset(&ServerAddr, 0, sizeof(ServerAddr));
170
+ ServerAddr.sin_family = AF_INET;
171
+ ServerAddr.sin_addr.s_addr = INADDR_ANY;
172
+ ServerAddr.sin_port = htons(ServerPort);
173
+
174
+ if (bind(ServerSocketFD, (sockaddr*)&ServerAddr, sizeof(ServerAddr)) < 0)
175
+ {
176
+ int32 ErrorCode = 0;
177
+ #if PLATFORM_WINDOWS
178
+ ErrorCode = WSAGetLastError();
179
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to bind socket to port %d, error: %d"), ServerPort, ErrorCode);
180
+ closesocket(ServerSocketFD);
181
+ WSACleanup();
182
+ #else
183
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to bind socket to port %d"), ServerPort);
184
+ close(ServerSocketFD);
185
+ #endif
186
+ return 1;
187
+ }
188
+
189
+ // Listen
190
+ if (listen(ServerSocketFD, 5) < 0)
191
+ {
192
+ int32 ErrorCode = 0;
193
+ #if PLATFORM_WINDOWS
194
+ ErrorCode = WSAGetLastError();
195
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to listen on socket, error: %d"), ErrorCode);
196
+ closesocket(ServerSocketFD);
197
+ WSACleanup();
198
+ #else
199
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to listen on socket"));
200
+ close(ServerSocketFD);
201
+ #endif
202
+ return 1;
203
+ }
204
+
205
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Bridge listening on ws://localhost:%d"), ServerPort);
206
+ bIsRunning = true;
207
+
208
+ // Accept connections
209
+ while (!bShouldStop)
210
+ {
211
+ fd_set ReadSet;
212
+ FD_ZERO(&ReadSet);
213
+ FD_SET(ServerSocketFD, &ReadSet);
214
+
215
+ timeval Timeout;
216
+ Timeout.tv_sec = 1;
217
+ Timeout.tv_usec = 0;
218
+
219
+ int32 SelectResult = select(ServerSocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
220
+ #if PLATFORM_WINDOWS
221
+ if (SelectResult > 0 && FD_ISSET(ServerSocketFD, &ReadSet))
222
+ #else
223
+ if (SelectResult > 0 && FD_ISSET(ServerSocketFD, &ReadSet))
224
+ #endif
225
+ {
226
+ sockaddr_in ClientAddr;
227
+ socklen_t ClientAddrLen = sizeof(ClientAddr);
228
+ #if PLATFORM_WINDOWS
229
+ SOCKET ClientSocketFD = accept(ServerSocketFD, (sockaddr*)&ClientAddr, &ClientAddrLen);
230
+ if (ClientSocketFD != INVALID_SOCKET)
231
+ {
232
+ #else
233
+ int32 ClientSocketFD = accept(ServerSocketFD, (sockaddr*)&ClientAddr, &ClientAddrLen);
234
+ if (ClientSocketFD >= 0)
235
+ {
236
+ #endif
237
+ char AddrStr[INET_ADDRSTRLEN];
238
+ inet_ntop(AF_INET, &ClientAddr.sin_addr, AddrStr, INET_ADDRSTRLEN);
239
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Client connected from %s:%d"),
240
+ ANSI_TO_TCHAR(AddrStr), ntohs(ClientAddr.sin_port));
241
+
242
+ // Handle each WebSocket connection in its own thread
243
+ Async(EAsyncExecution::Thread, [this, ClientSocketFD]() {
244
+ HandleWebSocketConnection(ClientSocketFD);
245
+ });
246
+ }
247
+ }
248
+ }
249
+
250
+ // Cleanup
251
+ #if PLATFORM_WINDOWS
252
+ closesocket(ServerSocketFD);
253
+ WSACleanup();
254
+ #else
255
+ close(ServerSocketFD);
256
+ #endif
257
+
258
+ bIsRunning = false;
259
+ return 0;
260
+ }
261
+
262
+ void FMCPBridgeServer::Stop()
263
+ {
264
+ bShouldStop = true;
265
+ }
266
+
267
+ void FMCPBridgeServer::Exit()
268
+ {
269
+ bIsRunning = false;
270
+ }
271
+
272
+ TSharedPtr<FJsonObject> FMCPBridgeServer::ParseJsonRpcRequest(const FString& Message)
273
+ {
274
+ TSharedPtr<FJsonObject> JsonObject;
275
+ TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
276
+
277
+ if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
278
+ {
279
+ return JsonObject;
280
+ }
281
+
282
+ return nullptr;
283
+ }
284
+
285
+ FString FMCPBridgeServer::CreateJsonRpcResponse(const TSharedPtr<FJsonObject>& Request, const TSharedPtr<FJsonValue>& Result)
286
+ {
287
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
288
+ Response->SetStringField(TEXT("jsonrpc"), TEXT("2.0"));
289
+
290
+ if (Request.IsValid() && Request->HasField(TEXT("id")))
291
+ {
292
+ Response->SetField(TEXT("id"), Request->TryGetField(TEXT("id")));
293
+ }
294
+
295
+ Response->SetField(TEXT("result"), Result);
296
+
297
+ FString OutputString;
298
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
299
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
300
+ return OutputString;
301
+ }
302
+
303
+ FString FMCPBridgeServer::CreateJsonRpcError(const TSharedPtr<FJsonObject>& Request, int32 ErrorCode, const FString& ErrorMessage)
304
+ {
305
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
306
+ Response->SetStringField(TEXT("jsonrpc"), TEXT("2.0"));
307
+
308
+ if (Request.IsValid() && Request->HasField(TEXT("id")))
309
+ {
310
+ Response->SetField(TEXT("id"), Request->TryGetField(TEXT("id")));
311
+ }
312
+ else
313
+ {
314
+ Response->SetField(TEXT("id"), MakeShared<FJsonValueNull>());
315
+ }
316
+
317
+ TSharedPtr<FJsonObject> ErrorObject = MakeShared<FJsonObject>();
318
+ ErrorObject->SetNumberField(TEXT("code"), ErrorCode);
319
+ ErrorObject->SetStringField(TEXT("message"), ErrorMessage);
320
+ Response->SetObjectField(TEXT("error"), ErrorObject);
321
+
322
+ FString OutputString;
323
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
324
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
325
+ return OutputString;
326
+ }
327
+
328
+ FString FMCPBridgeServer::ProcessMessage(const FString& Message)
329
+ {
330
+ TSharedPtr<FJsonObject> Request = ParseJsonRpcRequest(Message);
331
+ if (!Request.IsValid())
332
+ {
333
+ return CreateJsonRpcError(nullptr, -32700, TEXT("Parse error"));
334
+ }
335
+
336
+ FString Method;
337
+ if (!Request->TryGetStringField(TEXT("method"), Method))
338
+ {
339
+ return CreateJsonRpcError(Request, -32600, TEXT("Invalid Request"));
340
+ }
341
+
342
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Processing method: %s"), *Method);
343
+
344
+ TSharedPtr<FJsonObject> Params;
345
+ if (Request->HasField(TEXT("params")))
346
+ {
347
+ TSharedPtr<FJsonValue> ParamsValue = Request->TryGetField(TEXT("params"));
348
+ if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
349
+ {
350
+ Params = ParamsValue->AsObject();
351
+ }
352
+ else
353
+ {
354
+ Params = MakeShared<FJsonObject>();
355
+ }
356
+ }
357
+ else
358
+ {
359
+ Params = MakeShared<FJsonObject>();
360
+ }
361
+
362
+ // Execute handler on game thread
363
+ FMCPHandlerRegistry::FHandlerFunction Handler = [this, Method](const TSharedPtr<FJsonObject>& HandlerParams) -> TSharedPtr<FJsonValue>
364
+ {
365
+ return HandlerRegistry.ExecuteHandler(Method, HandlerParams);
366
+ };
367
+
368
+ TSharedPtr<FJsonValue> Result = GameThreadExecutor.ExecuteOnGameThread(Handler, Params);
369
+
370
+ if (Result.IsValid())
371
+ {
372
+ return CreateJsonRpcResponse(Request, Result);
373
+ }
374
+ else
375
+ {
376
+ return CreateJsonRpcError(Request, -32601, FString::Printf(TEXT("Unknown method: %s"), *Method));
377
+ }
378
+ }
379
+
380
+ #if PLATFORM_WINDOWS
381
+ void FMCPBridgeServer::HandleWebSocketConnection(SOCKET ClientSocketFD)
382
+ #else
383
+ void FMCPBridgeServer::HandleWebSocketConnection(int32 ClientSocketFD)
384
+ #endif
385
+ {
386
+ // Set TCP_NODELAY on client socket for immediate send
387
+ int32 NoDelay = 1;
388
+ setsockopt(ClientSocketFD, IPPROTO_TCP, TCP_NODELAY, (char*)&NoDelay, sizeof(NoDelay));
389
+
390
+ // Perform WebSocket handshake
391
+ FString Response = PerformWebSocketHandshake(ClientSocketFD);
392
+ if (Response.IsEmpty())
393
+ {
394
+ #if PLATFORM_WINDOWS
395
+ closesocket(ClientSocketFD);
396
+ #else
397
+ close(ClientSocketFD);
398
+ #endif
399
+ return;
400
+ }
401
+
402
+ // Send handshake response
403
+ // HTTP headers are ASCII, FString uses TCHAR (which is wchar_t on Windows)
404
+ // Convert to UTF-8 bytes for network transmission
405
+ FTCHARToUTF8 UTF8Response(*Response);
406
+ const char* ResponseBytes = (const char*)UTF8Response.Get();
407
+ int32 TotalBytes = UTF8Response.Length();
408
+
409
+ // Send response - ensure all bytes are sent
410
+ int32 SentBytes = 0;
411
+ while (SentBytes < TotalBytes)
412
+ {
413
+ int32 BytesSent = send(ClientSocketFD, ResponseBytes + SentBytes, TotalBytes - SentBytes, 0);
414
+ if (BytesSent < 0)
415
+ {
416
+ int32 ErrorCode = 0;
417
+ #if PLATFORM_WINDOWS
418
+ ErrorCode = WSAGetLastError();
419
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to send WebSocket handshake response, error: %d"), ErrorCode);
420
+ closesocket(ClientSocketFD);
421
+ #else
422
+ UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to send WebSocket handshake response"));
423
+ close(ClientSocketFD);
424
+ #endif
425
+ return;
426
+ }
427
+ SentBytes += BytesSent;
428
+ }
429
+
430
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Sent WebSocket handshake response (%d/%d bytes)"), SentBytes, TotalBytes);
431
+
432
+ // Small delay to ensure response is fully sent and received by client
433
+ FPlatformProcess::Sleep(0.01f); // 10ms
434
+
435
+ // Process WebSocket messages
436
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Starting WebSocket message processing"));
437
+ ProcessWebSocketMessages(ClientSocketFD);
438
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] WebSocket message processing ended"));
439
+
440
+ #if PLATFORM_WINDOWS
441
+ closesocket(ClientSocketFD);
442
+ #else
443
+ close(ClientSocketFD);
444
+ #endif
445
+ }
446
+
447
+ #if PLATFORM_WINDOWS
448
+ FString FMCPBridgeServer::PerformWebSocketHandshake(SOCKET ClientSocketFD)
449
+ #else
450
+ FString FMCPBridgeServer::PerformWebSocketHandshake(int32 ClientSocketFD)
451
+ #endif
452
+ {
453
+ FString Request = ReadHttpRequest(ClientSocketFD);
454
+ if (Request.IsEmpty())
455
+ {
456
+ return TEXT("");
457
+ }
458
+
459
+ // Extract WebSocket-Key from request (case-insensitive search)
460
+ FString WebSocketKey;
461
+ int32 KeyStart = Request.Find(TEXT("Sec-WebSocket-Key:"), ESearchCase::IgnoreCase);
462
+ if (KeyStart != INDEX_NONE)
463
+ {
464
+ // Skip past the header name and any whitespace
465
+ int32 ValueStart = KeyStart + 18; // Length of "Sec-WebSocket-Key:"
466
+ while (ValueStart < Request.Len() && (Request[ValueStart] == TEXT(' ') || Request[ValueStart] == TEXT('\t')))
467
+ {
468
+ ValueStart++;
469
+ }
470
+ int32 KeyEnd = Request.Find(TEXT("\r\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ValueStart);
471
+ if (KeyEnd != INDEX_NONE)
472
+ {
473
+ WebSocketKey = Request.Mid(ValueStart, KeyEnd - ValueStart).TrimStartAndEnd();
474
+ }
475
+ }
476
+
477
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Extracted WebSocket-Key: %s"), *WebSocketKey);
478
+
479
+ if (WebSocketKey.IsEmpty())
480
+ {
481
+ return TEXT("");
482
+ }
483
+
484
+ // Create accept key
485
+ FString AcceptKey = CreateWebSocketAcceptKey(WebSocketKey);
486
+
487
+ // Build response (WebSocket spec requires exact format)
488
+ // Must be: HTTP/1.1 101 Switching Protocols\r\n
489
+ // Upgrade: websocket\r\n
490
+ // Connection: Upgrade\r\n
491
+ // Sec-WebSocket-Accept: <key>\r\n
492
+ // \r\n
493
+ FString Response = TEXT("HTTP/1.1 101 Switching Protocols\r\n");
494
+ Response += TEXT("Upgrade: websocket\r\n");
495
+ Response += TEXT("Connection: Upgrade\r\n");
496
+ Response += FString::Printf(TEXT("Sec-WebSocket-Accept: %s\r\n"), *AcceptKey);
497
+ Response += TEXT("\r\n");
498
+
499
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Accept key: %s"), *AcceptKey);
500
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Response length: %d chars"), Response.Len());
501
+
502
+ return Response;
503
+ }
504
+
505
+ #if PLATFORM_WINDOWS
506
+ FString FMCPBridgeServer::ReadHttpRequest(SOCKET SocketFD)
507
+ #else
508
+ FString FMCPBridgeServer::ReadHttpRequest(int32 SocketFD)
509
+ #endif
510
+ {
511
+ // Read HTTP request headers (until \r\n\r\n)
512
+ FString Request;
513
+ TArray<uint8> Buffer;
514
+ Buffer.SetNum(4096);
515
+
516
+ // Use select to wait for data with timeout
517
+ fd_set ReadSet;
518
+ FD_ZERO(&ReadSet);
519
+ FD_SET(SocketFD, &ReadSet);
520
+
521
+ timeval Timeout;
522
+ Timeout.tv_sec = 5; // 5 second timeout
523
+ Timeout.tv_usec = 0;
524
+
525
+ int32 SelectResult = select(SocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
526
+ if (SelectResult <= 0 || !FD_ISSET(SocketFD, &ReadSet))
527
+ {
528
+ UE_LOG(LogMCPBridge, Warning, TEXT("[UE-MCP] Timeout waiting for HTTP request"));
529
+ return TEXT("");
530
+ }
531
+
532
+ // Read data
533
+ int32 BytesReceived = recv(SocketFD, (char*)Buffer.GetData(), Buffer.Num(), 0);
534
+ if (BytesReceived <= 0)
535
+ {
536
+ UE_LOG(LogMCPBridge, Warning, TEXT("[UE-MCP] Failed to read HTTP request"));
537
+ return TEXT("");
538
+ }
539
+
540
+ Buffer.SetNum(BytesReceived);
541
+ Request = FString(ANSI_TO_TCHAR((char*)Buffer.GetData()));
542
+
543
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Read HTTP request (%d bytes):\n%s"), BytesReceived, *Request.Left(200));
544
+
545
+ return Request;
546
+ }
547
+
548
+ FString FMCPBridgeServer::CreateWebSocketAcceptKey(const FString& ClientKey)
549
+ {
550
+ // WebSocket accept key = base64(sha1(client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
551
+ FString MagicString = TEXT("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
552
+ FString Combined = ClientKey + MagicString;
553
+
554
+ // Compute SHA1 hash (20 bytes) using Windows CryptoAPI
555
+ FTCHARToUTF8 UTF8String(*Combined);
556
+ uint8 HashBytes[20];
557
+
558
+ #if PLATFORM_WINDOWS
559
+ HCRYPTPROV hProv = 0;
560
+ HCRYPTHASH hHash = 0;
561
+ if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
562
+ {
563
+ if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
564
+ {
565
+ CryptHashData(hHash, (BYTE*)UTF8String.Get(), UTF8String.Length(), 0);
566
+ DWORD HashLen = 20;
567
+ CryptGetHashParam(hHash, HP_HASHVAL, HashBytes, &HashLen, 0);
568
+ CryptDestroyHash(hHash);
569
+ }
570
+ CryptReleaseContext(hProv, 0);
571
+ }
572
+ #else
573
+ // Fallback: use a simple hash (not secure, but works for WebSocket handshake)
574
+ uint32 Hash = 0;
575
+ for (int32 i = 0; i < UTF8String.Length(); ++i)
576
+ {
577
+ Hash = Hash * 31 + UTF8String.Get()[i];
578
+ }
579
+ FMemory::Memcpy(HashBytes, &Hash, 20);
580
+ #endif
581
+
582
+ // Base64 encode
583
+ FString AcceptKey = FBase64::Encode(HashBytes, 20);
584
+ return AcceptKey;
585
+ }
586
+
587
+ #if PLATFORM_WINDOWS
588
+ void FMCPBridgeServer::ProcessWebSocketMessages(SOCKET ClientSocketFD)
589
+ #else
590
+ void FMCPBridgeServer::ProcessWebSocketMessages(int32 ClientSocketFD)
591
+ #endif
592
+ {
593
+ constexpr int32 RecvBufferSize = 65536;
594
+ TArray<uint8> Buffer;
595
+ Buffer.SetNumUninitialized(RecvBufferSize);
596
+
597
+ while (!bShouldStop)
598
+ {
599
+ fd_set ReadSet;
600
+ FD_ZERO(&ReadSet);
601
+ FD_SET(ClientSocketFD, &ReadSet);
602
+
603
+ timeval Timeout;
604
+ Timeout.tv_sec = 1;
605
+ Timeout.tv_usec = 0;
606
+
607
+ int32 SelectResult = select(ClientSocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
608
+
609
+ if (SelectResult > 0 && FD_ISSET(ClientSocketFD, &ReadSet))
610
+ {
611
+ int32 BytesReceived = recv(ClientSocketFD, (char*)Buffer.GetData(), RecvBufferSize, 0);
612
+ if (BytesReceived <= 0)
613
+ {
614
+ break;
615
+ }
616
+
617
+ TArray<uint8> FrameData(Buffer.GetData(), BytesReceived);
618
+ FString Message = ParseWebSocketFrame(FrameData);
619
+
620
+ if (!Message.IsEmpty())
621
+ {
622
+ FString Response = ProcessMessage(Message);
623
+ TArray<uint8> ResponseFrame = CreateWebSocketFrame(Response);
624
+ int32 TotalToSend = ResponseFrame.Num();
625
+ int32 Sent = 0;
626
+ while (Sent < TotalToSend)
627
+ {
628
+ int32 BytesSent = send(ClientSocketFD, (char*)ResponseFrame.GetData() + Sent, TotalToSend - Sent, 0);
629
+ if (BytesSent <= 0) break;
630
+ Sent += BytesSent;
631
+ }
632
+ }
633
+ }
634
+ else if (SelectResult < 0)
635
+ {
636
+ break;
637
+ }
638
+ }
639
+ }
640
+
641
+ TArray<uint8> FMCPBridgeServer::CreateWebSocketFrame(const FString& Message)
642
+ {
643
+ // Simple WebSocket frame creation (text frame, no masking)
644
+ TArray<uint8> Frame;
645
+
646
+ // Convert to UTF-8 first to get correct byte length
647
+ FTCHARToUTF8 UTF8String(*Message);
648
+ int32 MessageLen = UTF8String.Length();
649
+
650
+ // Frame header
651
+ uint8 FirstByte = 0x81; // FIN + text frame
652
+ Frame.Add(FirstByte);
653
+
654
+ if (MessageLen < 126)
655
+ {
656
+ Frame.Add(MessageLen);
657
+ }
658
+ else if (MessageLen < 65536)
659
+ {
660
+ Frame.Add(126);
661
+ Frame.Add((MessageLen >> 8) & 0xFF);
662
+ Frame.Add(MessageLen & 0xFF);
663
+ }
664
+ else
665
+ {
666
+ Frame.Add(127);
667
+ for (int32 i = 7; i >= 0; --i)
668
+ {
669
+ Frame.Add((MessageLen >> (i * 8)) & 0xFF);
670
+ }
671
+ }
672
+
673
+ // Message payload (UTF-8 bytes)
674
+ Frame.Append((uint8*)UTF8String.Get(), MessageLen);
675
+
676
+ return Frame;
677
+ }
678
+
679
+ FString FMCPBridgeServer::ParseWebSocketFrame(const TArray<uint8>& Data)
680
+ {
681
+ if (Data.Num() < 2)
682
+ {
683
+ return TEXT("");
684
+ }
685
+
686
+ uint8 FirstByte = Data[0];
687
+ uint8 SecondByte = Data[1];
688
+
689
+ bool bMasked = (SecondByte & 0x80) != 0;
690
+ int32 PayloadLen = SecondByte & 0x7F;
691
+
692
+ int32 HeaderLen = 2;
693
+ if (PayloadLen == 126)
694
+ {
695
+ if (Data.Num() < 4)
696
+ {
697
+ return TEXT("");
698
+ }
699
+ PayloadLen = (Data[2] << 8) | Data[3];
700
+ HeaderLen = 4;
701
+ }
702
+ else if (PayloadLen == 127)
703
+ {
704
+ if (Data.Num() < 10)
705
+ {
706
+ return TEXT("");
707
+ }
708
+ PayloadLen = 0;
709
+ for (int32 i = 0; i < 8; ++i)
710
+ {
711
+ PayloadLen = (PayloadLen << 8) | Data[2 + i];
712
+ }
713
+ HeaderLen = 10;
714
+ }
715
+
716
+ if (bMasked)
717
+ {
718
+ HeaderLen += 4; // Masking key
719
+ }
720
+
721
+ if (Data.Num() < HeaderLen + PayloadLen)
722
+ {
723
+ return TEXT("");
724
+ }
725
+
726
+ TArray<uint8> Payload;
727
+ Payload.Append(Data.GetData() + HeaderLen, PayloadLen);
728
+
729
+ if (bMasked)
730
+ {
731
+ // Unmask payload
732
+ uint8 MaskKey[4];
733
+ MaskKey[0] = Data[HeaderLen - 4];
734
+ MaskKey[1] = Data[HeaderLen - 3];
735
+ MaskKey[2] = Data[HeaderLen - 2];
736
+ MaskKey[3] = Data[HeaderLen - 1];
737
+
738
+ for (int32 i = 0; i < Payload.Num(); ++i)
739
+ {
740
+ Payload[i] ^= MaskKey[i % 4];
741
+ }
742
+ }
743
+
744
+ FUTF8ToTCHAR UTF8ToTCHAR((char*)Payload.GetData(), Payload.Num());
745
+ return FString(UTF8ToTCHAR.Length(), UTF8ToTCHAR.Get());
746
+ }