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,276 @@
1
+ // src/generators/file-modifier.ts
2
+ // Pure in-memory string transformations for modifying existing UE C++ header files.
3
+ //
4
+ // GEN-05: addProperty — inserts UPROPERTY declaration after last existing UPROPERTY
5
+ // GEN-06: addFunction — inserts UFUNCTION declaration after last existing UFUNCTION
6
+ // GEN-07: addInclude — inserts #include before .generated.h line (idempotent)
7
+ // GEN-08: all modifications validated via validateUhtRules before returning
8
+ //
9
+ // T-05-06 mitigation: all output passes through validateUhtRules (specifier/name integrity).
10
+ // T-05-08 mitigation: TDD suite covers insertion edge cases.
11
+ //
12
+ // Constraints:
13
+ // - Pure functions — no fs, no async, no side effects
14
+ // - Never throws — always returns ModifyResult
15
+ // - console.error only (never console.log)
16
+ import { parseCppFile } from '../parsers/cpp-parser.js';
17
+ import { validateUhtRules } from './uht-validator.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Internal helpers
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Inserts `toInsert` lines after the given 0-based line index.
23
+ */
24
+ function insertLinesAt(lines, afterLineIndex, toInsert) {
25
+ return [
26
+ ...lines.slice(0, afterLineIndex + 1),
27
+ ...toInsert,
28
+ ...lines.slice(afterLineIndex + 1),
29
+ ];
30
+ }
31
+ /**
32
+ * Validates modified content with UHT rules and returns ModifyResult.
33
+ */
34
+ function validateAndReturn(content) {
35
+ const validation = validateUhtRules(content);
36
+ if (!validation.valid) {
37
+ return { success: false, error: validation.errors.join('; ') };
38
+ }
39
+ return { success: true, content };
40
+ }
41
+ /**
42
+ * Find the 0-based line index of GENERATED_BODY() starting from startLine (0-based).
43
+ * Returns -1 if not found within the search range.
44
+ */
45
+ function findGeneratedBodyLine(lines, startLine) {
46
+ for (let i = startLine; i < lines.length; i++) {
47
+ const ln = lines[i] ?? '';
48
+ if (ln.includes('GENERATED_BODY()')) {
49
+ return i;
50
+ }
51
+ // Stop searching if we hit a class-ending brace at depth 0
52
+ // (simple heuristic: if line is just '}' or '};' after the opening brace)
53
+ if (i > startLine + 2 && (ln.trim() === '}' || ln.trim() === '};')) {
54
+ break;
55
+ }
56
+ }
57
+ return -1;
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Public functions
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Adds a UPROPERTY declaration to an existing class in the C++ header content.
64
+ *
65
+ * Algorithm:
66
+ * 1. Parse content — error if parse fails.
67
+ * 2. Find class by name — error if not found.
68
+ * 3. If class has existing properties: insert after the last property's declaration line.
69
+ * If not: insert after GENERATED_BODY() line.
70
+ * 4. Validate with validateUhtRules before returning.
71
+ *
72
+ * GEN-05, GEN-08
73
+ */
74
+ export function addProperty(content, className, decl) {
75
+ try {
76
+ const parseResult = parseCppFile(content);
77
+ if (!parseResult.success) {
78
+ return { success: false, error: `Parse error: ${parseResult.error}` };
79
+ }
80
+ const cls = parseResult.data.classes.find((c) => c.name === className);
81
+ if (!cls) {
82
+ return { success: false, error: `Class '${className}' not found in file` };
83
+ }
84
+ const lines = content.split('\n');
85
+ // Build the insertion block (tab-indented, UE style)
86
+ const specStr = decl.specifiers.join(', ');
87
+ const defaultSuffix = decl.defaultValue ? ` = ${decl.defaultValue}` : '';
88
+ const insertBlock = [
89
+ `\tUPROPERTY(${specStr})`,
90
+ `\t${decl.type} ${decl.name}${defaultSuffix};`,
91
+ `\t`,
92
+ ];
93
+ let insertAfterIndex;
94
+ if (cls.properties.length > 0) {
95
+ // Insert after the declaration line of the last property.
96
+ // prop.line is 1-based line of the UPROPERTY macro.
97
+ // Declaration is on the next line: prop.line + 1 (1-based) = prop.line (0-based).
98
+ const lastProp = cls.properties[cls.properties.length - 1];
99
+ // 0-based index of declaration line = lastProp.line (since macro is at lastProp.line - 1 in 0-based,
100
+ // declaration is at lastProp.line in 0-based)
101
+ insertAfterIndex = lastProp.line; // 0-based index of the property declaration line
102
+ }
103
+ else {
104
+ // No properties — find GENERATED_BODY() and insert after it
105
+ // cls.line is 1-based class declaration line, convert to 0-based
106
+ const classLineIdx = cls.line - 1;
107
+ const genBodyIdx = findGeneratedBodyLine(lines, classLineIdx);
108
+ if (genBodyIdx < 0) {
109
+ return { success: false, error: `GENERATED_BODY() not found in class '${className}'` };
110
+ }
111
+ insertAfterIndex = genBodyIdx;
112
+ }
113
+ const newLines = insertLinesAt(lines, insertAfterIndex, insertBlock);
114
+ const newContent = newLines.join('\n');
115
+ return validateAndReturn(newContent);
116
+ }
117
+ catch (err) {
118
+ const msg = err instanceof Error ? err.message : String(err);
119
+ console.error(`[file-modifier] addProperty error: ${msg}`);
120
+ return { success: false, error: `Internal error: ${msg}` };
121
+ }
122
+ }
123
+ /**
124
+ * Adds a UFUNCTION declaration to an existing class in the C++ header content.
125
+ *
126
+ * Algorithm:
127
+ * 1. Parse content — error if parse fails.
128
+ * 2. Find class by name — error if not found.
129
+ * 3. If class has existing functions: insert after the last function's declaration line.
130
+ * If not: insert after GENERATED_BODY() line.
131
+ * 4. Validate with validateUhtRules before returning.
132
+ *
133
+ * GEN-06, GEN-08
134
+ */
135
+ export function addFunction(content, className, decl) {
136
+ try {
137
+ const parseResult = parseCppFile(content);
138
+ if (!parseResult.success) {
139
+ return { success: false, error: `Parse error: ${parseResult.error}` };
140
+ }
141
+ const cls = parseResult.data.classes.find((c) => c.name === className);
142
+ if (!cls) {
143
+ return { success: false, error: `Class '${className}' not found in file` };
144
+ }
145
+ const lines = content.split('\n');
146
+ // Build the insertion block (tab-indented, UE style)
147
+ const specStr = decl.specifiers.join(', ');
148
+ const paramStr = decl.params ?? '';
149
+ const constSuffix = decl.isConst ? ' const' : '';
150
+ const insertBlock = [
151
+ `\tUFUNCTION(${specStr})`,
152
+ `\t${decl.returnType} ${decl.name}(${paramStr})${constSuffix};`,
153
+ `\t`,
154
+ ];
155
+ let insertAfterIndex;
156
+ if (cls.functions.length > 0) {
157
+ // Insert after the declaration line of the last function.
158
+ // fn.line is 1-based line of the UFUNCTION macro.
159
+ // Declaration is on the next line: fn.line + 1 (1-based) = fn.line (0-based).
160
+ const lastFn = cls.functions[cls.functions.length - 1];
161
+ insertAfterIndex = lastFn.line; // 0-based index of the function declaration line
162
+ }
163
+ else {
164
+ // No functions — find GENERATED_BODY() and insert after it
165
+ const classLineIdx = cls.line - 1;
166
+ const genBodyIdx = findGeneratedBodyLine(lines, classLineIdx);
167
+ if (genBodyIdx < 0) {
168
+ return { success: false, error: `GENERATED_BODY() not found in class '${className}'` };
169
+ }
170
+ insertAfterIndex = genBodyIdx;
171
+ }
172
+ const newLines = insertLinesAt(lines, insertAfterIndex, insertBlock);
173
+ const newContent = newLines.join('\n');
174
+ return validateAndReturn(newContent);
175
+ }
176
+ catch (err) {
177
+ const msg = err instanceof Error ? err.message : String(err);
178
+ console.error(`[file-modifier] addFunction error: ${msg}`);
179
+ return { success: false, error: `Internal error: ${msg}` };
180
+ }
181
+ }
182
+ /**
183
+ * Adds an #include directive to a C++ header file.
184
+ *
185
+ * Algorithm:
186
+ * 1. Parse content — error if parse fails.
187
+ * 2. Check for duplicate — if include already present, return unchanged content (idempotent).
188
+ * 3. Find .generated.h line — if present, insert before it.
189
+ * Otherwise insert after last #include, or after #pragma once if no includes.
190
+ * 4. Validate with validateUhtRules before returning.
191
+ *
192
+ * GEN-07, GEN-08
193
+ */
194
+ export function addInclude(content, includePath) {
195
+ try {
196
+ const parseResult = parseCppFile(content);
197
+ if (!parseResult.success) {
198
+ return { success: false, error: `Parse error: ${parseResult.error}` };
199
+ }
200
+ const { includes } = parseResult.data;
201
+ // Normalize the includePath for duplicate checking.
202
+ // Strip surrounding quotes or angle brackets.
203
+ const normalizedPath = includePath.replace(/^["<]/, '').replace(/[">]$/, '');
204
+ // Check if already present (idempotent)
205
+ const alreadyPresent = includes.some((inc) => {
206
+ // Parser strips angle brackets for angle-bracket includes, adds '<>' prefix
207
+ // For quoted includes, the parser stores just the path without quotes
208
+ const normalizedInc = inc.startsWith('<') ? inc.slice(1, -1) : inc;
209
+ return normalizedInc === normalizedPath;
210
+ });
211
+ if (alreadyPresent) {
212
+ return { success: true, content };
213
+ }
214
+ // Determine the formatted include directive
215
+ const isAngleBracket = includePath.startsWith('<') && includePath.endsWith('>');
216
+ let directive;
217
+ if (isAngleBracket) {
218
+ directive = `#include ${includePath}`;
219
+ }
220
+ else {
221
+ // If path already has quotes, use as-is; otherwise add quotes
222
+ if (includePath.startsWith('"') && includePath.endsWith('"')) {
223
+ directive = `#include ${includePath}`;
224
+ }
225
+ else {
226
+ directive = `#include "${includePath}"`;
227
+ }
228
+ }
229
+ const lines = content.split('\n');
230
+ // Find the .generated.h line index (0-based)
231
+ let generatedHLineIdx = -1;
232
+ let lastIncludeLineIdx = -1;
233
+ let pragmaOnceLineIdx = -1;
234
+ for (let i = 0; i < lines.length; i++) {
235
+ const ln = lines[i] ?? '';
236
+ const trimmed = ln.trim();
237
+ if (trimmed.startsWith('#pragma once')) {
238
+ pragmaOnceLineIdx = i;
239
+ }
240
+ if (trimmed.startsWith('#include')) {
241
+ lastIncludeLineIdx = i;
242
+ if (trimmed.includes('.generated.h')) {
243
+ generatedHLineIdx = i;
244
+ }
245
+ }
246
+ }
247
+ let newLines;
248
+ if (generatedHLineIdx >= 0) {
249
+ // Insert before the .generated.h line
250
+ newLines = [
251
+ ...lines.slice(0, generatedHLineIdx),
252
+ directive,
253
+ ...lines.slice(generatedHLineIdx),
254
+ ];
255
+ }
256
+ else if (lastIncludeLineIdx >= 0) {
257
+ // Insert after the last #include line
258
+ newLines = insertLinesAt(lines, lastIncludeLineIdx, [directive]);
259
+ }
260
+ else if (pragmaOnceLineIdx >= 0) {
261
+ // Insert after #pragma once
262
+ newLines = insertLinesAt(lines, pragmaOnceLineIdx, [directive]);
263
+ }
264
+ else {
265
+ // Insert at the beginning
266
+ newLines = [directive, ...lines];
267
+ }
268
+ const newContent = newLines.join('\n');
269
+ return validateAndReturn(newContent);
270
+ }
271
+ catch (err) {
272
+ const msg = err instanceof Error ? err.message : String(err);
273
+ console.error(`[file-modifier] addInclude error: ${msg}`);
274
+ return { success: false, error: `Internal error: ${msg}` };
275
+ }
276
+ }
@@ -0,0 +1,177 @@
1
+ // src/generators/uht-validator.ts
2
+ // Pure UHT pre-validation for generated or modified C++ header content.
3
+ // GEN-08: validates content against UHT rules before any disk write.
4
+ //
5
+ // Rules enforced:
6
+ // 1. GENERATED_BODY() present in every UCLASS body
7
+ // 2. ClassName.generated.h must be the last #include
8
+ // 3. No duplicate UPROPERTY names within a class
9
+ // 4. No duplicate UFUNCTION names within a class
10
+ // 5. UPROPERTY specifiers must be from the known allowlist (bare word specifiers only)
11
+ //
12
+ // Never throws — always returns UhtValidationResult.
13
+ // Pure function — no fs, no async, no side effects.
14
+ //
15
+ // T-05-01 mitigation: parseCppFile already caps balanced-paren search at 500 lines.
16
+ // The line-scan in this module is O(n) with no backtracking — safe for large input.
17
+ import { parseCppFile } from '../parsers/cpp-parser.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Constants
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Known valid bare-word UPROPERTY specifiers.
23
+ * Compound specifiers (containing '=' or '(') are always allowed and not checked here.
24
+ */
25
+ const VALID_UPROPERTY_SPECIFIERS = new Set([
26
+ 'EditAnywhere',
27
+ 'EditDefaultsOnly',
28
+ 'EditInstanceOnly',
29
+ 'VisibleAnywhere',
30
+ 'VisibleDefaultsOnly',
31
+ 'VisibleInstanceOnly',
32
+ 'BlueprintReadWrite',
33
+ 'BlueprintReadOnly',
34
+ 'BlueprintGetter',
35
+ 'BlueprintSetter',
36
+ 'Category',
37
+ 'DisplayName',
38
+ 'AdvancedDisplay',
39
+ 'SaveGame',
40
+ 'Transient',
41
+ 'Replicated',
42
+ 'ReplicatedUsing',
43
+ 'NotReplicated',
44
+ 'Config',
45
+ 'GlobalConfig',
46
+ 'Localized',
47
+ 'Export',
48
+ 'NoClear',
49
+ 'EditFixedSize',
50
+ 'meta',
51
+ 'AllowPrivateAccess',
52
+ ]);
53
+ // ---------------------------------------------------------------------------
54
+ // Main export
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Validates generated or modified C++ header content against UHT rules.
58
+ *
59
+ * @param content - Raw C++ header file content as a string
60
+ * @returns UhtValidationResult with valid flag and collected errors
61
+ *
62
+ * Never throws. Returns { valid: true, errors: [] } for empty or no-UCLASS content.
63
+ */
64
+ export function validateUhtRules(content) {
65
+ const errors = [];
66
+ try {
67
+ // Guard: null/undefined or whitespace-only input is considered valid (nothing to check)
68
+ if (!content || typeof content !== 'string' || !content.trim()) {
69
+ return { valid: true, errors: [] };
70
+ }
71
+ const parseResult = parseCppFile(content);
72
+ if (!parseResult.success) {
73
+ return {
74
+ valid: false,
75
+ errors: [`UHT validator: failed to parse C++ content: ${parseResult.error}`],
76
+ };
77
+ }
78
+ const { classes, includes } = parseResult.data;
79
+ // No UCLASS declarations found — nothing to validate
80
+ if (classes.length === 0) {
81
+ return { valid: true, errors: [] };
82
+ }
83
+ // -------------------------------------------------------------------------
84
+ // Rule 2: generated.h must be the LAST include
85
+ // -------------------------------------------------------------------------
86
+ // Find the last include that ends with '.generated.h'
87
+ const generatedIdx = includes.findIndex((inc) => inc.endsWith('.generated.h'));
88
+ if (generatedIdx >= 0 && generatedIdx !== includes.length - 1) {
89
+ errors.push(`${includes[generatedIdx]} must be the last #include in the file (UHT requirement)`);
90
+ }
91
+ // -------------------------------------------------------------------------
92
+ // Per-class rules
93
+ // -------------------------------------------------------------------------
94
+ const lines = content.split('\n');
95
+ for (const cls of classes) {
96
+ const className = cls.name;
97
+ // -----------------------------------------------------------------------
98
+ // Rule 1: GENERATED_BODY() must appear in the class body
99
+ // Search from the class declaration line forward, stopping when the
100
+ // top-level class brace closes.
101
+ // cls.line is 1-based; convert to 0-based index.
102
+ // -----------------------------------------------------------------------
103
+ const classStartLine = cls.line - 1; // 0-based
104
+ let foundGenBody = false;
105
+ let braceDepth = 0;
106
+ let inBody = false;
107
+ let bodyDone = false;
108
+ for (let i = classStartLine; i < lines.length && !bodyDone; i++) {
109
+ const ln = lines[i] ?? '';
110
+ // Check for GENERATED_BODY() on this line before brace accounting
111
+ if (ln.includes('GENERATED_BODY()')) {
112
+ foundGenBody = true;
113
+ }
114
+ // Track brace depth to find the end of the class body
115
+ for (const ch of ln) {
116
+ if (ch === '{') {
117
+ braceDepth++;
118
+ inBody = true;
119
+ }
120
+ else if (ch === '}') {
121
+ braceDepth--;
122
+ if (inBody && braceDepth <= 0) {
123
+ bodyDone = true;
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ if (!foundGenBody) {
130
+ errors.push(`Class ${className}: GENERATED_BODY() macro is missing from class body`);
131
+ }
132
+ // -----------------------------------------------------------------------
133
+ // Rule 3: No duplicate UPROPERTY names within a class
134
+ // -----------------------------------------------------------------------
135
+ const seenPropNames = new Set();
136
+ for (const prop of cls.properties) {
137
+ if (seenPropNames.has(prop.name)) {
138
+ errors.push(`Class ${className}: duplicate UPROPERTY name '${prop.name}'`);
139
+ }
140
+ else {
141
+ seenPropNames.add(prop.name);
142
+ }
143
+ }
144
+ // -----------------------------------------------------------------------
145
+ // Rule 4: No duplicate UFUNCTION names within a class
146
+ // -----------------------------------------------------------------------
147
+ const seenFnNames = new Set();
148
+ for (const fn of cls.functions) {
149
+ if (seenFnNames.has(fn.name)) {
150
+ errors.push(`Class ${className}: duplicate UFUNCTION name '${fn.name}'`);
151
+ }
152
+ else {
153
+ seenFnNames.add(fn.name);
154
+ }
155
+ }
156
+ // -----------------------------------------------------------------------
157
+ // Rule 5: UPROPERTY specifier allowlist (bare-word specifiers only)
158
+ // Compound specifiers (containing '=' or '(') are always allowed.
159
+ // -----------------------------------------------------------------------
160
+ for (const prop of cls.properties) {
161
+ for (const spec of prop.specifiers) {
162
+ // Skip compound specifiers — they contain '=' or '(' and are valid by convention
163
+ if (spec.includes('=') || spec.includes('('))
164
+ continue;
165
+ if (!VALID_UPROPERTY_SPECIFIERS.has(spec)) {
166
+ errors.push(`Class ${className}, property ${prop.name}: unknown UPROPERTY specifier '${spec}'`);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ catch (err) {
173
+ const msg = err instanceof Error ? err.message : String(err);
174
+ return { valid: false, errors: [`UHT validator internal error: ${msg}`] };
175
+ }
176
+ return { valid: errors.length === 0, errors };
177
+ }
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ // src/index.ts
2
+ // MCP server entry point for the Ultimate Unreal Engine MCP.
3
+ // Registers all domain tool modules and connects the StdioServerTransport.
4
+ //
5
+ // Logging: ALL output goes to stderr via logger.ts — never console.log.
6
+ // Reason: stdout is the MCP JSON-RPC channel; any non-JSON stdout output
7
+ // corrupts the protocol stream and disconnects the client (PITFALLS.md Pitfall 8).
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { SERVER_VERSION, PLUGIN_PORT } from './config.js';
11
+ import { log } from './utils/logger.js';
12
+ import { registerKnownIssuesTools } from './tools/known-issues/index.js';
13
+ import { registerCppTools } from './tools/cpp/index.js';
14
+ import { registerConfigTools } from './tools/config/index.js';
15
+ import { registerDocsTools } from './tools/docs/index.js';
16
+ import { registerBuildTools } from './tools/build/index.js';
17
+ import { registerBlueprintTools } from './tools/blueprint/index.js';
18
+ import { registerEditorTools } from './tools/editor/index.js';
19
+ import { registerViewportTools } from './tools/viewport/index.js';
20
+ import { registerSequencerTools } from './tools/sequencer/index.js';
21
+ import { registerInputTools } from './tools/input/index.js';
22
+ import { registerMaterialTools } from './tools/material/index.js';
23
+ import { registerValidationTools } from './tools/validation/index.js';
24
+ import { registerAnimationTools } from './tools/animation/index.js';
25
+ import { registerWorldPartitionTools } from './tools/worldpartition/index.js';
26
+ import { registerSelectionTools } from './tools/selection/index.js';
27
+ import { registerCollisionPhysicsTools } from './tools/collision-physics/index.js';
28
+ import { registerImportExportTools } from './tools/import-export/index.js';
29
+ import { registerAISystemsTools } from './tools/ai-systems/index.js';
30
+ import { registerLiveLinkTools } from './tools/livelink/index.js';
31
+ import { registerAudioTools } from './tools/audio/index.js';
32
+ import { registerPCGTools } from './tools/pcg/index.js';
33
+ import { registerGASTools } from './tools/gas/index.js';
34
+ import { registerChaosTools } from './tools/chaos/index.js';
35
+ import { registerMotionDesignTools } from './tools/motion-design/index.js';
36
+ import { registerMovieRenderTools } from './tools/movie-render/index.js';
37
+ import { registerNetworkingTools } from './tools/networking/index.js';
38
+ // ---------------------------------------------------------------------------
39
+ // Main async IIFE — wraps the full startup sequence.
40
+ // Any unhandled error triggers a clean process.exit(1) with a stderr message.
41
+ // ---------------------------------------------------------------------------
42
+ (async () => {
43
+ log('Starting ultimate-unreal-engine-mcp v' + SERVER_VERSION);
44
+ log('Plugin connection will be attempted on port ' + PLUGIN_PORT);
45
+ const server = new McpServer({
46
+ name: 'ultimate-unreal-engine-mcp',
47
+ version: SERVER_VERSION,
48
+ });
49
+ // Register all domain tool modules in dependency order:
50
+ // 1. KNOWN_ISSUES tools (infrastructure — must come first so other tools can surface issues)
51
+ // 2. File-system tool domains (no plugin dependency)
52
+ // 3. Plugin-dependent tool domains (return structured errors when plugin is absent)
53
+ registerKnownIssuesTools(server);
54
+ registerCppTools(server);
55
+ registerConfigTools(server);
56
+ registerDocsTools(server);
57
+ registerBuildTools(server);
58
+ registerBlueprintTools(server);
59
+ registerEditorTools(server);
60
+ registerViewportTools(server);
61
+ registerSequencerTools(server);
62
+ registerInputTools(server);
63
+ registerMaterialTools(server); // Material & Shader tools (Phase 15)
64
+ // Validation tools (VAL-01..04)
65
+ registerValidationTools(server);
66
+ registerAnimationTools(server); // Animation tools (ANIM-01..05)
67
+ registerWorldPartitionTools(server); // World Partition tools (WP-01..04)
68
+ registerSelectionTools(server); // Selection tools (SEL-01..04)
69
+ registerCollisionPhysicsTools(server); // Collision & Physics tools (PHY-01..04)
70
+ registerImportExportTools(server); // Import/Export tools (IMP-01..04)
71
+ registerAISystemsTools(server); // AI system tools (AI-01..05)
72
+ registerLiveLinkTools(server); // Live Link tools (LL-01..04)
73
+ registerAudioTools(server); // Audio tools (AUD-01..04)
74
+ registerPCGTools(server); // PCG Framework tools (PCG-01..04)
75
+ registerGASTools(server); // GAS tools (GAS-01..04)
76
+ registerChaosTools(server); // Chaos physics tools (CHAOS-01..04)
77
+ registerMotionDesignTools(server); // Motion Design tools (MD-01..04)
78
+ registerMovieRenderTools(server); // Movie Render Pipeline tools (MRP-01..04)
79
+ registerNetworkingTools(server); // Networking & Replication tools (NET-01..04)
80
+ const transport = new StdioServerTransport();
81
+ await server.connect(transport);
82
+ log('Server ready — connected via stdio');
83
+ })().catch((err) => {
84
+ // Fatal startup error — log to stderr and exit with non-zero code.
85
+ // Do NOT rethrow: unhandled rejection leaves the process in limbo,
86
+ // which can cause the MCP host to wait indefinitely for a response.
87
+ console.error('[UE MCP] Fatal startup error:', err);
88
+ process.exit(1);
89
+ });