unreal-engine-mcp-server 0.5.2 → 0.5.4

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 (98) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +9 -6
  3. package/dist/automation/bridge.d.ts +1 -0
  4. package/dist/automation/bridge.js +62 -4
  5. package/dist/automation/types.d.ts +1 -0
  6. package/dist/config/class-aliases.d.ts +5 -0
  7. package/dist/config/class-aliases.js +30 -0
  8. package/dist/constants.d.ts +5 -0
  9. package/dist/constants.js +5 -0
  10. package/dist/graphql/server.d.ts +0 -1
  11. package/dist/graphql/server.js +15 -16
  12. package/dist/index.js +1 -1
  13. package/dist/services/metrics-server.d.ts +2 -1
  14. package/dist/services/metrics-server.js +29 -4
  15. package/dist/tools/consolidated-tool-definitions.js +3 -3
  16. package/dist/tools/debug.d.ts +5 -0
  17. package/dist/tools/debug.js +7 -0
  18. package/dist/tools/handlers/actor-handlers.js +4 -27
  19. package/dist/tools/handlers/asset-handlers.js +13 -1
  20. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -1
  21. package/dist/tools/handlers/common-handlers.d.ts +11 -11
  22. package/dist/tools/handlers/common-handlers.js +6 -4
  23. package/dist/tools/handlers/editor-handlers.d.ts +2 -1
  24. package/dist/tools/handlers/editor-handlers.js +6 -6
  25. package/dist/tools/handlers/effect-handlers.js +3 -0
  26. package/dist/tools/handlers/graph-handlers.d.ts +2 -1
  27. package/dist/tools/handlers/graph-handlers.js +1 -1
  28. package/dist/tools/handlers/input-handlers.d.ts +5 -1
  29. package/dist/tools/handlers/level-handlers.d.ts +2 -1
  30. package/dist/tools/handlers/level-handlers.js +3 -3
  31. package/dist/tools/handlers/lighting-handlers.d.ts +2 -1
  32. package/dist/tools/handlers/lighting-handlers.js +3 -0
  33. package/dist/tools/handlers/pipeline-handlers.d.ts +2 -1
  34. package/dist/tools/handlers/pipeline-handlers.js +64 -10
  35. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  36. package/dist/tools/handlers/system-handlers.d.ts +1 -1
  37. package/dist/tools/input.d.ts +5 -1
  38. package/dist/tools/input.js +37 -1
  39. package/dist/tools/lighting.d.ts +1 -0
  40. package/dist/tools/lighting.js +7 -0
  41. package/dist/tools/physics.d.ts +1 -1
  42. package/dist/tools/sequence.d.ts +1 -0
  43. package/dist/tools/sequence.js +7 -0
  44. package/dist/types/handler-types.d.ts +343 -0
  45. package/dist/types/handler-types.js +2 -0
  46. package/dist/unreal-bridge.d.ts +1 -1
  47. package/dist/unreal-bridge.js +8 -6
  48. package/dist/utils/command-validator.d.ts +1 -0
  49. package/dist/utils/command-validator.js +11 -1
  50. package/dist/utils/error-handler.js +3 -1
  51. package/dist/utils/response-validator.js +2 -2
  52. package/dist/utils/safe-json.d.ts +1 -1
  53. package/dist/utils/safe-json.js +3 -6
  54. package/dist/utils/unreal-command-queue.js +1 -1
  55. package/dist/utils/validation.js +6 -2
  56. package/docs/handler-mapping.md +6 -1
  57. package/package.json +2 -2
  58. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +25 -1
  59. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +40 -58
  60. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +27 -46
  61. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +16 -1
  62. package/server.json +2 -2
  63. package/src/automation/bridge.ts +80 -10
  64. package/src/automation/types.ts +1 -0
  65. package/src/config/class-aliases.ts +65 -0
  66. package/src/constants.ts +10 -0
  67. package/src/graphql/server.ts +23 -23
  68. package/src/index.ts +1 -1
  69. package/src/services/metrics-server.ts +40 -6
  70. package/src/tools/consolidated-tool-definitions.ts +3 -3
  71. package/src/tools/debug.ts +8 -0
  72. package/src/tools/handlers/actor-handlers.ts +5 -31
  73. package/src/tools/handlers/asset-handlers.ts +19 -1
  74. package/src/tools/handlers/blueprint-handlers.ts +1 -1
  75. package/src/tools/handlers/common-handlers.ts +32 -11
  76. package/src/tools/handlers/editor-handlers.ts +8 -7
  77. package/src/tools/handlers/effect-handlers.ts +4 -0
  78. package/src/tools/handlers/graph-handlers.ts +7 -6
  79. package/src/tools/handlers/level-handlers.ts +5 -4
  80. package/src/tools/handlers/lighting-handlers.ts +5 -1
  81. package/src/tools/handlers/pipeline-handlers.ts +83 -16
  82. package/src/tools/input.ts +60 -1
  83. package/src/tools/lighting.ts +11 -0
  84. package/src/tools/physics.ts +1 -1
  85. package/src/tools/sequence.ts +11 -0
  86. package/src/types/handler-types.ts +442 -0
  87. package/src/unreal-bridge.ts +8 -6
  88. package/src/utils/command-validator.ts +23 -1
  89. package/src/utils/error-handler.ts +4 -1
  90. package/src/utils/response-validator.ts +7 -9
  91. package/src/utils/safe-json.ts +20 -15
  92. package/src/utils/unreal-command-queue.ts +3 -1
  93. package/src/utils/validation.test.ts +3 -3
  94. package/src/utils/validation.ts +36 -26
  95. package/tests/test-console-command.mjs +1 -1
  96. package/tests/test-runner.mjs +63 -3
  97. package/tests/run-unreal-tool-tests.mjs +0 -948
  98. package/tests/test-asset-errors.mjs +0 -35
@@ -0,0 +1,343 @@
1
+ export interface Vector3 {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ }
6
+ export interface Rotator {
7
+ pitch: number;
8
+ yaw: number;
9
+ roll: number;
10
+ }
11
+ export interface Transform {
12
+ location?: Vector3;
13
+ rotation?: Rotator;
14
+ scale?: Vector3;
15
+ }
16
+ export interface HandlerArgs {
17
+ action?: string;
18
+ subAction?: string;
19
+ [key: string]: unknown;
20
+ }
21
+ export interface AutomationResponse {
22
+ success: boolean;
23
+ message?: string;
24
+ error?: string;
25
+ result?: unknown;
26
+ [key: string]: unknown;
27
+ }
28
+ export interface ComponentInfo {
29
+ name: string;
30
+ class?: string;
31
+ objectPath?: string;
32
+ [key: string]: unknown;
33
+ }
34
+ export interface ActorArgs extends HandlerArgs {
35
+ actorName?: string;
36
+ name?: string;
37
+ classPath?: string;
38
+ class?: string;
39
+ type?: string;
40
+ location?: Vector3;
41
+ rotation?: Rotator;
42
+ scale?: Vector3;
43
+ meshPath?: string;
44
+ timeoutMs?: number;
45
+ force?: Vector3;
46
+ parentActor?: string;
47
+ childActor?: string;
48
+ tag?: string;
49
+ newName?: string;
50
+ offset?: Vector3;
51
+ visible?: boolean;
52
+ componentName?: string;
53
+ componentType?: string;
54
+ properties?: Record<string, unknown>;
55
+ }
56
+ export interface AssetArgs extends HandlerArgs {
57
+ assetPath?: string;
58
+ path?: string;
59
+ directory?: string;
60
+ directoryPath?: string;
61
+ sourcePath?: string;
62
+ destinationPath?: string;
63
+ newName?: string;
64
+ name?: string;
65
+ filter?: string;
66
+ recursive?: boolean;
67
+ overwrite?: boolean;
68
+ classNames?: string[];
69
+ packagePaths?: string[];
70
+ parentMaterial?: string;
71
+ parameters?: Record<string, unknown>;
72
+ assetPaths?: string[];
73
+ meshPath?: string;
74
+ }
75
+ export interface BlueprintArgs extends HandlerArgs {
76
+ blueprintPath?: string;
77
+ name?: string;
78
+ savePath?: string;
79
+ blueprintType?: string;
80
+ componentType?: string;
81
+ componentName?: string;
82
+ attachTo?: string;
83
+ variableName?: string;
84
+ eventType?: string;
85
+ customEventName?: string;
86
+ nodeType?: string;
87
+ graphName?: string;
88
+ x?: number;
89
+ y?: number;
90
+ memberName?: string;
91
+ nodeId?: string;
92
+ pinName?: string;
93
+ linkedTo?: string;
94
+ fromNodeId?: string;
95
+ fromPin?: string;
96
+ fromPinName?: string;
97
+ toNodeId?: string;
98
+ toPin?: string;
99
+ toPinName?: string;
100
+ propertyName?: string;
101
+ value?: unknown;
102
+ properties?: Record<string, unknown>;
103
+ compile?: boolean;
104
+ save?: boolean;
105
+ metadata?: Record<string, unknown>;
106
+ }
107
+ export interface EditorArgs extends HandlerArgs {
108
+ command?: string;
109
+ filename?: string;
110
+ resolution?: string;
111
+ location?: Vector3;
112
+ rotation?: Rotator;
113
+ fov?: number;
114
+ speed?: number;
115
+ viewMode?: string;
116
+ width?: number;
117
+ height?: number;
118
+ enabled?: boolean;
119
+ realtime?: boolean;
120
+ bookmarkName?: string;
121
+ assetPath?: string;
122
+ path?: string;
123
+ category?: string;
124
+ preferences?: Record<string, unknown>;
125
+ timeoutMs?: number;
126
+ }
127
+ export interface LevelArgs extends HandlerArgs {
128
+ levelPath?: string;
129
+ levelName?: string;
130
+ levelPaths?: string[];
131
+ destinationPath?: string;
132
+ savePath?: string;
133
+ subLevelPath?: string;
134
+ parentLevel?: string;
135
+ parentPath?: string;
136
+ streamingMethod?: 'Blueprint' | 'AlwaysLoaded';
137
+ exportPath?: string;
138
+ packagePath?: string;
139
+ sourcePath?: string;
140
+ lightType?: 'Directional' | 'Point' | 'Spot' | 'Rect';
141
+ name?: string;
142
+ location?: Vector3;
143
+ rotation?: Rotator;
144
+ intensity?: number;
145
+ color?: number[];
146
+ quality?: string;
147
+ streaming?: boolean;
148
+ shouldBeLoaded?: boolean;
149
+ shouldBeVisible?: boolean;
150
+ dataLayerLabel?: string;
151
+ dataLayerName?: string;
152
+ dataLayerState?: string;
153
+ actorPath?: string;
154
+ min?: number[];
155
+ max?: number[];
156
+ origin?: number[];
157
+ extent?: number[];
158
+ metadata?: Record<string, unknown>;
159
+ timeoutMs?: number;
160
+ }
161
+ export interface SequenceArgs extends HandlerArgs {
162
+ path?: string;
163
+ name?: string;
164
+ actorName?: string;
165
+ actorNames?: string[];
166
+ spawnable?: boolean;
167
+ trackName?: string;
168
+ trackType?: string;
169
+ property?: string;
170
+ frame?: number;
171
+ value?: unknown;
172
+ speed?: number;
173
+ lengthInFrames?: number;
174
+ start?: number;
175
+ end?: number;
176
+ startFrame?: number;
177
+ endFrame?: number;
178
+ assetPath?: string;
179
+ muted?: boolean;
180
+ solo?: boolean;
181
+ locked?: boolean;
182
+ }
183
+ export interface EffectArgs extends HandlerArgs {
184
+ location?: Vector3;
185
+ rotation?: Rotator;
186
+ scale?: number;
187
+ preset?: string;
188
+ systemPath?: string;
189
+ shape?: string;
190
+ size?: number;
191
+ color?: number[];
192
+ name?: string;
193
+ emitterName?: string;
194
+ modulePath?: string;
195
+ parameterName?: string;
196
+ parameterType?: string;
197
+ type?: string;
198
+ filter?: string;
199
+ }
200
+ export interface EnvironmentArgs extends HandlerArgs {
201
+ name?: string;
202
+ landscapeName?: string;
203
+ location?: Vector3;
204
+ scale?: Vector3;
205
+ componentCount?: {
206
+ x: number;
207
+ y: number;
208
+ };
209
+ sectionSize?: number;
210
+ sectionsPerComponent?: number;
211
+ materialPath?: string;
212
+ foliageType?: string;
213
+ foliageTypePath?: string;
214
+ meshPath?: string;
215
+ density?: number;
216
+ radius?: number;
217
+ minScale?: number;
218
+ maxScale?: number;
219
+ alignToNormal?: boolean;
220
+ randomYaw?: boolean;
221
+ cullDistance?: number;
222
+ transforms?: Transform[];
223
+ locations?: Vector3[];
224
+ bounds?: {
225
+ min: Vector3;
226
+ max: Vector3;
227
+ };
228
+ seed?: number;
229
+ heightData?: number[];
230
+ layerName?: string;
231
+ }
232
+ export interface LightingArgs extends HandlerArgs {
233
+ lightType?: 'Directional' | 'Point' | 'Spot' | 'Rect';
234
+ name?: string;
235
+ location?: Vector3;
236
+ rotation?: Rotator;
237
+ intensity?: number;
238
+ color?: number[];
239
+ temperature?: number;
240
+ radius?: number;
241
+ falloffExponent?: number;
242
+ innerCone?: number;
243
+ outerCone?: number;
244
+ castShadows?: boolean;
245
+ method?: 'Lightmass' | 'LumenGI' | 'ScreenSpace' | 'None';
246
+ bounces?: number;
247
+ quality?: string;
248
+ enabled?: boolean;
249
+ density?: number;
250
+ fogHeight?: number;
251
+ cubemapPath?: string;
252
+ sourceType?: 'CapturedScene' | 'SpecifiedCubemap';
253
+ recapture?: boolean;
254
+ }
255
+ export interface PerformanceArgs extends HandlerArgs {
256
+ type?: 'CPU' | 'GPU' | 'Memory' | 'RenderThread' | 'GameThread' | 'All';
257
+ category?: string;
258
+ duration?: number;
259
+ outputPath?: string;
260
+ level?: number;
261
+ scale?: number;
262
+ enabled?: boolean;
263
+ maxFPS?: number;
264
+ verbose?: boolean;
265
+ detailed?: boolean;
266
+ }
267
+ export interface InspectArgs extends HandlerArgs {
268
+ objectPath?: string;
269
+ name?: string;
270
+ actorName?: string;
271
+ componentName?: string;
272
+ propertyName?: string;
273
+ propertyPath?: string;
274
+ value?: unknown;
275
+ className?: string;
276
+ classPath?: string;
277
+ filter?: string;
278
+ tag?: string;
279
+ snapshotName?: string;
280
+ destinationPath?: string;
281
+ outputPath?: string;
282
+ format?: string;
283
+ }
284
+ export interface GraphArgs extends HandlerArgs {
285
+ assetPath?: string;
286
+ blueprintPath?: string;
287
+ systemPath?: string;
288
+ graphName?: string;
289
+ nodeType?: string;
290
+ nodeId?: string;
291
+ x?: number;
292
+ y?: number;
293
+ memberName?: string;
294
+ variableName?: string;
295
+ eventName?: string;
296
+ functionName?: string;
297
+ targetClass?: string;
298
+ memberClass?: string;
299
+ componentClass?: string;
300
+ pinName?: string;
301
+ linkedTo?: string;
302
+ fromNodeId?: string;
303
+ fromPinName?: string;
304
+ toNodeId?: string;
305
+ toPinName?: string;
306
+ parentNodeId?: string;
307
+ childNodeId?: string;
308
+ properties?: Record<string, unknown>;
309
+ }
310
+ export interface SystemArgs extends HandlerArgs {
311
+ command?: string;
312
+ category?: string;
313
+ profileType?: string;
314
+ level?: number;
315
+ key?: string;
316
+ value?: string;
317
+ section?: string;
318
+ configName?: string;
319
+ resolution?: string;
320
+ enabled?: boolean;
321
+ widgetPath?: string;
322
+ parentName?: string;
323
+ childClass?: string;
324
+ target?: string;
325
+ platform?: string;
326
+ configuration?: string;
327
+ arguments?: string;
328
+ }
329
+ export interface InputArgs extends HandlerArgs {
330
+ name?: string;
331
+ path?: string;
332
+ actionPath?: string;
333
+ contextPath?: string;
334
+ key?: string;
335
+ }
336
+ export interface PipelineArgs extends HandlerArgs {
337
+ target?: string;
338
+ platform?: string;
339
+ configuration?: string;
340
+ arguments?: string;
341
+ projectPath?: string;
342
+ }
343
+ //# sourceMappingURL=handler-types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=handler-types.js.map
@@ -26,7 +26,7 @@ export declare class UnrealBridge {
26
26
  timeoutMs?: number;
27
27
  allowAlternate?: boolean;
28
28
  }): Promise<Record<string, any>>;
29
- executeConsoleCommand(command: string, _options?: Record<string, never>): Promise<any>;
29
+ executeConsoleCommand(command: string): Promise<any>;
30
30
  executeConsoleCommands(commands: Iterable<string | {
31
31
  command: string;
32
32
  priority?: number;
@@ -1,6 +1,6 @@
1
1
  import { Logger } from './utils/logger.js';
2
2
  import { ErrorHandler } from './utils/error-handler.js';
3
- import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT } from './constants.js';
3
+ import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT, CONSOLE_COMMAND_TIMEOUT_MS, ENGINE_QUERY_TIMEOUT_MS } from './constants.js';
4
4
  import { UnrealCommandQueue } from './utils/unreal-command-queue.js';
5
5
  import { CommandValidator } from './utils/command-validator.js';
6
6
  export class UnrealBridge {
@@ -72,7 +72,9 @@ export class UnrealBridge {
72
72
  try {
73
73
  await this.connectPromise;
74
74
  }
75
- catch { }
75
+ catch (err) {
76
+ this.log.debug('Existing connect promise rejected', err instanceof Error ? err.message : String(err));
77
+ }
76
78
  return this.connected;
77
79
  }
78
80
  this.connectPromise = ErrorHandler.retryWithBackoff(() => {
@@ -337,7 +339,7 @@ export class UnrealBridge {
337
339
  };
338
340
  }
339
341
  }
340
- async executeConsoleCommand(command, _options = {}) {
342
+ async executeConsoleCommand(command) {
341
343
  const automationAvailable = Boolean(this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function');
342
344
  if (!automationAvailable) {
343
345
  throw new Error('Automation bridge not connected');
@@ -359,7 +361,7 @@ export class UnrealBridge {
359
361
  if (!this.automationBridge || !this.automationBridge.isConnected()) {
360
362
  throw new Error('Automation bridge not connected');
361
363
  }
362
- const pluginResp = await this.automationBridge.sendAutomationRequest('console_command', { command: cmdTrimmed }, { timeoutMs: 30000 });
364
+ const pluginResp = await this.automationBridge.sendAutomationRequest('console_command', { command: cmdTrimmed }, { timeoutMs: CONSOLE_COMMAND_TIMEOUT_MS });
363
365
  if (pluginResp && pluginResp.success) {
364
366
  return { ...pluginResp, transport: 'automation_bridge' };
365
367
  }
@@ -420,7 +422,7 @@ export class UnrealBridge {
420
422
  }
421
423
  const bridge = this.getAutomationBridge();
422
424
  try {
423
- const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_engine_version' }, { timeoutMs: 15000 });
425
+ const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_engine_version' }, { timeoutMs: ENGINE_QUERY_TIMEOUT_MS });
424
426
  const raw = resp && typeof resp.result === 'object'
425
427
  ? resp.result
426
428
  : resp?.result ?? resp ?? {};
@@ -456,7 +458,7 @@ export class UnrealBridge {
456
458
  }
457
459
  const bridge = this.getAutomationBridge();
458
460
  try {
459
- const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_feature_flags' }, { timeoutMs: 15000 });
461
+ const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_feature_flags' }, { timeoutMs: ENGINE_QUERY_TIMEOUT_MS });
460
462
  const raw = resp && typeof resp.result === 'object'
461
463
  ? resp.result
462
464
  : resp?.result ?? resp ?? {};
@@ -2,6 +2,7 @@ export declare class CommandValidator {
2
2
  private static readonly DANGEROUS_COMMANDS;
3
3
  private static readonly FORBIDDEN_TOKENS;
4
4
  private static readonly INVALID_PATTERNS;
5
+ private static readonly DANGEROUS_PATTERNS;
5
6
  static validate(command: string): void;
6
7
  static isLikelyInvalid(command: string): boolean;
7
8
  static getPriority(command: string): number;
@@ -21,6 +21,7 @@ export class CommandValidator {
21
21
  /^invalid_command/i,
22
22
  /^this_is_not_a_valid/i,
23
23
  ];
24
+ static DANGEROUS_PATTERNS = CommandValidator.DANGEROUS_COMMANDS.map(cmd => new RegExp(`(?:^|\\s)${cmd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`, 'i'));
24
25
  static validate(command) {
25
26
  if (!command || typeof command !== 'string') {
26
27
  throw new Error('Invalid command: must be a non-empty string');
@@ -36,15 +37,24 @@ export class CommandValidator {
36
37
  if (cmdLower === 'py' || cmdLower.startsWith('py ')) {
37
38
  throw new Error('Python console commands are blocked from external calls for safety.');
38
39
  }
39
- if (this.DANGEROUS_COMMANDS.some(dangerous => cmdLower.includes(dangerous))) {
40
+ if (this.DANGEROUS_PATTERNS.some(pattern => pattern.test(cmdLower))) {
40
41
  throw new Error(`Dangerous command blocked: ${command}`);
41
42
  }
42
43
  if (cmdLower.includes('&&') || cmdLower.includes('||')) {
43
44
  throw new Error('Command chaining with && or || is blocked for safety.');
44
45
  }
46
+ if (cmdTrimmed.includes(';')) {
47
+ throw new Error('Command chaining with ; (semicolon) is blocked for safety.');
48
+ }
49
+ if (cmdTrimmed.includes('|')) {
50
+ throw new Error('Command piping with | is blocked for safety.');
51
+ }
45
52
  if (this.FORBIDDEN_TOKENS.some(token => cmdLower.includes(token))) {
46
53
  throw new Error(`Command contains unsafe token and was blocked: ${command}`);
47
54
  }
55
+ if (cmdTrimmed.includes('`')) {
56
+ throw new Error('Backtick characters are blocked for safety.');
57
+ }
48
58
  }
49
59
  static isLikelyInvalid(command) {
50
60
  const cmdTrimmed = command.trim();
@@ -142,7 +142,9 @@ export class ErrorHandler {
142
142
  if (!isNaN(status) && (status === 429 || (status >= 500 && status < 600)))
143
143
  return true;
144
144
  }
145
- catch { }
145
+ catch (err) {
146
+ log.debug('isRetriable check failed', err instanceof Error ? err.message : String(err));
147
+ }
146
148
  return false;
147
149
  }
148
150
  static async retryWithBackoff(fn, options = {}) {
@@ -128,8 +128,8 @@ export class ResponseValidator {
128
128
  ajv;
129
129
  validators = new Map();
130
130
  constructor() {
131
- const AjvCtor = Ajv?.default ?? Ajv;
132
- this.ajv = new AjvCtor({
131
+ const AjvClass = Ajv.default ?? Ajv.default;
132
+ this.ajv = new AjvClass({
133
133
  allErrors: true,
134
134
  verbose: true,
135
135
  strict: true
@@ -1,2 +1,2 @@
1
- export declare function cleanObject(obj: any, maxDepth?: number): any;
1
+ export declare function cleanObject<T = unknown>(obj: T, maxDepth?: number): T;
2
2
  //# sourceMappingURL=safe-json.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { Logger } from './logger.js';
2
+ const log = new Logger('safe-json');
2
3
  export function cleanObject(obj, maxDepth = 10) {
3
4
  const seen = new WeakSet();
4
- const logger = new Logger('safe-json');
5
5
  function clean(value, depth, path = 'root') {
6
6
  if (depth > maxDepth) {
7
7
  return '[Max depth reached]';
@@ -23,9 +23,7 @@ export function cleanObject(obj, maxDepth = 10) {
23
23
  }
24
24
  seen.add(value);
25
25
  if (Array.isArray(value)) {
26
- const result = value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
27
- seen.delete(value);
28
- return result;
26
+ return value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
29
27
  }
30
28
  const cleaned = {};
31
29
  const keys = Object.keys(value);
@@ -37,10 +35,9 @@ export function cleanObject(obj, maxDepth = 10) {
37
35
  }
38
36
  }
39
37
  catch (e) {
40
- logger.error(`Error cleaning property ${path}.${key}`, e);
38
+ log.error(`Error cleaning property ${path}.${key}`, e);
41
39
  }
42
40
  }
43
- seen.delete(value);
44
41
  return cleaned;
45
42
  }
46
43
  return clean(obj, 0);
@@ -112,7 +112,7 @@ export class UnrealCommandQueue {
112
112
  if (!this.isProcessing && this.queue.length > 0) {
113
113
  this.processQueue();
114
114
  }
115
- }, 1000);
115
+ }, 250);
116
116
  }
117
117
  stopProcessor() {
118
118
  if (this.processorInterval) {
@@ -1,5 +1,6 @@
1
1
  import { toRotTuple, toVec3Tuple } from './normalize.js';
2
2
  const MAX_PATH_LENGTH = 260;
3
+ const MAX_ASSET_NAME_LENGTH = 64;
3
4
  const INVALID_CHARS = /[@#%$&*()+=\[\]{}<>?|\\;:'"`,~!\s]/g;
4
5
  const RESERVED_KEYWORDS = [
5
6
  'None', 'null', 'undefined', 'true', 'false',
@@ -23,8 +24,8 @@ export function sanitizeAssetName(name) {
23
24
  if (!/^[A-Za-z]/.test(sanitized)) {
24
25
  sanitized = `Asset_${sanitized}`;
25
26
  }
26
- if (sanitized.length > 64) {
27
- sanitized = sanitized.slice(0, 64);
27
+ if (sanitized.length > MAX_ASSET_NAME_LENGTH) {
28
+ sanitized = sanitized.slice(0, MAX_ASSET_NAME_LENGTH);
28
29
  }
29
30
  return sanitized;
30
31
  }
@@ -37,6 +38,9 @@ export function sanitizePath(path) {
37
38
  path = `/${path}`;
38
39
  }
39
40
  let segments = path.split('/').filter(s => s.length > 0);
41
+ if (segments.some(s => s === '..' || s === '.')) {
42
+ throw new Error('Path traversal (..) is not allowed');
43
+ }
40
44
  if (segments.length === 0) {
41
45
  return '/Game';
42
46
  }
@@ -122,6 +122,7 @@ This document maps the TypeScript tool definitions to their corresponding C++ ha
122
122
  | `configure_shadows` | `McpAutomationBridge_LightingHandlers.cpp` | `HandleLightingAction` | |
123
123
  | `set_exposure` | `McpAutomationBridge_LightingHandlers.cpp` | `HandleLightingAction` | |
124
124
  | `set_ambient_occlusion` | `McpAutomationBridge_LightingHandlers.cpp` | `HandleLightingAction` | |
125
+ | `list_light_types` | `McpAutomationBridge_LightingHandlers.cpp` | `HandleLightingAction` | Discovery: Returns all `ALight` subclasses |
125
126
 
126
127
  ## 7. Performance Manager (`manage_performance`)
127
128
 
@@ -148,7 +149,7 @@ This document maps the TypeScript tool definitions to their corresponding C++ ha
148
149
  | `create_animation_bp` | `McpAutomationBridge_AnimationHandlers.cpp` | `HandleCreateAnimBlueprint` | |
149
150
  | `play_montage` | `McpAutomationBridge_AnimationHandlers.cpp` | `HandlePlayAnimMontage` | |
150
151
  | `setup_ragdoll` | `McpAutomationBridge_AnimationHandlers.cpp` | `HandleSetupRagdoll` | |
151
- | `configure_vehicle` | `McpAutomationBridge_AnimationHandlers.cpp` | `HandleAnimationPhysicsAction` | |
152
+ | `configure_vehicle` | `McpAutomationBridge_AnimationHandlers.cpp` | `HandleAnimationPhysicsAction` | Supports custom vehicle type passthrough |
152
153
 
153
154
  ## 9. Effects Manager (`manage_effect`)
154
155
 
@@ -160,6 +161,8 @@ This document maps the TypeScript tool definitions to their corresponding C++ ha
160
161
  | `create_niagara_system` | `McpAutomationBridge_EffectHandlers.cpp` | `HandleCreateNiagaraSystem` | |
161
162
  | `create_niagara_emitter` | `McpAutomationBridge_EffectHandlers.cpp` | `HandleCreateNiagaraEmitter` | |
162
163
  | `add_niagara_module` | `McpAutomationBridge_NiagaraGraphHandlers.cpp` | `HandleNiagaraGraphAction` | |
164
+ | `list_debug_shapes` | `McpAutomationBridge_EffectHandlers.cpp` | `HandleEffectAction` | Discovery: Returns all debug shape types |
165
+ | `clear_debug_shapes` | `McpAutomationBridge_EffectHandlers.cpp` | `HandleEffectAction` | Clears persistent debug shapes |
163
166
 
164
167
  ## 10. Environment Builder (`build_environment`)
165
168
 
@@ -202,6 +205,8 @@ This document maps the TypeScript tool definitions to their corresponding C++ ha
202
205
  | `play` | `McpAutomationBridge_SequenceHandlers.cpp` | `HandleSequenceAction` | |
203
206
  | `add_keyframe` | `McpAutomationBridge_SequencerHandlers.cpp` | `HandleAddSequencerKeyframe` | |
204
207
  | `add_camera` | `McpAutomationBridge_SequenceHandlers.cpp` | `HandleAddCameraTrack` | |
208
+ | `add_track` | `McpAutomationBridge_SequenceHandlers.cpp` | `HandleSequenceAction` | Dynamic track class resolution |
209
+ | `list_track_types` | `McpAutomationBridge_SequenceHandlers.cpp` | `HandleSequenceAction` | Discovery: Returns all `UMovieSceneTrack` subclasses |
205
210
 
206
211
  ## 13. Introspection (`inspect`)
207
212
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unreal-engine-mcp-server",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "mcpName": "io.github.ChiR24/unreal-engine-mcp",
5
5
  "description": "A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control Unreal Engine via native automation bridge. Built with TypeScript and designed for game development automation.",
6
6
  "type": "module",
@@ -85,7 +85,7 @@
85
85
  "dataloader": "^2.2.3",
86
86
  "dotenv": "^17.2.3",
87
87
  "graphql": "^16.12.0",
88
- "graphql-yoga": "^5.17.1",
88
+ "graphql-yoga": "^5.18.0",
89
89
  "ws": "^8.18.3",
90
90
  "zod": "^4.2.1"
91
91
  },
@@ -1,4 +1,4 @@
1
- #include "McpAutomationBridgeGlobals.h"
1
+ #include "McpAutomationBridgeGlobals.h"
2
2
  #include "McpAutomationBridgeHelpers.h"
3
3
  #include "McpAutomationBridgeSubsystem.h"
4
4
 
@@ -1024,6 +1024,30 @@ bool UMcpAutomationBridgeSubsystem::HandleConsoleCommandAction(
1024
1024
  return true;
1025
1025
  }
1026
1026
 
1027
+ // 4. Block line breaks
1028
+ if (LowerCommand.Contains(TEXT("\n")) || LowerCommand.Contains(TEXT("\r"))) {
1029
+ SendAutomationResponse(RequestingSocket, RequestId, false,
1030
+ TEXT("Multi-line commands are blocked for safety"),
1031
+ nullptr, TEXT("COMMAND_BLOCKED"));
1032
+ return true;
1033
+ }
1034
+
1035
+ // 5. Block semicolon and pipe
1036
+ if (LowerCommand.Contains(TEXT(";")) || LowerCommand.Contains(TEXT("|"))) {
1037
+ SendAutomationResponse(RequestingSocket, RequestId, false,
1038
+ TEXT("Command chaining with semicolon or pipe is blocked for safety"),
1039
+ nullptr, TEXT("COMMAND_BLOCKED"));
1040
+ return true;
1041
+ }
1042
+
1043
+ // 6. Block backticks
1044
+ if (LowerCommand.Contains(TEXT("`"))) {
1045
+ SendAutomationResponse(RequestingSocket, RequestId, false,
1046
+ TEXT("Commands containing backticks are blocked for safety"),
1047
+ nullptr, TEXT("COMMAND_BLOCKED"));
1048
+ return true;
1049
+ }
1050
+
1027
1051
  // Execute the command
1028
1052
  try {
1029
1053
  UWorld *TargetWorld = nullptr;