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,22 @@
1
+ // MCPImportExportCommands.h (Plan 21-01)
2
+ // Declares the registration function for all asset import/export MCP command handlers.
3
+ // Handlers: import.fbx, import.usd, export.mesh, import.batch
4
+ //
5
+ // Call RegisterImportExportCommands(*Router) in MCPBridgeSubsystem::Initialize()
6
+ // BEFORE the TCP server starts accepting connections.
7
+
8
+ #pragma once
9
+
10
+ #include "MCPCommandRouter.h"
11
+
12
+ /**
13
+ * Register all four import/export command handlers into the given router.
14
+ * Must be called on the game thread before connections arrive.
15
+ *
16
+ * Registered commands:
17
+ * import.fbx -- import an FBX file to a content path (IMP-01)
18
+ * import.usd -- import a USD file via Interchange pipeline (IMP-02)
19
+ * export.mesh -- export a StaticMesh or SkeletalMesh to FBX on disk (IMP-03)
20
+ * import.batch -- batch import multiple files from a directory (IMP-04)
21
+ */
22
+ void RegisterImportExportCommands(FMCPCommandRouter& Router);
@@ -0,0 +1,381 @@
1
+ // MCPInputHandlers.cpp
2
+ // Implements input.listActions, input.createAction, input.listContexts,
3
+ // and input.addBinding command handlers.
4
+ // All handlers are registered on the FMCPCommandRouter and run on the game thread
5
+ // (enforced by FMCPCommandRouter::Dispatch via AsyncTask).
6
+ //
7
+ // INVARIANT: Every write handler calls Object->Modify() before any mutation and
8
+ // saves the package after -- never omit these.
9
+ //
10
+ // Security mitigations per threat model (T-14-01, T-14-02):
11
+ // T-14-01: asset_path validated via FPackageName::IsValidLongPackageName()
12
+ // T-14-02: key field validated via FKey(*KeyName).IsValid() before MapKey call
13
+
14
+ #include "MCPInputHandlers.h"
15
+
16
+ #include "MCPCommandRouter.h"
17
+
18
+ // Enhanced Input APIs
19
+ #include "InputAction.h"
20
+ #include "InputMappingContext.h"
21
+ #include "EnhancedActionKeyMapping.h"
22
+
23
+ // Asset Registry APIs
24
+ #include "AssetRegistry/AssetRegistryModule.h"
25
+
26
+ // UObject/Package APIs
27
+ #include "UObject/Package.h"
28
+ #include "Misc/PackageName.h"
29
+
30
+ // JSON APIs
31
+ #include "Dom/JsonObject.h"
32
+ #include "Serialization/JsonSerializer.h"
33
+ #include "Serialization/JsonWriter.h"
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Internal helper: map EInputActionValueType to a display string.
37
+ // ---------------------------------------------------------------------------
38
+ static FString ValueTypeToString(EInputActionValueType ValueType)
39
+ {
40
+ switch (ValueType)
41
+ {
42
+ case EInputActionValueType::Boolean: return TEXT("bool");
43
+ case EInputActionValueType::Axis1D: return TEXT("float");
44
+ case EInputActionValueType::Axis2D: return TEXT("Axis2D");
45
+ case EInputActionValueType::Axis3D: return TEXT("Axis3D");
46
+ default: return TEXT("unknown");
47
+ }
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Internal helper: map a value_type string to EInputActionValueType int.
52
+ // Returns -1 if the string is unrecognized.
53
+ // ---------------------------------------------------------------------------
54
+ static int32 StringToValueTypeInt(const FString& ValueTypeStr)
55
+ {
56
+ if (ValueTypeStr == TEXT("bool")) return 0;
57
+ if (ValueTypeStr == TEXT("float")) return 1;
58
+ if (ValueTypeStr == TEXT("Axis2D")) return 2;
59
+ if (ValueTypeStr == TEXT("Axis3D")) return 3;
60
+ return -1;
61
+ }
62
+
63
+ void RegisterInputHandlers(FMCPCommandRouter& Router)
64
+ {
65
+ // ---------------------------------------------------------------------------
66
+ // Shared response helpers -- verbatim pattern from MCPBlueprintWriteHandlers.cpp.
67
+ // ---------------------------------------------------------------------------
68
+
69
+ auto SendSuccess = [](FMCPResponseSender SendResponse,
70
+ const FString& CorrelationId,
71
+ TSharedPtr<FJsonObject> Data)
72
+ {
73
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
74
+ Response->SetBoolField(TEXT("success"), true);
75
+ if (!CorrelationId.IsEmpty())
76
+ {
77
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
78
+ }
79
+ Response->SetObjectField(TEXT("data"), Data);
80
+
81
+ FString Output;
82
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
83
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
84
+ Output += TEXT("\n");
85
+ SendResponse(Output);
86
+ };
87
+
88
+ auto SendError = [](FMCPResponseSender SendResponse,
89
+ const FString& CorrelationId,
90
+ const FString& ErrorCode)
91
+ {
92
+ TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
93
+ Response->SetBoolField(TEXT("success"), false);
94
+ if (!CorrelationId.IsEmpty())
95
+ {
96
+ Response->SetStringField(TEXT("correlationId"), CorrelationId);
97
+ }
98
+ Response->SetStringField(TEXT("error"), ErrorCode);
99
+
100
+ FString Output;
101
+ TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
102
+ FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
103
+ Output += TEXT("\n");
104
+ SendResponse(Output);
105
+ };
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // input.listActions (INP-01)
109
+ // Returns: { "actions": [{ "name": string, "path": string, "valueType": string }] }
110
+ // Returns empty array (not error) when no assets found.
111
+ // ---------------------------------------------------------------------------
112
+ Router.RegisterHandler(TEXT("input.listActions"),
113
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
114
+ {
115
+ FString CorrelationId;
116
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
117
+
118
+ IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
119
+
120
+ TArray<FAssetData> Assets;
121
+ AR.GetAssetsByClass(UInputAction::StaticClass()->GetClassPathName(), Assets);
122
+
123
+ TArray<TSharedPtr<FJsonValue>> ActionsArray;
124
+ for (const FAssetData& AssetData : Assets)
125
+ {
126
+ UInputAction* Action = Cast<UInputAction>(AssetData.GetAsset());
127
+ if (!Action)
128
+ {
129
+ continue;
130
+ }
131
+
132
+ TSharedPtr<FJsonObject> ActionObj = MakeShared<FJsonObject>();
133
+ ActionObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
134
+ ActionObj->SetStringField(TEXT("path"), AssetData.PackageName.ToString());
135
+ ActionObj->SetStringField(TEXT("valueType"), ValueTypeToString(Action->ValueType));
136
+ ActionsArray.Add(MakeShared<FJsonValueObject>(ActionObj));
137
+ }
138
+
139
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
140
+ Data->SetArrayField(TEXT("actions"), ActionsArray);
141
+ SendSuccess(SendResponse, CorrelationId, Data);
142
+ });
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // input.createAction (INP-02)
146
+ // Payload: { "asset_path": string, "value_type": "bool"|"float"|"Axis2D"|"Axis3D" }
147
+ // Returns: { "assetPath": string, "valueType": string }
148
+ //
149
+ // Security: T-14-01 (path validation via IsValidLongPackageName)
150
+ // ---------------------------------------------------------------------------
151
+ Router.RegisterHandler(TEXT("input.createAction"),
152
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
153
+ {
154
+ FString CorrelationId;
155
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
156
+
157
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
158
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj))
159
+ {
160
+ SendError(SendResponse, CorrelationId, TEXT("missing_payload"));
161
+ return;
162
+ }
163
+
164
+ FString AssetPath;
165
+ if (!(*PayloadObj)->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
166
+ {
167
+ SendError(SendResponse, CorrelationId, TEXT("missing_asset_path"));
168
+ return;
169
+ }
170
+
171
+ FString ValueTypeStr;
172
+ if (!(*PayloadObj)->TryGetStringField(TEXT("value_type"), ValueTypeStr) || ValueTypeStr.IsEmpty())
173
+ {
174
+ SendError(SendResponse, CorrelationId, TEXT("missing_value_type"));
175
+ return;
176
+ }
177
+
178
+ // T-14-01: Validate the asset path is a well-formed long package name.
179
+ // Rejects path traversal ("../") and bare filenames.
180
+ FString ValidationError;
181
+ if (!FPackageName::IsValidLongPackageName(AssetPath, /*bIncludeReadOnlyRoots=*/false, &ValidationError))
182
+ {
183
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] input.createAction: Invalid asset path '%s': %s"),
184
+ *AssetPath, *ValidationError);
185
+ SendError(SendResponse, CorrelationId, TEXT("invalid_asset_path"));
186
+ return;
187
+ }
188
+
189
+ // Map value_type string to EInputActionValueType int.
190
+ const int32 ValueTypeInt = StringToValueTypeInt(ValueTypeStr);
191
+ if (ValueTypeInt < 0)
192
+ {
193
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] input.createAction: Unrecognized value_type '%s'"),
194
+ *ValueTypeStr);
195
+ SendError(SendResponse, CorrelationId, TEXT("invalid_value_type"));
196
+ return;
197
+ }
198
+
199
+ // Extract the asset name from the full package path.
200
+ FString AssetName = FPackageName::GetLongPackageAssetName(AssetPath);
201
+
202
+ // Create (or retrieve) the package for this asset.
203
+ UPackage* Pkg = CreatePackage(*AssetPath);
204
+ if (!Pkg)
205
+ {
206
+ UE_LOG(LogTemp, Error, TEXT("[MCPBridge] input.createAction: Failed to create package for '%s'"),
207
+ *AssetPath);
208
+ SendError(SendResponse, CorrelationId, TEXT("package_create_failed"));
209
+ return;
210
+ }
211
+ Pkg->FullyLoad();
212
+
213
+ // Create the UInputAction asset.
214
+ UInputAction* Action = NewObject<UInputAction>(Pkg, FName(*AssetName), RF_Public | RF_Standalone);
215
+ if (!Action)
216
+ {
217
+ UE_LOG(LogTemp, Error, TEXT("[MCPBridge] input.createAction: NewObject<UInputAction> failed for '%s'"),
218
+ *AssetPath);
219
+ SendError(SendResponse, CorrelationId, TEXT("create_failed"));
220
+ return;
221
+ }
222
+
223
+ // MANDATORY: Modify() before mutation.
224
+ Action->Modify();
225
+ Action->ValueType = static_cast<EInputActionValueType>(ValueTypeInt);
226
+
227
+ // Save the package to disk so the asset persists.
228
+ FString FilePath = FPackageName::LongPackageNameToFilename(
229
+ AssetPath, FPackageName::GetAssetPackageExtension());
230
+ UPackage::SavePackage(Pkg, Action, RF_Standalone, *FilePath,
231
+ GError, nullptr, false, true, SAVE_NoError);
232
+
233
+ // Notify the Asset Registry so the asset appears in the Content Browser.
234
+ FAssetRegistryModule::AssetCreated(Action);
235
+
236
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
237
+ Data->SetStringField(TEXT("assetPath"), AssetPath);
238
+ Data->SetStringField(TEXT("valueType"), ValueTypeStr);
239
+ SendSuccess(SendResponse, CorrelationId, Data);
240
+ });
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // input.listContexts (INP-03)
244
+ // Returns: { "contexts": [{ "name": string, "path": string, "bindings": [...] }] }
245
+ // Each binding: { "action": string, "key": string }
246
+ // ---------------------------------------------------------------------------
247
+ Router.RegisterHandler(TEXT("input.listContexts"),
248
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
249
+ {
250
+ FString CorrelationId;
251
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
252
+
253
+ IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
254
+
255
+ TArray<FAssetData> Assets;
256
+ AR.GetAssetsByClass(UInputMappingContext::StaticClass()->GetClassPathName(), Assets);
257
+
258
+ TArray<TSharedPtr<FJsonValue>> ContextsArray;
259
+ for (const FAssetData& AssetData : Assets)
260
+ {
261
+ UInputMappingContext* IMC = Cast<UInputMappingContext>(AssetData.GetAsset());
262
+ if (!IMC)
263
+ {
264
+ continue;
265
+ }
266
+
267
+ TArray<TSharedPtr<FJsonValue>> BindingsArray;
268
+ const TArray<FEnhancedActionKeyMapping>& Mappings = IMC->GetMappings();
269
+ for (const FEnhancedActionKeyMapping& Mapping : Mappings)
270
+ {
271
+ TSharedPtr<FJsonObject> BindingObj = MakeShared<FJsonObject>();
272
+ BindingObj->SetStringField(TEXT("action"),
273
+ Mapping.Action ? Mapping.Action->GetName() : TEXT(""));
274
+ BindingObj->SetStringField(TEXT("key"),
275
+ Mapping.Key.GetFName().ToString());
276
+ BindingsArray.Add(MakeShared<FJsonValueObject>(BindingObj));
277
+ }
278
+
279
+ TSharedPtr<FJsonObject> ContextObj = MakeShared<FJsonObject>();
280
+ ContextObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
281
+ ContextObj->SetStringField(TEXT("path"), AssetData.PackageName.ToString());
282
+ ContextObj->SetArrayField(TEXT("bindings"), BindingsArray);
283
+ ContextsArray.Add(MakeShared<FJsonValueObject>(ContextObj));
284
+ }
285
+
286
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
287
+ Data->SetArrayField(TEXT("contexts"), ContextsArray);
288
+ SendSuccess(SendResponse, CorrelationId, Data);
289
+ });
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // input.addBinding (INP-04)
293
+ // Payload: { "asset_path": string, "action_path": string, "key": string }
294
+ // Returns: { "bound": true, "action": string, "key": string }
295
+ //
296
+ // Security: T-14-02 (key validated via FKey::IsValid() before MapKey call)
297
+ // ---------------------------------------------------------------------------
298
+ Router.RegisterHandler(TEXT("input.addBinding"),
299
+ [SendSuccess, SendError](TSharedPtr<FJsonObject> Command, FMCPResponseSender SendResponse)
300
+ {
301
+ FString CorrelationId;
302
+ Command->TryGetStringField(TEXT("correlationId"), CorrelationId);
303
+
304
+ const TSharedPtr<FJsonObject>* PayloadObj = nullptr;
305
+ if (!Command->TryGetObjectField(TEXT("payload"), PayloadObj))
306
+ {
307
+ SendError(SendResponse, CorrelationId, TEXT("missing_payload"));
308
+ return;
309
+ }
310
+
311
+ FString AssetPath;
312
+ if (!(*PayloadObj)->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
313
+ {
314
+ SendError(SendResponse, CorrelationId, TEXT("missing_asset_path"));
315
+ return;
316
+ }
317
+
318
+ FString ActionPath;
319
+ if (!(*PayloadObj)->TryGetStringField(TEXT("action_path"), ActionPath) || ActionPath.IsEmpty())
320
+ {
321
+ SendError(SendResponse, CorrelationId, TEXT("missing_action_path"));
322
+ return;
323
+ }
324
+
325
+ FString KeyName;
326
+ if (!(*PayloadObj)->TryGetStringField(TEXT("key"), KeyName) || KeyName.IsEmpty())
327
+ {
328
+ SendError(SendResponse, CorrelationId, TEXT("missing_key"));
329
+ return;
330
+ }
331
+
332
+ // T-14-02: Validate that the key name resolves to a known FKey before calling MapKey.
333
+ // FKey constructor accepts any FName; IsValid() checks whether it is a registered key.
334
+ const FKey ResolvedKey(FName(*KeyName));
335
+ if (!ResolvedKey.IsValid())
336
+ {
337
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] input.addBinding: Key '%s' is not a valid FKey"), *KeyName);
338
+ SendError(SendResponse, CorrelationId, TEXT("invalid_key"));
339
+ return;
340
+ }
341
+
342
+ // Load the UInputMappingContext.
343
+ UInputMappingContext* IMC = LoadObject<UInputMappingContext>(nullptr, *AssetPath);
344
+ if (!IMC)
345
+ {
346
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] input.addBinding: UInputMappingContext not found at '%s'"),
347
+ *AssetPath);
348
+ SendError(SendResponse, CorrelationId, TEXT("context_not_found"));
349
+ return;
350
+ }
351
+
352
+ // Load the UInputAction.
353
+ UInputAction* TargetAction = LoadObject<UInputAction>(nullptr, *ActionPath);
354
+ if (!TargetAction)
355
+ {
356
+ UE_LOG(LogTemp, Warning, TEXT("[MCPBridge] input.addBinding: UInputAction not found at '%s'"),
357
+ *ActionPath);
358
+ SendError(SendResponse, CorrelationId, TEXT("action_not_found"));
359
+ return;
360
+ }
361
+
362
+ // MANDATORY: Modify() before any mutation (per constraint from CONTEXT.md and threat model).
363
+ IMC->Modify();
364
+
365
+ // MapKey adds (or replaces) the binding for this action+key pair.
366
+ IMC->MapKey(TargetAction, ResolvedKey);
367
+
368
+ // Save the package to persist the binding change.
369
+ UPackage* Pkg = IMC->GetPackage();
370
+ FString FilePath = FPackageName::LongPackageNameToFilename(
371
+ AssetPath, FPackageName::GetAssetPackageExtension());
372
+ UPackage::SavePackage(Pkg, IMC, RF_Standalone, *FilePath,
373
+ GError, nullptr, false, true, SAVE_NoError);
374
+
375
+ TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
376
+ Data->SetBoolField(TEXT("bound"), true);
377
+ Data->SetStringField(TEXT("action"), TargetAction->GetName());
378
+ Data->SetStringField(TEXT("key"), KeyName);
379
+ SendSuccess(SendResponse, CorrelationId, Data);
380
+ });
381
+ }
@@ -0,0 +1,24 @@
1
+ // MCPInputHandlers.h
2
+ // Declares the Enhanced Input command handler registration function.
3
+ // Call RegisterInputHandlers() from UMCPBridgeSubsystem::Initialize()
4
+ // after the router is created.
5
+ //
6
+ // Handlers: input.listActions, input.createAction, input.listContexts,
7
+ // input.addBinding
8
+ //
9
+ // IMPORTANT: Every write handler calls Object->Modify() before any mutation
10
+ // and saves the package after -- never skip these.
11
+
12
+ #pragma once
13
+
14
+ #include "CoreMinimal.h"
15
+
16
+ class FMCPCommandRouter;
17
+
18
+ /**
19
+ * Register input.listActions, input.createAction, input.listContexts,
20
+ * and input.addBinding handlers on the given router.
21
+ * All handlers run on the game thread (enforced by FMCPCommandRouter::Dispatch
22
+ * via AsyncTask).
23
+ */
24
+ void RegisterInputHandlers(FMCPCommandRouter& Router);