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
@@ -1,43 +1,20 @@
1
1
  import { executeAutomationRequest } from './common-handlers.js';
2
2
  import { normalizeArgs } from './argument-helper.js';
3
3
  import { ResponseFactory } from '../../utils/response-factory.js';
4
+ import { ACTOR_CLASS_ALIASES, getRequiredComponent } from '../../config/class-aliases.js';
4
5
  const handlers = {
5
6
  spawn: async (args, tools) => {
6
- const classAliases = {
7
- 'SplineActor': '/Script/Engine.Actor',
8
- 'Spline': '/Script/Engine.Actor',
9
- 'PointLight': '/Script/Engine.PointLight',
10
- 'SpotLight': '/Script/Engine.SpotLight',
11
- 'DirectionalLight': '/Script/Engine.DirectionalLight',
12
- 'Camera': '/Script/Engine.CameraActor',
13
- 'CameraActor': '/Script/Engine.CameraActor',
14
- 'StaticMeshActor': '/Script/Engine.StaticMeshActor',
15
- 'SkeletalMeshActor': '/Script/Engine.SkeletalMeshActor',
16
- 'PlayerStart': '/Script/Engine.PlayerStart',
17
- 'TriggerBox': '/Script/Engine.TriggerBox',
18
- 'TriggerSphere': '/Script/Engine.TriggerSphere',
19
- 'BlockingVolume': '/Script/Engine.BlockingVolume',
20
- 'Pawn': '/Script/Engine.Pawn',
21
- 'Character': '/Script/Engine.Character',
22
- 'Actor': '/Script/Engine.Actor'
23
- };
24
7
  const params = normalizeArgs(args, [
25
- { key: 'classPath', aliases: ['class', 'type', 'actorClass'], required: true, map: classAliases },
8
+ { key: 'classPath', aliases: ['class', 'type', 'actorClass'], required: true, map: ACTOR_CLASS_ALIASES },
26
9
  { key: 'actorName', aliases: ['name'] },
27
10
  { key: 'timeoutMs', default: undefined }
28
11
  ]);
29
12
  const timeoutMs = typeof params.timeoutMs === 'number' ? params.timeoutMs : undefined;
30
13
  if (typeof timeoutMs === 'number' && timeoutMs > 0 && timeoutMs < 200) {
31
- return {
32
- success: false,
33
- error: `Timeout too small for spawn operation: ${timeoutMs}ms`,
34
- message: 'Timeout too small for spawn operation'
35
- };
14
+ throw new Error(`Timeout too small for spawn operation: ${timeoutMs}ms`);
36
15
  }
37
16
  const originalClass = args.classPath || args.class || args.type || args.actorClass;
38
- const componentToAdd = (originalClass === 'SplineActor' || originalClass === 'Spline')
39
- ? 'SplineComponent'
40
- : undefined;
17
+ const componentToAdd = getRequiredComponent(originalClass);
41
18
  const result = await tools.actorTools.spawn({
42
19
  classPath: params.classPath,
43
20
  actorName: params.actorName,
@@ -168,7 +168,19 @@ export async function handleAssetTools(action, args, tools) {
168
168
  if (paths.length === 0) {
169
169
  throw new Error('No paths provided for delete action');
170
170
  }
171
- const res = await tools.assetTools.deleteAssets({ paths });
171
+ const normalizedPaths = paths.map(p => {
172
+ let normalized = p.replace(/\\/g, '/').trim();
173
+ const lastSlash = normalized.lastIndexOf('/');
174
+ if (lastSlash >= 0) {
175
+ const afterSlash = normalized.substring(lastSlash + 1);
176
+ const dotIndex = afterSlash.indexOf('.');
177
+ if (dotIndex > 0) {
178
+ normalized = normalized.substring(0, lastSlash + 1 + dotIndex);
179
+ }
180
+ }
181
+ return normalized;
182
+ });
183
+ const res = await tools.assetTools.deleteAssets({ paths: normalizedPaths });
172
184
  return ResponseFactory.success(res, 'Assets deleted successfully');
173
185
  }
174
186
  case 'generate_lods': {
@@ -1,4 +1,7 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
2
  export declare function handleBlueprintTools(action: string, args: any, tools: ITools): Promise<any>;
3
- export declare function handleBlueprintGet(args: any, tools: ITools): Promise<any>;
3
+ export declare function handleBlueprintGet(args: any, tools: ITools): Promise<{
4
+ success?: boolean;
5
+ message?: string;
6
+ } | null>;
4
7
  //# sourceMappingURL=blueprint-handlers.d.ts.map
@@ -1,14 +1,14 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function ensureArgsPresent(args: any): void;
3
- export declare function requireAction(args: any): string;
4
- export declare function requireNonEmptyString(value: any, field: string, message?: string): string;
5
- export declare function executeAutomationRequest(tools: ITools, toolName: string, args: any, errorMessage?: string, options?: {
2
+ import type { HandlerArgs, Vector3, Rotator } from '../../types/handler-types.js';
3
+ export declare function ensureArgsPresent(args: unknown): asserts args is Record<string, unknown>;
4
+ export declare function requireAction(args: HandlerArgs): string;
5
+ export declare function requireNonEmptyString(value: unknown, field: string, message?: string): string;
6
+ export declare function executeAutomationRequest(tools: ITools, toolName: string, args: HandlerArgs, errorMessage?: string, options?: {
6
7
  timeoutMs?: number;
7
- }): Promise<import("../../automation/types.js").AutomationBridgeResponseMessage>;
8
- export declare function normalizeLocation(location: any): [number, number, number] | undefined;
9
- export declare function normalizeRotation(rotation: any): {
10
- pitch: number;
11
- yaw: number;
12
- roll: number;
13
- } | undefined;
8
+ }): Promise<unknown>;
9
+ type LocationInput = Vector3 | [number, number, number] | number[] | null | undefined;
10
+ export declare function normalizeLocation(location: LocationInput): [number, number, number] | undefined;
11
+ type RotationInput = Rotator | [number, number, number] | number[] | null | undefined;
12
+ export declare function normalizeRotation(rotation: RotationInput): Rotator | undefined;
13
+ export {};
14
14
  //# sourceMappingURL=common-handlers.d.ts.map
@@ -34,7 +34,8 @@ export function normalizeLocation(location) {
34
34
  return [Number(location[0]) || 0, Number(location[1]) || 0, Number(location[2]) || 0];
35
35
  }
36
36
  if (typeof location === 'object' && ('x' in location || 'y' in location || 'z' in location)) {
37
- return [Number(location.x) || 0, Number(location.y) || 0, Number(location.z) || 0];
37
+ const loc = location;
38
+ return [Number(loc.x) || 0, Number(loc.y) || 0, Number(loc.z) || 0];
38
39
  }
39
40
  return undefined;
40
41
  }
@@ -45,10 +46,11 @@ export function normalizeRotation(rotation) {
45
46
  return { pitch: Number(rotation[0]) || 0, yaw: Number(rotation[1]) || 0, roll: Number(rotation[2]) || 0 };
46
47
  }
47
48
  if (typeof rotation === 'object') {
49
+ const rot = rotation;
48
50
  return {
49
- pitch: Number(rotation.pitch) || 0,
50
- yaw: Number(rotation.yaw) || 0,
51
- roll: Number(rotation.roll) || 0
51
+ pitch: Number(rot.pitch) || 0,
52
+ yaw: Number(rot.yaw) || 0,
53
+ roll: Number(rot.roll) || 0
52
54
  };
53
55
  }
54
56
  return undefined;
@@ -1,3 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleEditorTools(action: string, args: any, tools: ITools): Promise<any>;
2
+ import type { EditorArgs } from '../../types/handler-types.js';
3
+ export declare function handleEditorTools(action: string, args: EditorArgs, tools: ITools): Promise<unknown>;
3
4
  //# sourceMappingURL=editor-handlers.d.ts.map
@@ -14,14 +14,14 @@ export async function handleEditorTools(action, args, tools) {
14
14
  case 'eject': {
15
15
  const inPie = await tools.editorTools.isInPIE();
16
16
  if (!inPie) {
17
- return { success: false, error: 'NOT_IN_PIE', message: 'Cannot eject while not in PIE' };
17
+ throw new Error('Cannot eject while not in PIE');
18
18
  }
19
19
  return await executeAutomationRequest(tools, 'control_editor', { action: 'eject' });
20
20
  }
21
21
  case 'possess': {
22
22
  const inPie = await tools.editorTools.isInPIE();
23
23
  if (!inPie) {
24
- return { success: false, error: 'NOT_IN_PIE', message: 'Cannot possess actor while not in PIE' };
24
+ throw new Error('Cannot possess actor while not in PIE');
25
25
  }
26
26
  return await executeAutomationRequest(tools, 'control_editor', args);
27
27
  }
@@ -38,7 +38,7 @@ export async function handleEditorTools(action, args, tools) {
38
38
  return cleanObject(res);
39
39
  }
40
40
  case 'console_command': {
41
- const res = await tools.editorTools.executeConsoleCommand(args.command);
41
+ const res = await tools.editorTools.executeConsoleCommand(args.command ?? '');
42
42
  return cleanObject(res);
43
43
  }
44
44
  case 'set_camera': {
@@ -59,17 +59,17 @@ export async function handleEditorTools(action, args, tools) {
59
59
  return { success: true, message: 'Stepped frame', action: 'step_frame' };
60
60
  }
61
61
  case 'create_bookmark': {
62
- const idx = parseInt(args.bookmarkName) || 0;
62
+ const idx = parseInt(args.bookmarkName ?? '0') || 0;
63
63
  await tools.editorTools.executeConsoleCommand(`r.SetBookmark ${idx}`);
64
64
  return { success: true, message: `Created bookmark ${idx}`, action: 'create_bookmark' };
65
65
  }
66
66
  case 'jump_to_bookmark': {
67
- const idx = parseInt(args.bookmarkName) || 0;
67
+ const idx = parseInt(args.bookmarkName ?? '0') || 0;
68
68
  await tools.editorTools.executeConsoleCommand(`r.JumpToBookmark ${idx}`);
69
69
  return { success: true, message: `Jumped to bookmark ${idx}`, action: 'jump_to_bookmark' };
70
70
  }
71
71
  case 'set_preferences': {
72
- const res = await tools.editorTools.setEditorPreferences(args.category, args.preferences);
72
+ const res = await tools.editorTools.setEditorPreferences(args.category ?? '', args.preferences ?? {});
73
73
  return cleanObject(res);
74
74
  }
75
75
  case 'open_asset': {
@@ -78,6 +78,9 @@ export async function handleEffectTools(action, args, tools) {
78
78
  if (action === 'clear_debug_shapes') {
79
79
  return executeAutomationRequest(tools, action, args);
80
80
  }
81
+ if (action === 'list_debug_shapes') {
82
+ return executeAutomationRequest(tools, 'list_debug_shapes', args);
83
+ }
81
84
  if (action === 'cleanup') {
82
85
  args.action = 'cleanup';
83
86
  args.subAction = 'cleanup';
@@ -1,3 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleGraphTools(toolName: string, action: string, args: any, tools: ITools): Promise<any>;
2
+ import type { GraphArgs } from '../../types/handler-types.js';
3
+ export declare function handleGraphTools(toolName: string, action: string, args: GraphArgs, tools: ITools): Promise<any>;
3
4
  //# sourceMappingURL=graph-handlers.d.ts.map
@@ -13,7 +13,7 @@ export async function handleGraphTools(toolName, action, args, tools) {
13
13
  case 'manage_behavior_tree':
14
14
  return handleBehaviorTree(action, args, tools);
15
15
  default:
16
- return { success: false, error: 'UNKNOWN_TOOL', message: `Unknown graph tool: ${toolName}` };
16
+ throw new Error(`Unknown graph tool: ${toolName}`);
17
17
  }
18
18
  }
19
19
  async function handleBlueprintGraph(action, args, tools) {
@@ -1,3 +1,7 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleInputTools(action: string, args: any, tools: ITools): Promise<any>;
2
+ export declare function handleInputTools(action: string, args: any, tools: ITools): Promise<import("../../automation/types.js").AutomationBridgeResponseMessage | import("../../types/tool-interfaces.js").StandardActionResponse<any> | {
3
+ success: boolean;
4
+ error: string;
5
+ message: string;
6
+ }>;
3
7
  //# sourceMappingURL=input-handlers.d.ts.map
@@ -1,3 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleLevelTools(action: string, args: any, tools: ITools): Promise<any>;
2
+ import type { LevelArgs } from '../../types/handler-types.js';
3
+ export declare function handleLevelTools(action: string, args: LevelArgs, tools: ITools): Promise<any>;
3
4
  //# sourceMappingURL=level-handlers.d.ts.map
@@ -111,14 +111,14 @@ export async function handleLevelTools(action, args, tools) {
111
111
  case 'export_level': {
112
112
  const res = await tools.levelTools.exportLevel({
113
113
  levelPath: args.levelPath,
114
- exportPath: args.exportPath || args.destinationPath,
114
+ exportPath: args.exportPath ?? args.destinationPath ?? '',
115
115
  timeoutMs: typeof args.timeoutMs === 'number' ? args.timeoutMs : undefined
116
116
  });
117
117
  return cleanObject(res);
118
118
  }
119
119
  case 'import_level': {
120
120
  const res = await tools.levelTools.importLevel({
121
- packagePath: args.packagePath || args.sourcePath,
121
+ packagePath: args.packagePath ?? args.sourcePath ?? '',
122
122
  destinationPath: args.destinationPath,
123
123
  timeoutMs: typeof args.timeoutMs === 'number' ? args.timeoutMs : undefined
124
124
  });
@@ -133,7 +133,7 @@ export async function handleLevelTools(action, args, tools) {
133
133
  return cleanObject(res);
134
134
  }
135
135
  case 'delete': {
136
- const levelPaths = Array.isArray(args.levelPaths) ? args.levelPaths : [args.levelPath];
136
+ const levelPaths = Array.isArray(args.levelPaths) ? args.levelPaths.filter((p) => typeof p === 'string') : (args.levelPath ? [args.levelPath] : []);
137
137
  const res = await tools.levelTools.deleteLevels({ levelPaths });
138
138
  return cleanObject(res);
139
139
  }
@@ -1,3 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleLightingTools(action: string, args: any, tools: ITools): Promise<any>;
2
+ import type { LightingArgs } from '../../types/handler-types.js';
3
+ export declare function handleLightingTools(action: string, args: LightingArgs, tools: ITools): Promise<any>;
3
4
  //# sourceMappingURL=lighting-handlers.d.ts.map
@@ -137,6 +137,9 @@ export async function handleLightingTools(action, args, tools) {
137
137
  useTemplate: args.useTemplate
138
138
  }));
139
139
  }
140
+ case 'list_light_types': {
141
+ return cleanObject(await tools.lightingTools.listLightTypes());
142
+ }
140
143
  default:
141
144
  throw new Error(`Unknown lighting action: ${action}`);
142
145
  }
@@ -1,3 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handlePipelineTools(action: string, args: any, tools: ITools): Promise<any>;
2
+ import type { PipelineArgs } from '../../types/handler-types.js';
3
+ export declare function handlePipelineTools(action: string, args: PipelineArgs, tools: ITools): Promise<unknown>;
3
4
  //# sourceMappingURL=pipeline-handlers.d.ts.map
@@ -3,6 +3,55 @@ import { executeAutomationRequest } from './common-handlers.js';
3
3
  import { spawn } from 'child_process';
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
+ function validateUbtArgumentsString(extraArgs) {
7
+ if (!extraArgs || typeof extraArgs !== 'string') {
8
+ return;
9
+ }
10
+ const forbiddenChars = ['\n', '\r', ';', '|', '`', '&&', '||', '>', '<'];
11
+ for (const char of forbiddenChars) {
12
+ if (extraArgs.includes(char)) {
13
+ throw new Error(`UBT arguments contain forbidden character(s) and are blocked for safety. Blocked: ${JSON.stringify(char)}.`);
14
+ }
15
+ }
16
+ }
17
+ function tokenizeArgs(extraArgs) {
18
+ if (!extraArgs) {
19
+ return [];
20
+ }
21
+ const args = [];
22
+ let current = '';
23
+ let inQuotes = false;
24
+ let escapeNext = false;
25
+ for (let i = 0; i < extraArgs.length; i++) {
26
+ const ch = extraArgs[i];
27
+ if (escapeNext) {
28
+ current += ch;
29
+ escapeNext = false;
30
+ continue;
31
+ }
32
+ if (ch === '\\') {
33
+ escapeNext = true;
34
+ continue;
35
+ }
36
+ if (ch === '"') {
37
+ inQuotes = !inQuotes;
38
+ continue;
39
+ }
40
+ if (!inQuotes && /\s/.test(ch)) {
41
+ if (current.length > 0) {
42
+ args.push(current);
43
+ current = '';
44
+ }
45
+ }
46
+ else {
47
+ current += ch;
48
+ }
49
+ }
50
+ if (current.length > 0) {
51
+ args.push(current);
52
+ }
53
+ return args;
54
+ }
6
55
  export async function handlePipelineTools(action, args, tools) {
7
56
  switch (action) {
8
57
  case 'run_ubt': {
@@ -11,8 +60,9 @@ export async function handlePipelineTools(action, args, tools) {
11
60
  const configuration = args.configuration || 'Development';
12
61
  const extraArgs = args.arguments || '';
13
62
  if (!target) {
14
- return { success: false, error: 'MISSING_TARGET', message: 'Target is required for run_ubt' };
63
+ throw new Error('Target is required for run_ubt');
15
64
  }
65
+ validateUbtArgumentsString(extraArgs);
16
66
  let ubtPath = 'UnrealBuildTool';
17
67
  const enginePath = process.env.UE_ENGINE_PATH || process.env.UNREAL_ENGINE_PATH;
18
68
  if (enginePath) {
@@ -26,7 +76,7 @@ export async function handlePipelineTools(action, args, tools) {
26
76
  projectPath = args.projectPath;
27
77
  }
28
78
  if (!projectPath) {
29
- return { success: false, error: 'MISSING_PROJECT_PATH', message: 'UE_PROJECT_PATH environment variable is not set and no projectPath argument was provided.' };
79
+ throw new Error('UE_PROJECT_PATH environment variable is not set and no projectPath argument was provided.');
30
80
  }
31
81
  let uprojectFile = projectPath;
32
82
  if (!uprojectFile.endsWith('.uproject')) {
@@ -38,18 +88,20 @@ export async function handlePipelineTools(action, args, tools) {
38
88
  }
39
89
  }
40
90
  catch (_e) {
41
- return { success: false, error: 'INVALID_PROJECT_PATH', message: `Could not read project directory: ${projectPath}` };
91
+ throw new Error(`Could not read project directory: ${projectPath}`);
42
92
  }
43
93
  }
94
+ const projectArg = `-Project="${uprojectFile}"`;
95
+ const extraTokens = tokenizeArgs(extraArgs);
44
96
  const cmdArgs = [
45
97
  target,
46
98
  platform,
47
99
  configuration,
48
- `-Project="${uprojectFile}"`,
49
- extraArgs
50
- ].filter(Boolean);
100
+ projectArg,
101
+ ...extraTokens
102
+ ];
51
103
  return new Promise((resolve) => {
52
- const child = spawn(ubtPath, cmdArgs, { shell: true });
104
+ const child = spawn(ubtPath, cmdArgs, { shell: false });
53
105
  const MAX_OUTPUT_SIZE = 20 * 1024;
54
106
  let stdout = '';
55
107
  let stderr = '';
@@ -73,12 +125,13 @@ export async function handlePipelineTools(action, args, tools) {
73
125
  const truncatedNote = (stdout.length >= MAX_OUTPUT_SIZE || stderr.length >= MAX_OUTPUT_SIZE)
74
126
  ? '\n[Output truncated for response payload]'
75
127
  : '';
128
+ const quotedArgs = cmdArgs.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
76
129
  if (code === 0) {
77
130
  resolve({
78
131
  success: true,
79
132
  message: 'UnrealBuildTool finished successfully',
80
133
  output: stdout + truncatedNote,
81
- command: `${ubtPath} ${cmdArgs.join(' ')}`
134
+ command: `${ubtPath} ${quotedArgs.join(' ')}`
82
135
  });
83
136
  }
84
137
  else {
@@ -88,16 +141,17 @@ export async function handlePipelineTools(action, args, tools) {
88
141
  message: `UnrealBuildTool failed with code ${code}`,
89
142
  output: stdout + truncatedNote,
90
143
  errorOutput: stderr + truncatedNote,
91
- command: `${ubtPath} ${cmdArgs.join(' ')}`
144
+ command: `${ubtPath} ${quotedArgs.join(' ')}`
92
145
  });
93
146
  }
94
147
  });
95
148
  child.on('error', (err) => {
149
+ const quotedArgs = cmdArgs.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
96
150
  resolve({
97
151
  success: false,
98
152
  error: 'SPAWN_FAILED',
99
153
  message: `Failed to spawn UnrealBuildTool: ${err.message}`,
100
- command: `${ubtPath} ${cmdArgs.join(' ')}`
154
+ command: `${ubtPath} ${quotedArgs.join(' ')}`
101
155
  });
102
156
  });
103
157
  });
@@ -1,3 +1,3 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
- export declare function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools): Promise<any>;
2
+ export declare function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools): Promise<unknown>;
3
3
  //# sourceMappingURL=sequence-handlers.d.ts.map
@@ -1,4 +1,4 @@
1
1
  import { ITools } from '../../types/tool-interfaces.js';
2
2
  export declare function handleSystemTools(action: string, args: any, tools: ITools): Promise<any>;
3
- export declare function handleConsoleCommand(args: any, tools: ITools): Promise<any>;
3
+ export declare function handleConsoleCommand(args: any, tools: ITools): Promise<unknown>;
4
4
  //# sourceMappingURL=system-handlers.d.ts.map
@@ -11,7 +11,11 @@ export declare class InputTools {
11
11
  setAutomationBridge(bridge: AutomationBridge): void;
12
12
  createInputAction(name: string, path: string): Promise<import("../automation/types.js").AutomationBridgeResponseMessage>;
13
13
  createInputMappingContext(name: string, path: string): Promise<import("../automation/types.js").AutomationBridgeResponseMessage>;
14
- addMapping(contextPath: string, actionPath: string, key: string): Promise<import("../automation/types.js").AutomationBridgeResponseMessage>;
14
+ addMapping(contextPath: string, actionPath: string, key: string): Promise<import("../automation/types.js").AutomationBridgeResponseMessage | {
15
+ success: boolean;
16
+ error: string;
17
+ message: string;
18
+ }>;
15
19
  removeMapping(contextPath: string, actionPath: string): Promise<import("../automation/types.js").AutomationBridgeResponseMessage>;
16
20
  }
17
21
  export declare const inputTools: ToolDefinition;
@@ -1,3 +1,25 @@
1
+ const VALID_KEY_NAMES = new Set([
2
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
3
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
4
+ 'Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine',
5
+ 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12',
6
+ 'SpaceBar', 'Enter', 'Escape', 'Tab', 'BackSpace', 'CapsLock',
7
+ 'LeftShift', 'RightShift', 'LeftControl', 'RightControl', 'LeftAlt', 'RightAlt',
8
+ 'LeftCommand', 'RightCommand', 'Insert', 'Delete', 'Home', 'End', 'PageUp', 'PageDown',
9
+ 'Up', 'Down', 'Left', 'Right',
10
+ 'Semicolon', 'Equals', 'Comma', 'Hyphen', 'Underscore', 'Period', 'Slash', 'Tilde',
11
+ 'LeftBracket', 'Backslash', 'RightBracket', 'Apostrophe', 'Quote',
12
+ 'LeftMouseButton', 'RightMouseButton', 'MiddleMouseButton', 'ThumbMouseButton', 'ThumbMouseButton2',
13
+ 'MouseX', 'MouseY', 'MouseWheelAxis', 'MouseScrollUp', 'MouseScrollDown',
14
+ 'Gamepad_FaceButton_Bottom', 'Gamepad_FaceButton_Right', 'Gamepad_FaceButton_Left', 'Gamepad_FaceButton_Top',
15
+ 'Gamepad_LeftShoulder', 'Gamepad_RightShoulder', 'Gamepad_LeftTrigger', 'Gamepad_RightTrigger',
16
+ 'Gamepad_LeftTriggerAxis', 'Gamepad_RightTriggerAxis',
17
+ 'Gamepad_LeftThumbstick', 'Gamepad_RightThumbstick',
18
+ 'Gamepad_LeftStick_Up', 'Gamepad_LeftStick_Down', 'Gamepad_LeftStick_Left', 'Gamepad_LeftStick_Right',
19
+ 'Gamepad_RightStick_Up', 'Gamepad_RightStick_Down', 'Gamepad_RightStick_Left', 'Gamepad_RightStick_Right',
20
+ 'Gamepad_DPad_Up', 'Gamepad_DPad_Down', 'Gamepad_DPad_Left', 'Gamepad_DPad_Right',
21
+ 'Gamepad_Special_Left', 'Gamepad_Special_Right'
22
+ ]);
1
23
  export class InputTools {
2
24
  automationBridge = null;
3
25
  constructor() { }
@@ -25,11 +47,25 @@ export class InputTools {
25
47
  async addMapping(contextPath, actionPath, key) {
26
48
  if (!this.automationBridge)
27
49
  throw new Error('Automation bridge not set');
50
+ if (!key || typeof key !== 'string' || key.trim().length === 0) {
51
+ return { success: false, error: 'INVALID_ARGUMENT', message: 'Key name is required.' };
52
+ }
53
+ const trimmedKey = key.trim();
54
+ if (trimmedKey === 'MouseXY2D' || trimmedKey === 'Mouse2D' || trimmedKey === 'MouseXY') {
55
+ return {
56
+ success: false,
57
+ error: 'INVALID_ARGUMENT',
58
+ message: `Invalid key name '${trimmedKey}'. For mouse axis input, use separate mappings with 'MouseX' and 'MouseY' keys instead of composite 2D axis names.`
59
+ };
60
+ }
61
+ if (!VALID_KEY_NAMES.has(trimmedKey)) {
62
+ console.warn(`[InputTools] Key '${trimmedKey}' is not in the standard key list. Attempting mapping anyway.`);
63
+ }
28
64
  return this.automationBridge.sendAutomationRequest('manage_input', {
29
65
  action: 'add_mapping',
30
66
  contextPath,
31
67
  actionPath,
32
- key
68
+ key: trimmedKey
33
69
  });
34
70
  }
35
71
  async removeMapping(contextPath, actionPath) {
@@ -6,6 +6,7 @@ export declare class LightingTools {
6
6
  constructor(bridge: UnrealBridge, automationBridge?: AutomationBridge | undefined);
7
7
  setAutomationBridge(automationBridge?: AutomationBridge): void;
8
8
  private normalizeName;
9
+ listLightTypes(): Promise<import("../automation/types.js").AutomationBridgeResponseMessage>;
9
10
  private spawnLightViaAutomation;
10
11
  createDirectionalLight(params: {
11
12
  name: string;
@@ -23,6 +23,13 @@ export class LightingTools {
23
23
  }
24
24
  return `Light_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
25
25
  }
26
+ async listLightTypes() {
27
+ if (!this.automationBridge) {
28
+ throw new Error('Automation Bridge required to list light types');
29
+ }
30
+ const response = await this.automationBridge.sendAutomationRequest('list_light_types', {});
31
+ return response;
32
+ }
26
33
  async spawnLightViaAutomation(lightClass, params) {
27
34
  if (!this.automationBridge) {
28
35
  throw new Error('Automation Bridge not available. Cannot spawn lights without plugin support.');
@@ -67,7 +67,7 @@ export declare class PhysicsTools {
67
67
  }>;
68
68
  configureVehicle(params: {
69
69
  vehicleName: string;
70
- vehicleType: 'Car' | 'Bike' | 'Tank' | 'Aircraft';
70
+ vehicleType: string;
71
71
  wheels?: Array<{
72
72
  name: string;
73
73
  radius: number;
@@ -129,6 +129,7 @@ export declare class SequenceTools extends BaseTool implements ISequenceTools {
129
129
  listTracks(params: {
130
130
  path: string;
131
131
  }): Promise<StandardActionResponse>;
132
+ listTrackTypes(): Promise<StandardActionResponse>;
132
133
  setWorkRange(params: {
133
134
  path?: string;
134
135
  start: number;
@@ -247,6 +247,13 @@ export class SequenceTools extends BaseTool {
247
247
  }
248
248
  return resp;
249
249
  }
250
+ async listTrackTypes() {
251
+ const resp = await this.sendAction('list_track_types', {});
252
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
253
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement list_track_types' };
254
+ }
255
+ return resp;
256
+ }
250
257
  async setWorkRange(params) {
251
258
  const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
252
259
  if (!resp.success && this.isUnknownActionResponse(resp)) {