ultimate-unreal-engine-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +729 -0
  2. package/dist/build/error-parser.js +51 -0
  3. package/dist/build/fix-suggester.js +84 -0
  4. package/dist/build/ubt-runner.js +146 -0
  5. package/dist/cli.js +13 -0
  6. package/dist/config.js +8 -0
  7. package/dist/docs/data/ue57-api.js +228 -0
  8. package/dist/docs/doc-index.js +110 -0
  9. package/dist/docs/types.js +4 -0
  10. package/dist/generators/class-generator.js +363 -0
  11. package/dist/generators/file-modifier.js +276 -0
  12. package/dist/generators/uht-validator.js +177 -0
  13. package/dist/index.js +89 -0
  14. package/dist/parsers/cpp-class-index.js +230 -0
  15. package/dist/parsers/cpp-parser.js +369 -0
  16. package/dist/parsers/ini-parser.js +216 -0
  17. package/dist/parsers/uproject-parser.js +130 -0
  18. package/dist/plugin-bridge/client.js +217 -0
  19. package/dist/plugin-bridge/protocol.js +6 -0
  20. package/dist/plugin-bridge/retry.js +23 -0
  21. package/dist/setup.js +209 -0
  22. package/dist/tools/ai-systems/index.js +247 -0
  23. package/dist/tools/ai-systems/types.js +4 -0
  24. package/dist/tools/animation/index.js +241 -0
  25. package/dist/tools/animation/types.js +4 -0
  26. package/dist/tools/audio/index.js +204 -0
  27. package/dist/tools/audio/types.js +4 -0
  28. package/dist/tools/blueprint/index.js +495 -0
  29. package/dist/tools/blueprint/types.js +4 -0
  30. package/dist/tools/build/index.js +163 -0
  31. package/dist/tools/chaos/index.js +230 -0
  32. package/dist/tools/chaos/types.js +4 -0
  33. package/dist/tools/collision-physics/index.js +211 -0
  34. package/dist/tools/config/index.js +288 -0
  35. package/dist/tools/cpp/index.js +305 -0
  36. package/dist/tools/docs/index.js +251 -0
  37. package/dist/tools/editor/index.js +242 -0
  38. package/dist/tools/gas/index.js +222 -0
  39. package/dist/tools/gas/types.js +5 -0
  40. package/dist/tools/import-export/index.js +218 -0
  41. package/dist/tools/input/index.js +146 -0
  42. package/dist/tools/known-issues/index.js +88 -0
  43. package/dist/tools/known-issues/middleware.js +55 -0
  44. package/dist/tools/known-issues/store.js +125 -0
  45. package/dist/tools/livelink/index.js +203 -0
  46. package/dist/tools/livelink/types.js +4 -0
  47. package/dist/tools/material/index.js +190 -0
  48. package/dist/tools/motion-design/index.js +251 -0
  49. package/dist/tools/motion-design/types.js +6 -0
  50. package/dist/tools/movie-render/index.js +220 -0
  51. package/dist/tools/networking/index.js +149 -0
  52. package/dist/tools/pcg/index.js +164 -0
  53. package/dist/tools/selection/index.js +180 -0
  54. package/dist/tools/sequencer/index.js +218 -0
  55. package/dist/tools/validation/index.js +183 -0
  56. package/dist/tools/validation/types.js +4 -0
  57. package/dist/tools/viewport/index.js +310 -0
  58. package/dist/tools/worldpartition/index.js +226 -0
  59. package/dist/tools/worldpartition/types.js +4 -0
  60. package/dist/utils/execFileNoThrow.js +40 -0
  61. package/dist/utils/logger.js +27 -0
  62. package/dist/utils/path-guard.js +26 -0
  63. package/package.json +40 -0
  64. package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
  65. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
  66. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
  67. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
  68. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
  69. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
  70. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
  71. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
  72. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
  73. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
  74. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
  75. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
  76. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
  77. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
  78. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
  79. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
  80. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
  81. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
  82. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
  83. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
  84. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
  85. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
  86. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
  87. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
  88. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
  89. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
  90. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
  91. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
  92. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
  93. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
  94. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
  95. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
  96. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
  97. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
  98. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
  99. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
  100. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
  101. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
  102. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
  103. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
  104. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
  105. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
  106. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
  107. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
  108. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
  109. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
  110. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
  111. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
  112. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
  113. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
  114. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
  115. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
  116. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
  117. package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
  118. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
  119. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
  120. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
  121. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
  122. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
  123. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
  124. package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
@@ -0,0 +1,23 @@
1
+ // MCPWorldPartitionCommands.h (Plan 18-01)
2
+ // Declares the registration function for all World Partition MCP command handlers.
3
+ // Handlers: worldpartition.settings, worldpartition.dataLayers,
4
+ // worldpartition.streamingSources, worldpartition.hlod
5
+ //
6
+ // Call RegisterWorldPartitionCommands(*Router) in MCPBridgeSubsystem::Initialize()
7
+ // BEFORE the TCP server starts accepting connections.
8
+
9
+ #pragma once
10
+
11
+ #include "MCPCommandRouter.h"
12
+
13
+ /**
14
+ * Register all four World Partition command handlers into the given router.
15
+ * Must be called on the game thread before connections arrive.
16
+ *
17
+ * Registered commands:
18
+ * worldpartition.settings -- read WP grid size, loading range, streaming config (WP-01)
19
+ * worldpartition.dataLayers -- list/create/toggle data layers, assign actors (WP-02)
20
+ * worldpartition.streamingSources -- inspect streaming sources with shape, priority, target state (WP-03)
21
+ * worldpartition.hlod -- inspect HLOD layer config and trigger HLOD generation (WP-04)
22
+ */
23
+ void RegisterWorldPartitionCommands(FMCPCommandRouter& Router);
@@ -0,0 +1,14 @@
1
+ // MCPBridgeEditor.h
2
+ // Public header for the MCPBridgeEditor module.
3
+
4
+ #pragma once
5
+
6
+ #include "CoreMinimal.h"
7
+ #include "Modules/ModuleManager.h"
8
+
9
+ class FMCPBridgeEditorModule : public IModuleInterface
10
+ {
11
+ public:
12
+ virtual void StartupModule() override;
13
+ virtual void ShutdownModule() override;
14
+ };
@@ -0,0 +1,28 @@
1
+ // MCPBridgeRuntime.Build.cs
2
+ // Runtime module: TCP server, JSON framing, command router.
3
+ // IMPORTANT: Do NOT add any editor module dependencies here.
4
+ // Editor-only code lives exclusively in MCPBridgeEditor.
5
+
6
+ using UnrealBuildTool;
7
+
8
+ public class MCPBridgeRuntime : ModuleRules
9
+ {
10
+ public MCPBridgeRuntime(ReadOnlyTargetRules Target) : base(Target)
11
+ {
12
+ PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
13
+
14
+ PublicDependencyModuleNames.AddRange(new string[]
15
+ {
16
+ "Core",
17
+ });
18
+
19
+ PrivateDependencyModuleNames.AddRange(new string[]
20
+ {
21
+ "CoreUObject",
22
+ "Engine",
23
+ "Sockets",
24
+ "Networking",
25
+ "Json",
26
+ });
27
+ }
28
+ }
@@ -0,0 +1,22 @@
1
+ // MCPBridgeRuntime.cpp
2
+ // Startup/shutdown for the Runtime module.
3
+ // TCP server lifecycle is managed by MCPBridgeSubsystem (Editor module).
4
+
5
+ #include "MCPBridgeRuntime.h"
6
+
7
+ #define LOCTEXT_NAMESPACE "FMCPBridgeRuntimeModule"
8
+
9
+ void FMCPBridgeRuntimeModule::StartupModule()
10
+ {
11
+ // TCP server is started by UMCPBridgeSubsystem::Initialize() in the Editor module.
12
+ // This module only provides the TCP server class -- it does not start it here.
13
+ }
14
+
15
+ void FMCPBridgeRuntimeModule::ShutdownModule()
16
+ {
17
+ // TCP server is stopped by UMCPBridgeSubsystem::Deinitialize().
18
+ }
19
+
20
+ #undef LOCTEXT_NAMESPACE
21
+
22
+ IMPLEMENT_MODULE(FMCPBridgeRuntimeModule, MCPBridgeRuntime)
@@ -0,0 +1,118 @@
1
+ // MCPCommandRouter.cpp
2
+ // Implementation of FMCPCommandRouter.
3
+ // All handler dispatches go through AsyncTask(ENamedThreads::GameThread) to protect
4
+ // UE editor API calls from being invoked on a background network-receive thread.
5
+
6
+ #include "MCPCommandRouter.h"
7
+ #include "Serialization/JsonSerializer.h"
8
+ #include "Serialization/JsonWriter.h"
9
+ #include "Dom/JsonObject.h"
10
+ #include "Async/Async.h"
11
+
12
+ FMCPCommandRouter::FMCPCommandRouter()
13
+ {
14
+ // Register built-in ping handler.
15
+ // ping validates the full round-trip: TypeScript -> TCP -> C++ -> AsyncTask -> response.
16
+ RegisterHandler(TEXT("ping"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
17
+ {
18
+ // This lambda runs on the game thread (dispatched by Dispatch() below).
19
+ const FString CorrelationId = Cmd.IsValid() ? Cmd->GetStringField(TEXT("correlationId")) : TEXT("");
20
+
21
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
22
+ Data->SetBoolField(TEXT("pong"), true);
23
+
24
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
25
+ Response->SetBoolField(TEXT("success"), true);
26
+ if (!CorrelationId.IsEmpty())
27
+ {
28
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
29
+ }
30
+ Response->SetObjectField(TEXT("data"), Data);
31
+
32
+ FString Output;
33
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
34
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
35
+ Output += TEXT("\n");
36
+ SendResponse(Output);
37
+ });
38
+ }
39
+
40
+ void FMCPCommandRouter::RegisterHandler(const FString& CommandType, FMCPCommandHandler Handler)
41
+ {
42
+ Handlers.Add(CommandType, MoveTemp(Handler));
43
+ }
44
+
45
+ void FMCPCommandRouter::Dispatch(const FString& RawMessage, FMCPResponseSender SendResponse) const
46
+ {
47
+ // Parse the incoming JSON.
48
+ TSharedPtr<FJsonObject> JsonObj;
49
+ TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawMessage);
50
+ if (!FJsonSerializer::Deserialize(Reader, JsonObj) || !JsonObj.IsValid())
51
+ {
52
+ // T-07-04: malformed JSON must not crash -- return structured error.
53
+ SendResponse(BuildErrorResponse(TEXT(""), TEXT("invalid_json")) + TEXT("\n"));
54
+ return;
55
+ }
56
+
57
+ FString CorrelationId;
58
+ JsonObj->TryGetStringField(TEXT("correlationId"), CorrelationId);
59
+
60
+ FString CommandType;
61
+ if (!JsonObj->TryGetStringField(TEXT("type"), CommandType) || CommandType.IsEmpty())
62
+ {
63
+ SendResponse(BuildErrorResponse(CorrelationId, TEXT("missing_type")) + TEXT("\n"));
64
+ return;
65
+ }
66
+
67
+ const FMCPCommandHandler* HandlerPtr = Handlers.Find(CommandType);
68
+ if (!HandlerPtr)
69
+ {
70
+ SendResponse(BuildErrorResponse(CorrelationId, TEXT("unknown_command")) + TEXT("\n"));
71
+ return;
72
+ }
73
+
74
+ // Capture handler and response sender by value -- safe across async boundary.
75
+ FMCPCommandHandler Handler = *HandlerPtr;
76
+
77
+ // All UE API calls must run on the game thread (Pitfall 2: game-thread assertions).
78
+ // This wrapping is applied universally -- handlers must never call UE APIs directly
79
+ // from a background thread.
80
+ AsyncTask(ENamedThreads::GameThread, [Handler, JsonObj, SendResponse, CorrelationId]()
81
+ {
82
+ // Inject correlationId back into JsonObj so handlers can read it if needed.
83
+ JsonObj->SetStringField(TEXT("correlationId"), CorrelationId);
84
+ Handler(JsonObj, SendResponse);
85
+ });
86
+ }
87
+
88
+ FString FMCPCommandRouter::BuildErrorResponse(const FString& CorrelationId, const FString& Error)
89
+ {
90
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
91
+ Obj->SetBoolField(TEXT("success"), false);
92
+ if (!CorrelationId.IsEmpty())
93
+ {
94
+ Obj->SetStringField(TEXT("correlationId"), CorrelationId);
95
+ }
96
+ Obj->SetStringField(TEXT("error"), Error);
97
+
98
+ FString Output;
99
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
100
+ FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
101
+ return Output;
102
+ }
103
+
104
+ FString FMCPCommandRouter::BuildSuccessResponse(const FString& CorrelationId, TSharedPtr<FJsonObject> Data)
105
+ {
106
+ TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
107
+ Obj->SetBoolField(TEXT("success"), true);
108
+ Obj->SetStringField(TEXT("correlationId"), CorrelationId);
109
+ if (Data.IsValid())
110
+ {
111
+ Obj->SetObjectField(TEXT("data"), Data);
112
+ }
113
+
114
+ FString Output;
115
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
116
+ FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
117
+ return Output;
118
+ }
@@ -0,0 +1,196 @@
1
+ // MCPTcpServer.cpp
2
+ // TCP server implementation for MCPBridge.
3
+ // Binds exclusively to 127.0.0.1 (FIPv4Address::InternalLoopback).
4
+ // Ticks at 20 Hz via FTSTicker to accept connections and read data without
5
+ // blocking the game thread.
6
+
7
+ #include "MCPTcpServer.h"
8
+ #include "SocketSubsystem.h"
9
+ #include "Common/TcpSocketBuilder.h"
10
+ #include "Interfaces/IPv4/IPv4Address.h"
11
+ #include "Interfaces/IPv4/IPv4Endpoint.h"
12
+
13
+ FMCPTcpServer::FMCPTcpServer(FMCPCommandRouter& InRouter)
14
+ : Router(InRouter)
15
+ {
16
+ }
17
+
18
+ FMCPTcpServer::~FMCPTcpServer()
19
+ {
20
+ Stop();
21
+ }
22
+
23
+ bool FMCPTcpServer::Start(int32 Port)
24
+ {
25
+ // Bind to loopback only -- never 0.0.0.0 (CONTEXT.md security constraint).
26
+ FIPv4Endpoint Endpoint(FIPv4Address::InternalLoopback, static_cast<uint16>(Port));
27
+
28
+ ListenerSocket = FTcpSocketBuilder(TEXT("MCPBridge.Listener"))
29
+ .AsReusable()
30
+ .BoundToEndpoint(Endpoint)
31
+ .Listening(8)
32
+ .Build();
33
+
34
+ if (!ListenerSocket)
35
+ {
36
+ UE_LOG(LogTemp, Error, TEXT("[MCPBridge] Failed to bind TCP listener on 127.0.0.1:%d"), Port);
37
+ return false;
38
+ }
39
+
40
+ ListenerSocket->SetNonBlocking(true);
41
+ bRunning = true;
42
+
43
+ // 20 Hz tick -- low overhead, <50ms latency for interactive editor commands.
44
+ TickerHandle = FTSTicker::GetCoreTicker().AddTicker(
45
+ FTickerDelegate::CreateRaw(this, &FMCPTcpServer::Tick), 0.05f);
46
+
47
+ UE_LOG(LogTemp, Log, TEXT("[MCPBridge] TCP server listening on 127.0.0.1:%d"), Port);
48
+ return true;
49
+ }
50
+
51
+ void FMCPTcpServer::Stop()
52
+ {
53
+ if (!bRunning)
54
+ {
55
+ return;
56
+ }
57
+ bRunning = false;
58
+
59
+ if (TickerHandle.IsValid())
60
+ {
61
+ FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle);
62
+ TickerHandle.Reset();
63
+ }
64
+
65
+ ISocketSubsystem* SocketSS = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
66
+
67
+ for (FMCPClientState& Client : Clients)
68
+ {
69
+ if (Client.Socket)
70
+ {
71
+ Client.Socket->Close();
72
+ SocketSS->DestroySocket(Client.Socket);
73
+ }
74
+ }
75
+ Clients.Empty();
76
+
77
+ if (ListenerSocket)
78
+ {
79
+ ListenerSocket->Close();
80
+ SocketSS->DestroySocket(ListenerSocket);
81
+ ListenerSocket = nullptr;
82
+ }
83
+
84
+ UE_LOG(LogTemp, Log, TEXT("[MCPBridge] TCP server stopped."));
85
+ }
86
+
87
+ bool FMCPTcpServer::Tick(float /*DeltaTime*/)
88
+ {
89
+ if (!bRunning)
90
+ {
91
+ return false; // Remove ticker
92
+ }
93
+ AcceptConnections();
94
+ ReadClients();
95
+ return true; // Keep ticking
96
+ }
97
+
98
+ void FMCPTcpServer::AcceptConnections()
99
+ {
100
+ if (!ListenerSocket)
101
+ {
102
+ return;
103
+ }
104
+
105
+ bool bHasPendingConnection = false;
106
+ while (ListenerSocket->HasPendingConnection(bHasPendingConnection) && bHasPendingConnection)
107
+ {
108
+ FSocket* ClientSocket = ListenerSocket->Accept(TEXT("MCPBridge.Client"));
109
+ if (ClientSocket)
110
+ {
111
+ ClientSocket->SetNonBlocking(true);
112
+ FMCPClientState State;
113
+ State.Socket = ClientSocket;
114
+ Clients.Add(MoveTemp(State));
115
+ UE_LOG(LogTemp, Log, TEXT("[MCPBridge] Client connected."));
116
+ }
117
+ }
118
+ }
119
+
120
+ void FMCPTcpServer::ReadClients()
121
+ {
122
+ ISocketSubsystem* SocketSS = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
123
+
124
+ for (int32 i = Clients.Num() - 1; i >= 0; --i)
125
+ {
126
+ FMCPClientState& Client = Clients[i];
127
+
128
+ if (!Client.Socket || Client.Socket->GetConnectionState() != SCS_Connected)
129
+ {
130
+ // Clean up disconnected client.
131
+ if (Client.Socket)
132
+ {
133
+ Client.Socket->Close();
134
+ SocketSS->DestroySocket(Client.Socket);
135
+ Client.Socket = nullptr;
136
+ }
137
+ Clients.RemoveAt(i);
138
+ UE_LOG(LogTemp, Log, TEXT("[MCPBridge] Client disconnected."));
139
+ continue;
140
+ }
141
+
142
+ uint32 PendingSize = 0;
143
+ while (Client.Socket->HasPendingData(PendingSize) && PendingSize > 0)
144
+ {
145
+ TArray<uint8> Buffer;
146
+ Buffer.SetNumUninitialized(PendingSize);
147
+ int32 BytesRead = 0;
148
+ if (!Client.Socket->Recv(Buffer.GetData(), Buffer.Num(), BytesRead) || BytesRead <= 0)
149
+ {
150
+ break;
151
+ }
152
+
153
+ // Null-terminate and convert UTF-8 bytes to TCHAR string.
154
+ Buffer.Add(0);
155
+ FUTF8ToTCHAR Converter(reinterpret_cast<const ANSICHAR*>(Buffer.GetData()), BytesRead);
156
+ Client.ReceiveBuffer.Append(Converter.Get(), Converter.Length());
157
+
158
+ // Process all complete lines (delimited by \n).
159
+ int32 NewlinePos;
160
+ while (Client.ReceiveBuffer.FindChar(TEXT('\n'), NewlinePos))
161
+ {
162
+ FString Message = Client.ReceiveBuffer.Left(NewlinePos).TrimEnd();
163
+ Client.ReceiveBuffer.RightChopInline(NewlinePos + 1);
164
+
165
+ if (!Message.IsEmpty())
166
+ {
167
+ // Capture socket pointer for response callback.
168
+ // Router.Dispatch() dispatches the handler to the game thread via AsyncTask.
169
+ // The SendResponse lambda captures Sock -- valid because clients are only
170
+ // destroyed on the next tick after disconnect detection.
171
+ FSocket* Sock = Client.Socket;
172
+ Router.Dispatch(Message, [this, Sock](const FString& Response)
173
+ {
174
+ SendResponse(Sock, Response);
175
+ });
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ void FMCPTcpServer::SendResponse(FSocket* ClientSocket, const FString& Response)
183
+ {
184
+ if (!ClientSocket || Response.IsEmpty())
185
+ {
186
+ return;
187
+ }
188
+
189
+ // Convert TCHAR string to UTF-8 bytes for sending over the wire.
190
+ FTCHARToUTF8 Converter(*Response);
191
+ const uint8* Data = reinterpret_cast<const uint8*>(Converter.Get());
192
+ int32 Size = Converter.Length();
193
+ int32 BytesSent = 0;
194
+
195
+ ClientSocket->Send(Data, Size, BytesSent);
196
+ }
@@ -0,0 +1,15 @@
1
+ // MCPBridgeRuntime.h
2
+ // Public header for the MCPBridgeRuntime module.
3
+ // Exposes the module interface and the TCP server singleton.
4
+
5
+ #pragma once
6
+
7
+ #include "CoreMinimal.h"
8
+ #include "Modules/ModuleManager.h"
9
+
10
+ class MCPBRIDGERUNTIME_API FMCPBridgeRuntimeModule : public IModuleInterface
11
+ {
12
+ public:
13
+ virtual void StartupModule() override;
14
+ virtual void ShutdownModule() override;
15
+ };
@@ -0,0 +1,55 @@
1
+ // MCPCommandRouter.h
2
+ // Routes incoming JSON-newline commands to registered handlers.
3
+ // ALL handler invocations are dispatched via AsyncTask(ENamedThreads::GameThread).
4
+ // This is non-retrofittable -- establish this pattern before any real handlers exist.
5
+ //
6
+ // Usage (from Phases 8-12):
7
+ // Router.RegisterHandler(TEXT("get_actors"), [](TSharedPtr<FJsonObject> Cmd,
8
+ // FMCPResponseSender Send) { /* editor API call -- runs on game thread */ });
9
+
10
+ #pragma once
11
+
12
+ #include "CoreMinimal.h"
13
+ #include "Dom/JsonObject.h"
14
+
15
+ /** Callback type for sending a JSON response back to the connected client. */
16
+ using FMCPResponseSender = TFunction<void(const FString& JsonResponse)>;
17
+
18
+ /**
19
+ * Handler signature: receives parsed command object, sends response via SendResponse.
20
+ * Guaranteed to run on the game thread -- safe to call any UE editor API.
21
+ */
22
+ using FMCPCommandHandler = TFunction<void(TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)>;
23
+
24
+ class MCPBRIDGERUNTIME_API FMCPCommandRouter
25
+ {
26
+ public:
27
+ FMCPCommandRouter();
28
+
29
+ /**
30
+ * Register a handler for a command type.
31
+ * Call this during plugin initialization (before any connections arrive).
32
+ * Not thread-safe after the server is accepting connections.
33
+ */
34
+ void RegisterHandler(const FString& CommandType, FMCPCommandHandler Handler);
35
+
36
+ /**
37
+ * Dispatch an incoming JSON-newline message.
38
+ * Parses JSON, looks up handler, dispatches on game thread via AsyncTask.
39
+ * If no handler registered, sends {success:false, error:"unknown_command"}.
40
+ * SendResponse is guaranteed to be called exactly once per Dispatch call.
41
+ *
42
+ * @param RawMessage Raw JSON string (without trailing newline).
43
+ * @param SendResponse Callback to send the JSON response back to the client.
44
+ */
45
+ void Dispatch(const FString& RawMessage, FMCPResponseSender SendResponse) const;
46
+
47
+ private:
48
+ /** Builds a JSON error response with the given correlationId and error string. */
49
+ static FString BuildErrorResponse(const FString& CorrelationId, const FString& Error);
50
+
51
+ /** Builds a JSON success response with the given correlationId and data object. */
52
+ static FString BuildSuccessResponse(const FString& CorrelationId, TSharedPtr<FJsonObject> Data);
53
+
54
+ TMap<FString, FMCPCommandHandler> Handlers;
55
+ };
@@ -0,0 +1,59 @@
1
+ // MCPTcpServer.h
2
+ // FSocket-based TCP server for the MCP bridge.
3
+ // Binds to 127.0.0.1:PORT (NEVER 0.0.0.0 -- see CONTEXT.md security constraint).
4
+ // Uses FTSTicker for non-blocking periodic accept/read at 20 Hz.
5
+ // Each message is one JSON object terminated by \n (JSON-newline framing).
6
+ //
7
+ // Per-client receive state is held in FMCPClientState to handle partial reads.
8
+
9
+ #pragma once
10
+
11
+ #include "CoreMinimal.h"
12
+ #include "MCPCommandRouter.h"
13
+ #include "Sockets.h"
14
+ #include "Containers/Ticker.h"
15
+
16
+ /**
17
+ * Holds per-connection state: socket handle + partial receive buffer.
18
+ */
19
+ struct FMCPClientState
20
+ {
21
+ FSocket* Socket = nullptr;
22
+ FString ReceiveBuffer; // accumulates bytes until \n is found
23
+ };
24
+
25
+ /**
26
+ * TCP server bound to 127.0.0.1:PORT.
27
+ * Owns the listener socket, all client sockets, and the ticker.
28
+ */
29
+ class MCPBRIDGERUNTIME_API FMCPTcpServer
30
+ {
31
+ public:
32
+ explicit FMCPTcpServer(FMCPCommandRouter& InRouter);
33
+ ~FMCPTcpServer();
34
+
35
+ /** Bind listener socket and start the FTSTicker. Returns false on bind failure. */
36
+ bool Start(int32 Port);
37
+
38
+ /** Close all client sockets, listener socket, and remove the ticker. */
39
+ void Stop();
40
+
41
+ private:
42
+ /** Called at 20 Hz by FTSTicker. Accepts new connections and reads pending data. */
43
+ bool Tick(float DeltaTime);
44
+
45
+ /** Accept any pending new connections from the listener socket. */
46
+ void AcceptConnections();
47
+
48
+ /** Read all pending data from each connected client, dispatch complete messages. */
49
+ void ReadClients();
50
+
51
+ /** Send a JSON-newline response to a specific client socket. */
52
+ void SendResponse(FSocket* ClientSocket, const FString& Response);
53
+
54
+ FMCPCommandRouter& Router;
55
+ FSocket* ListenerSocket = nullptr;
56
+ TArray<FMCPClientState> Clients;
57
+ FTSTicker::FDelegateHandle TickerHandle;
58
+ bool bRunning = false;
59
+ };