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,616 @@
1
+ // MCPBlueprintHandlers.cpp
2
+ // Implements blueprint.read, blueprint.graph, and blueprint.list command handlers.
3
+ // All handlers are registered on the FMCPCommandRouter and run on the game thread
4
+ // (enforced by FMCPCommandRouter::Dispatch via AsyncTask).
5
+ //
6
+ // Response format matches the ping handler pattern in MCPCommandRouter.cpp:
7
+ // { success: bool, correlationId: string, data: {...} } + "\n"
8
+
9
+ #include "MCPBlueprintHandlers.h"
10
+
11
+ #include "MCPCommandRouter.h"
12
+
13
+ // Blueprint APIs
14
+ #include "Engine/Blueprint.h"
15
+ #include "BlueprintEditorUtils.h"
16
+ #include "EdGraph/EdGraph.h"
17
+ #include "EdGraph/EdGraphNode.h"
18
+ #include "EdGraph/EdGraphPin.h"
19
+ #include "Engine/SimpleConstructionScript.h"
20
+ #include "Engine/SCS_Node.h"
21
+
22
+ // K2Node APIs — required for blueprint.cppUsage graph scanning
23
+ #include "K2Node_CallFunction.h"
24
+ #include "K2Node_VariableGet.h"
25
+ #include "K2Node_VariableSet.h"
26
+
27
+ // Asset Registry APIs
28
+ #include "AssetRegistry/AssetRegistryModule.h"
29
+ #include "AssetRegistry/IAssetRegistry.h"
30
+ #include "AssetRegistry/AssetData.h"
31
+ #include "Blueprint/BlueprintTags.h"
32
+
33
+ // JSON APIs
34
+ #include "Dom/JsonObject.h"
35
+ #include "Serialization/JsonSerializer.h"
36
+ #include "Serialization/JsonWriter.h"
37
+
38
+ void RegisterBlueprintHandlers(FMCPCommandRouter& Router)
39
+ {
40
+ // ---------------------------------------------------------------------------
41
+ // Shared response helpers captured by value in each handler lambda.
42
+ // ---------------------------------------------------------------------------
43
+
44
+ auto SendSuccess = [](FMCPResponseSender SendResponse,
45
+ const FString& CorrelationId,
46
+ TSharedPtr<FJsonObject> Data)
47
+ {
48
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
49
+ Response->SetBoolField(TEXT("success"), true);
50
+ if (!CorrelationId.IsEmpty())
51
+ {
52
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
53
+ }
54
+ Response->SetObjectField(TEXT("data"), Data);
55
+
56
+ FString Output;
57
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
58
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
59
+ Output += TEXT("\n");
60
+ SendResponse(Output);
61
+ };
62
+
63
+ auto SendError = [](FMCPResponseSender SendResponse,
64
+ const FString& CorrelationId,
65
+ const FString& ErrorCode)
66
+ {
67
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
68
+ Response->SetBoolField(TEXT("success"), false);
69
+ if (!CorrelationId.IsEmpty())
70
+ {
71
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
72
+ }
73
+ Response->SetStringField(TEXT("error"), ErrorCode);
74
+
75
+ FString Output;
76
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
77
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
78
+ Output += TEXT("\n");
79
+ SendResponse(Output);
80
+ };
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // blueprint.read
84
+ // Input payload: { "asset_path": "/Game/Blueprints/BP_MyActor" }
85
+ // Returns: { assetPath, parentClass, variables[], components[] }
86
+ // ---------------------------------------------------------------------------
87
+ Router.RegisterHandler(TEXT("blueprint.read"),
88
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
89
+ {
90
+ FString CorrelationId;
91
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
92
+
93
+ // Extract asset_path from payload
94
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
95
+ FString AssetPath;
96
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj) ||
97
+ !(*PayloadObj)->TryGetStringField(TEXT("asset_path"), AssetPath) ||
98
+ AssetPath.IsEmpty())
99
+ {
100
+ SendError(SendResponse, CorrelationId, TEXT("missing_asset_path"));
101
+ return;
102
+ }
103
+
104
+ // Load the Blueprint asset via UE asset system (never parses .uasset binary directly)
105
+ UBlueprint* BP = LoadObject<UBlueprint>(nullptr, *AssetPath);
106
+ if (!BP)
107
+ {
108
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] blueprint.read: Blueprint not found at path '%s'"), *AssetPath);
109
+ SendError(SendResponse, CorrelationId, TEXT("blueprint_not_found"));
110
+ return;
111
+ }
112
+
113
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
114
+ Data->SetStringField(TEXT("assetPath"), AssetPath);
115
+ Data->SetStringField(TEXT("parentClass"),
116
+ BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None"));
117
+
118
+ // Variables: iterate BP->NewVariables (member variables defined in the Blueprint)
119
+ TArray<TSharedPtr<FJsonValue>> VarArray;
120
+ for (const FBPVariableDescription& Var : BP->NewVariables)
121
+ {
122
+ TSharedPtr<FJsonObject> VarObj = MakeShared<FJsonObject>();
123
+ VarObj->SetStringField(TEXT("name"), Var.VarName.ToString());
124
+ VarObj->SetStringField(TEXT("type"), Var.VarType.PinCategory.ToString());
125
+ VarArray.Add(MakeShared<FJsonValueObject>(VarObj));
126
+ }
127
+ Data->SetArrayField(TEXT("variables"), VarArray);
128
+
129
+ // Components: iterate SimpleConstructionScript nodes (actor component tree)
130
+ TArray<TSharedPtr<FJsonValue>> CompArray;
131
+ if (BP->SimpleConstructionScript)
132
+ {
133
+ for (USCS_Node* Node : BP->SimpleConstructionScript->GetAllNodes())
134
+ {
135
+ if (!Node || !Node->ComponentTemplate)
136
+ {
137
+ continue;
138
+ }
139
+ TSharedPtr<FJsonObject> CompObj = MakeShared<FJsonObject>();
140
+ CompObj->SetStringField(TEXT("name"), Node->GetVariableName().ToString());
141
+ CompObj->SetStringField(TEXT("class"), Node->ComponentTemplate->GetClass()->GetName());
142
+ CompArray.Add(MakeShared<FJsonValueObject>(CompObj));
143
+ }
144
+ }
145
+ Data->SetArrayField(TEXT("components"), CompArray);
146
+
147
+ SendSuccess(SendResponse, CorrelationId, Data);
148
+ });
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // blueprint.graph
152
+ // Input payload: { "asset_path": "/Game/Blueprints/BP_MyActor", "graph_name": "EventGraph" }
153
+ // graph_name is optional -- defaults to "EventGraph"
154
+ // Returns: { assetPath, graphName, nodes[], connections[] }
155
+ // ---------------------------------------------------------------------------
156
+ Router.RegisterHandler(TEXT("blueprint.graph"),
157
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
158
+ {
159
+ FString CorrelationId;
160
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
161
+
162
+ // Extract asset_path and optional graph_name from payload
163
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
164
+ FString AssetPath;
165
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj) ||
166
+ !(*PayloadObj)->TryGetStringField(TEXT("asset_path"), AssetPath) ||
167
+ AssetPath.IsEmpty())
168
+ {
169
+ SendError(SendResponse, CorrelationId, TEXT("missing_asset_path"));
170
+ return;
171
+ }
172
+
173
+ FString GraphName = TEXT("EventGraph");
174
+ (*PayloadObj)->TryGetStringField(TEXT("graph_name"), GraphName);
175
+
176
+ // Load the Blueprint asset
177
+ UBlueprint* BP = LoadObject<UBlueprint>(nullptr, *AssetPath);
178
+ if (!BP)
179
+ {
180
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] blueprint.graph: Blueprint not found at path '%s'"), *AssetPath);
181
+ SendError(SendResponse, CorrelationId, TEXT("blueprint_not_found"));
182
+ return;
183
+ }
184
+
185
+ // Search UbergraphPages (event graphs) then FunctionGraphs for the named graph
186
+ UEdGraph* TargetGraph = nullptr;
187
+ for (UEdGraph* Graph : BP->UbergraphPages)
188
+ {
189
+ if (Graph && Graph->GetName() == GraphName)
190
+ {
191
+ TargetGraph = Graph;
192
+ break;
193
+ }
194
+ }
195
+ if (!TargetGraph)
196
+ {
197
+ for (UEdGraph* Graph : BP->FunctionGraphs)
198
+ {
199
+ if (Graph && Graph->GetName() == GraphName)
200
+ {
201
+ TargetGraph = Graph;
202
+ break;
203
+ }
204
+ }
205
+ }
206
+ // Default to first ubergraph if "EventGraph" was requested and not found by name
207
+ if (!TargetGraph && GraphName == TEXT("EventGraph") && BP->UbergraphPages.Num() > 0)
208
+ {
209
+ TargetGraph = BP->UbergraphPages[0];
210
+ }
211
+ if (!TargetGraph)
212
+ {
213
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] blueprint.graph: Graph '%s' not found in '%s'"),
214
+ *GraphName, *AssetPath);
215
+ SendError(SendResponse, CorrelationId, TEXT("graph_not_found"));
216
+ return;
217
+ }
218
+
219
+ // Serialize nodes with their pins
220
+ TArray<TSharedPtr<FJsonValue>> NodeArray;
221
+ for (UEdGraphNode* Node : TargetGraph->Nodes)
222
+ {
223
+ if (!Node)
224
+ {
225
+ continue;
226
+ }
227
+
228
+ TSharedPtr<FJsonObject> NodeObj = MakeShared<FJsonObject>();
229
+ NodeObj->SetStringField(TEXT("nodeGuid"), Node->NodeGuid.ToString());
230
+ NodeObj->SetStringField(TEXT("type"), Node->GetClass()->GetName());
231
+ NodeObj->SetStringField(TEXT("title"), Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
232
+ NodeObj->SetNumberField(TEXT("posX"), Node->NodePosX);
233
+ NodeObj->SetNumberField(TEXT("posY"), Node->NodePosY);
234
+
235
+ // Serialize pins
236
+ TArray<TSharedPtr<FJsonValue>> PinArray;
237
+ for (UEdGraphPin* Pin : Node->Pins)
238
+ {
239
+ if (!Pin)
240
+ {
241
+ continue;
242
+ }
243
+ TSharedPtr<FJsonObject> PinObj = MakeShared<FJsonObject>();
244
+ PinObj->SetStringField(TEXT("pinName"), Pin->PinName.ToString());
245
+ PinObj->SetStringField(TEXT("direction"),
246
+ Pin->Direction == EGPD_Input ? TEXT("input") : TEXT("output"));
247
+ PinObj->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString());
248
+ PinObj->SetStringField(TEXT("defaultValue"), Pin->DefaultValue);
249
+ PinArray.Add(MakeShared<FJsonValueObject>(PinObj));
250
+ }
251
+ NodeObj->SetArrayField(TEXT("pins"), PinArray);
252
+ NodeArray.Add(MakeShared<FJsonValueObject>(NodeObj));
253
+ }
254
+
255
+ // Serialize connections: walk output pins and their LinkedTo list
256
+ TArray<TSharedPtr<FJsonValue>> ConnArray;
257
+ for (UEdGraphNode* Node : TargetGraph->Nodes)
258
+ {
259
+ if (!Node)
260
+ {
261
+ continue;
262
+ }
263
+ for (UEdGraphPin* Pin : Node->Pins)
264
+ {
265
+ if (!Pin || Pin->Direction != EGPD_Output)
266
+ {
267
+ continue;
268
+ }
269
+ for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
270
+ {
271
+ if (!LinkedPin || !LinkedPin->GetOwningNode())
272
+ {
273
+ continue;
274
+ }
275
+ TSharedPtr<FJsonObject> Conn = MakeShared<FJsonObject>();
276
+ Conn->SetStringField(TEXT("fromNodeGuid"), Node->NodeGuid.ToString());
277
+ Conn->SetStringField(TEXT("fromPinName"), Pin->PinName.ToString());
278
+ Conn->SetStringField(TEXT("toNodeGuid"), LinkedPin->GetOwningNode()->NodeGuid.ToString());
279
+ Conn->SetStringField(TEXT("toPinName"), LinkedPin->PinName.ToString());
280
+ ConnArray.Add(MakeShared<FJsonValueObject>(Conn));
281
+ }
282
+ }
283
+ }
284
+
285
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
286
+ Data->SetStringField(TEXT("assetPath"), AssetPath);
287
+ Data->SetStringField(TEXT("graphName"), TargetGraph->GetName());
288
+ Data->SetArrayField(TEXT("nodes"), NodeArray);
289
+ Data->SetArrayField(TEXT("connections"), ConnArray);
290
+
291
+ SendSuccess(SendResponse, CorrelationId, Data);
292
+ });
293
+
294
+ // ---------------------------------------------------------------------------
295
+ // blueprint.list
296
+ // Input payload: { "path_prefix": "/Game/Blueprints/" } (path_prefix optional)
297
+ // Returns: { blueprints[], count }
298
+ // ---------------------------------------------------------------------------
299
+ Router.RegisterHandler(TEXT("blueprint.list"),
300
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
301
+ {
302
+ FString CorrelationId;
303
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
304
+
305
+ // Extract optional path_prefix from payload
306
+ FString PathPrefix;
307
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
308
+ if (Command->TryGetObjectField(TEXT("payload"), PayloadObj))
309
+ {
310
+ (*PayloadObj)->TryGetStringField(TEXT("path_prefix"), PathPrefix);
311
+ }
312
+
313
+ // Query the Asset Registry for all Blueprint assets
314
+ IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
315
+ TEXT("AssetRegistry")).Get();
316
+
317
+ FARFilter Filter;
318
+ Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
319
+ Filter.bRecursivePaths = true;
320
+ if (!PathPrefix.IsEmpty())
321
+ {
322
+ Filter.PackagePaths.Add(FName(*PathPrefix));
323
+ }
324
+
325
+ TArray<FAssetData> Assets;
326
+ AR.GetAssets(Filter, Assets);
327
+
328
+ TArray<TSharedPtr<FJsonValue>> AssetArray;
329
+ for (const FAssetData& Asset : Assets)
330
+ {
331
+ TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
332
+ AssetObj->SetStringField(TEXT("assetPath"), Asset.GetSoftObjectPath().ToString());
333
+ AssetObj->SetStringField(TEXT("packagePath"), Asset.PackagePath.ToString());
334
+
335
+ // ParentClass is stored as a tag on Blueprint assets in the asset registry
336
+ FString ParentClass;
337
+ Asset.GetTagValue(FBlueprintTags::ParentClassPath, ParentClass);
338
+ AssetObj->SetStringField(TEXT("parentClass"), ParentClass);
339
+
340
+ AssetArray.Add(MakeShared<FJsonValueObject>(AssetObj));
341
+ }
342
+
343
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
344
+ Data->SetArrayField(TEXT("blueprints"), AssetArray);
345
+ Data->SetNumberField(TEXT("count"), AssetArray.Num());
346
+
347
+ SendSuccess(SendResponse, CorrelationId, Data);
348
+ });
349
+ }
350
+
351
+ void RegisterBlueprintBridgeHandlers(FMCPCommandRouter& Router)
352
+ {
353
+ // ---------------------------------------------------------------------------
354
+ // Shared response helpers — same pattern as RegisterBlueprintHandlers above.
355
+ // ---------------------------------------------------------------------------
356
+
357
+ auto SendSuccess = [](FMCPResponseSender SendResponse,
358
+ const FString& CorrelationId,
359
+ TSharedPtr<FJsonObject> Data)
360
+ {
361
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
362
+ Response->SetBoolField(TEXT("success"), true);
363
+ if (!CorrelationId.IsEmpty())
364
+ {
365
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
366
+ }
367
+ Response->SetObjectField(TEXT("data"), Data);
368
+
369
+ FString Output;
370
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
371
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
372
+ Output += TEXT("\n");
373
+ SendResponse(Output);
374
+ };
375
+
376
+ auto SendError = [](FMCPResponseSender SendResponse,
377
+ const FString& CorrelationId,
378
+ const FString& ErrorCode)
379
+ {
380
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
381
+ Response->SetBoolField(TEXT("success"), false);
382
+ if (!CorrelationId.IsEmpty())
383
+ {
384
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
385
+ }
386
+ Response->SetStringField(TEXT("error"), ErrorCode);
387
+
388
+ FString Output;
389
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
390
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
391
+ Output += TEXT("\n");
392
+ SendResponse(Output);
393
+ };
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // blueprint.subclasses
397
+ // Input payload: { "class_name": "AMyActor" }
398
+ // Returns: { className, subclasses[] } where each entry has { assetPath, packagePath, parentClassTag }
399
+ //
400
+ // Uses asset registry tag filtering only — does NOT call LoadObject to avoid loading
401
+ // potentially thousands of Blueprint assets for a class hierarchy query.
402
+ // ---------------------------------------------------------------------------
403
+ Router.RegisterHandler(TEXT("blueprint.subclasses"),
404
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
405
+ {
406
+ FString CorrelationId;
407
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
408
+
409
+ // Extract class_name from payload (T-11-01: validate non-empty before query)
410
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
411
+ FString ClassName;
412
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj) ||
413
+ !(*PayloadObj)->TryGetStringField(TEXT("class_name"), ClassName) ||
414
+ ClassName.IsEmpty())
415
+ {
416
+ SendError(SendResponse, CorrelationId, TEXT("missing_class_name"));
417
+ return;
418
+ }
419
+
420
+ // Query the Asset Registry for all Blueprint assets (no path prefix — search entire project)
421
+ IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
422
+ TEXT("AssetRegistry")).Get();
423
+
424
+ FARFilter Filter;
425
+ Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
426
+ Filter.bRecursivePaths = true;
427
+
428
+ TArray<FAssetData> Assets;
429
+ AR.GetAssets(Filter, Assets);
430
+
431
+ // Filter by ParentClassPath tag — match if the tag value contains the requested class_name.
432
+ // The tag value is typically "/Script/MyGame.AMyActor" so a Contains check is appropriate.
433
+ TArray<TSharedPtr<FJsonValue>> SubclassArray;
434
+ for (const FAssetData& Asset : Assets)
435
+ {
436
+ FString ParentClassTag;
437
+ Asset.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassTag);
438
+
439
+ if (ParentClassTag.IsEmpty())
440
+ {
441
+ continue;
442
+ }
443
+
444
+ // Case-sensitive contains — class names are case-sensitive in C++/UE
445
+ if (ParentClassTag.Contains(ClassName))
446
+ {
447
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
448
+ Entry->SetStringField(TEXT("assetPath"), Asset.GetSoftObjectPath().ToString());
449
+ Entry->SetStringField(TEXT("packagePath"), Asset.PackagePath.ToString());
450
+ Entry->SetStringField(TEXT("parentClassTag"), ParentClassTag);
451
+ SubclassArray.Add(MakeShared<FJsonValueObject>(Entry));
452
+ }
453
+ }
454
+
455
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
456
+ Data->SetStringField(TEXT("className"), ClassName);
457
+ Data->SetArrayField(TEXT("subclasses"), SubclassArray);
458
+
459
+ SendSuccess(SendResponse, CorrelationId, Data);
460
+ });
461
+
462
+ // ---------------------------------------------------------------------------
463
+ // blueprint.cppUsage
464
+ // Input payload: { "class_name": "AMyActor", "member_name": "Attack" }
465
+ // Returns: { className, memberName, usages[], count }
466
+ // Each usage: { assetPath, graphName, nodeType, nodeTitle }
467
+ //
468
+ // Scans UK2Node_CallFunction, UK2Node_VariableGet, and UK2Node_VariableSet nodes
469
+ // across all Blueprint graphs (UbergraphPages + FunctionGraphs).
470
+ // LoadObject is called per asset — this is intentional and expected to be slow on
471
+ // large projects (T-11-02: accepted DoS risk — editor-only, not a production path).
472
+ // ---------------------------------------------------------------------------
473
+ Router.RegisterHandler(TEXT("blueprint.cppUsage"),
474
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
475
+ {
476
+ FString CorrelationId;
477
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
478
+
479
+ // Extract class_name and member_name from payload
480
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
481
+ FString ClassName;
482
+ FString MemberName;
483
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj))
484
+ {
485
+ SendError(SendResponse, CorrelationId, TEXT("missing_parameters"));
486
+ return;
487
+ }
488
+ (*PayloadObj)->TryGetStringField(TEXT("class_name"), ClassName);
489
+ (*PayloadObj)->TryGetStringField(TEXT("member_name"), MemberName);
490
+
491
+ if (ClassName.IsEmpty() || MemberName.IsEmpty())
492
+ {
493
+ SendError(SendResponse, CorrelationId, TEXT("missing_parameters"));
494
+ return;
495
+ }
496
+
497
+ // Query the Asset Registry for all Blueprint assets
498
+ IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
499
+ TEXT("AssetRegistry")).Get();
500
+
501
+ FARFilter Filter;
502
+ Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
503
+ Filter.bRecursivePaths = true;
504
+
505
+ TArray<FAssetData> Assets;
506
+ AR.GetAssets(Filter, Assets);
507
+
508
+ // Collect usage records — deduplicated by assetPath+graphName+nodeType+nodeTitle
509
+ TSet<FString> SeenKeys;
510
+ TArray<TSharedPtr<FJsonValue>> UsageArray;
511
+
512
+ for (const FAssetData& AssetData : Assets)
513
+ {
514
+ const FString AssetPath = AssetData.GetSoftObjectPath().ToString();
515
+
516
+ // Load the Blueprint to access its graphs
517
+ UBlueprint* BP = LoadObject<UBlueprint>(nullptr, *AssetPath);
518
+ if (!BP)
519
+ {
520
+ continue;
521
+ }
522
+
523
+ // Build a combined list of graphs to scan: event graphs + function graphs
524
+ TArray<UEdGraph*> AllGraphs;
525
+ AllGraphs.Append(BP->UbergraphPages);
526
+ AllGraphs.Append(BP->FunctionGraphs);
527
+
528
+ for (UEdGraph* Graph : AllGraphs)
529
+ {
530
+ if (!Graph)
531
+ {
532
+ continue;
533
+ }
534
+
535
+ const FString GraphName = Graph->GetName();
536
+
537
+ for (UEdGraphNode* Node : Graph->Nodes)
538
+ {
539
+ if (!Node)
540
+ {
541
+ continue;
542
+ }
543
+
544
+ bool bMatched = false;
545
+
546
+ // Check UK2Node_CallFunction: match member_name AND owning class contains class_name
547
+ if (UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Node))
548
+ {
549
+ if (CallNode->FunctionReference.GetMemberName().ToString() == MemberName)
550
+ {
551
+ // Check if the function's parent class name contains class_name
552
+ UClass* MemberParent = CallNode->FunctionReference.GetMemberParentClass();
553
+ const bool bClassMatch = MemberParent
554
+ ? MemberParent->GetName().Contains(ClassName)
555
+ : false;
556
+
557
+ // Also accept if the blueprint's parent class tag contains class_name
558
+ FString ParentClassTag;
559
+ AssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassTag);
560
+ const bool bTagMatch = ParentClassTag.Contains(ClassName);
561
+
562
+ if (bClassMatch || bTagMatch)
563
+ {
564
+ bMatched = true;
565
+ }
566
+ }
567
+ }
568
+ // Check UK2Node_VariableGet: match member_name only (variable names are unique within scope)
569
+ else if (UK2Node_VariableGet* GetNode = Cast<UK2Node_VariableGet>(Node))
570
+ {
571
+ if (GetNode->VariableReference.GetMemberName().ToString() == MemberName)
572
+ {
573
+ bMatched = true;
574
+ }
575
+ }
576
+ // Check UK2Node_VariableSet: match member_name only
577
+ else if (UK2Node_VariableSet* SetNode = Cast<UK2Node_VariableSet>(Node))
578
+ {
579
+ if (SetNode->VariableReference.GetMemberName().ToString() == MemberName)
580
+ {
581
+ bMatched = true;
582
+ }
583
+ }
584
+
585
+ if (bMatched)
586
+ {
587
+ const FString NodeType = Node->GetClass()->GetName();
588
+ const FString NodeTitle = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
589
+
590
+ // Deduplicate by composite key
591
+ const FString DedupeKey = AssetPath + TEXT("|") + GraphName + TEXT("|") + NodeType + TEXT("|") + NodeTitle;
592
+ if (!SeenKeys.Contains(DedupeKey))
593
+ {
594
+ SeenKeys.Add(DedupeKey);
595
+
596
+ TSharedPtr<FJsonObject> Usage = MakeShared<FJsonObject>();
597
+ Usage->SetStringField(TEXT("assetPath"), AssetPath);
598
+ Usage->SetStringField(TEXT("graphName"), GraphName);
599
+ Usage->SetStringField(TEXT("nodeType"), NodeType);
600
+ Usage->SetStringField(TEXT("nodeTitle"), NodeTitle);
601
+ UsageArray.Add(MakeShared<FJsonValueObject>(Usage));
602
+ }
603
+ }
604
+ }
605
+ }
606
+ }
607
+
608
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
609
+ Data->SetStringField(TEXT("className"), ClassName);
610
+ Data->SetStringField(TEXT("memberName"), MemberName);
611
+ Data->SetArrayField(TEXT("usages"), UsageArray);
612
+ Data->SetNumberField(TEXT("count"), UsageArray.Num());
613
+
614
+ SendSuccess(SendResponse, CorrelationId, Data);
615
+ });
616
+ }
@@ -0,0 +1,25 @@
1
+ // MCPBlueprintHandlers.h
2
+ // Declares the Blueprint command handler registration functions.
3
+ // Call RegisterBlueprintHandlers() and RegisterBlueprintBridgeHandlers()
4
+ // from UMCPBridgeSubsystem::Initialize() after the router is created.
5
+
6
+ #pragma once
7
+
8
+ #include "CoreMinimal.h"
9
+
10
+ class FMCPCommandRouter;
11
+
12
+ /**
13
+ * Register blueprint.read, blueprint.graph, and blueprint.list handlers
14
+ * on the given router. All handlers run on the game thread (enforced by
15
+ * FMCPCommandRouter::Dispatch via AsyncTask).
16
+ */
17
+ void RegisterBlueprintHandlers(FMCPCommandRouter& Router);
18
+
19
+ /**
20
+ * Register Blueprint-C++ bridge handlers on the given router:
21
+ * blueprint.subclasses -- find all Blueprint assets that inherit from a given C++ class
22
+ * blueprint.cppUsage -- scan Blueprint graphs for nodes referencing a specific C++ member
23
+ * All handlers run on the game thread (enforced by FMCPCommandRouter::Dispatch via AsyncTask).
24
+ */
25
+ void RegisterBlueprintBridgeHandlers(FMCPCommandRouter& Router);