unreal-engine-mcp-server 0.5.1 → 0.5.2

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 (75) hide show
  1. package/.github/workflows/publish-mcp.yml +1 -4
  2. package/.github/workflows/release-drafter.yml +2 -1
  3. package/CHANGELOG.md +38 -0
  4. package/dist/automation/bridge.d.ts +1 -2
  5. package/dist/automation/bridge.js +24 -23
  6. package/dist/automation/connection-manager.d.ts +1 -0
  7. package/dist/automation/connection-manager.js +10 -0
  8. package/dist/automation/message-handler.js +5 -4
  9. package/dist/automation/request-tracker.d.ts +4 -0
  10. package/dist/automation/request-tracker.js +11 -3
  11. package/dist/tools/actors.d.ts +19 -1
  12. package/dist/tools/actors.js +15 -5
  13. package/dist/tools/assets.js +1 -1
  14. package/dist/tools/blueprint.d.ts +12 -0
  15. package/dist/tools/blueprint.js +43 -14
  16. package/dist/tools/consolidated-tool-definitions.js +2 -1
  17. package/dist/tools/editor.js +3 -2
  18. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  19. package/dist/tools/handlers/actor-handlers.js +14 -8
  20. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  21. package/dist/tools/handlers/sequence-handlers.js +24 -13
  22. package/dist/tools/introspection.d.ts +1 -1
  23. package/dist/tools/introspection.js +1 -1
  24. package/dist/tools/level.js +3 -3
  25. package/dist/tools/lighting.d.ts +54 -7
  26. package/dist/tools/lighting.js +4 -4
  27. package/dist/tools/materials.d.ts +1 -1
  28. package/dist/types/tool-types.d.ts +2 -0
  29. package/dist/unreal-bridge.js +4 -4
  30. package/dist/utils/command-validator.js +6 -5
  31. package/dist/utils/error-handler.d.ts +24 -2
  32. package/dist/utils/error-handler.js +58 -23
  33. package/dist/utils/normalize.d.ts +7 -4
  34. package/dist/utils/normalize.js +12 -10
  35. package/dist/utils/response-validator.js +88 -73
  36. package/dist/utils/unreal-command-queue.d.ts +2 -0
  37. package/dist/utils/unreal-command-queue.js +8 -1
  38. package/docs/handler-mapping.md +4 -2
  39. package/package.json +1 -1
  40. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  41. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  42. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  43. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  44. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  45. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  46. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  47. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  48. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  49. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  50. package/server.json +3 -3
  51. package/src/automation/bridge.ts +27 -25
  52. package/src/automation/connection-manager.ts +18 -0
  53. package/src/automation/message-handler.ts +33 -8
  54. package/src/automation/request-tracker.ts +39 -7
  55. package/src/server/tool-registry.ts +3 -3
  56. package/src/tools/actors.ts +44 -19
  57. package/src/tools/assets.ts +3 -3
  58. package/src/tools/blueprint.ts +115 -49
  59. package/src/tools/consolidated-tool-definitions.ts +2 -1
  60. package/src/tools/editor.ts +4 -3
  61. package/src/tools/handlers/actor-handlers.ts +14 -9
  62. package/src/tools/handlers/sequence-handlers.ts +86 -63
  63. package/src/tools/introspection.ts +7 -7
  64. package/src/tools/level.ts +6 -6
  65. package/src/tools/lighting.ts +19 -19
  66. package/src/tools/materials.ts +1 -1
  67. package/src/tools/sequence.ts +1 -1
  68. package/src/tools/ui.ts +1 -1
  69. package/src/types/tool-types.ts +4 -0
  70. package/src/unreal-bridge.ts +71 -26
  71. package/src/utils/command-validator.ts +46 -5
  72. package/src/utils/error-handler.ts +128 -45
  73. package/src/utils/normalize.ts +38 -16
  74. package/src/utils/response-validator.ts +103 -87
  75. package/src/utils/unreal-command-queue.ts +13 -1
@@ -20,7 +20,17 @@ function markSequenceDeleted(path) {
20
20
  if (!norm)
21
21
  return;
22
22
  managedSequences.delete(norm);
23
- deletedSequences.add(norm);
23
+ deletedSequences.delete(norm);
24
+ }
25
+ function getErrorString(res) {
26
+ if (!res)
27
+ return '';
28
+ return typeof res.error === 'string' ? res.error : '';
29
+ }
30
+ function getMessageString(res) {
31
+ if (!res)
32
+ return '';
33
+ return typeof res.message === 'string' ? res.message : '';
24
34
  }
25
35
  export async function handleSequenceTools(action, args, tools) {
26
36
  const seqAction = String(action || '').trim();
@@ -39,8 +49,8 @@ export async function handleSequenceTools(action, args, tools) {
39
49
  if (sequencePath && res && res.success !== false) {
40
50
  markSequenceCreated(sequencePath);
41
51
  }
42
- const errorCode = String((res && res.error) || '').toUpperCase();
43
- const msgLower = String((res && res.message) || '').toLowerCase();
52
+ const errorCode = getErrorString(res).toUpperCase();
53
+ const msgLower = getMessageString(res).toLowerCase();
44
54
  if (res && res.success === false && (errorCode === 'FACTORY_NOT_AVAILABLE' || msgLower.includes('ulevelsequencefactorynew not available'))) {
45
55
  const path = sequencePath || (typeof args.path === 'string' ? args.path : undefined);
46
56
  return cleanObject({
@@ -75,8 +85,8 @@ export async function handleSequenceTools(action, args, tools) {
75
85
  subAction: 'add_actor'
76
86
  };
77
87
  const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
78
- const errorCode = String((res && res.error) || '').toUpperCase();
79
- const msgLower = String((res && res.message) || '').toLowerCase();
88
+ const errorCode = getErrorString(res).toUpperCase();
89
+ const msgLower = getMessageString(res).toLowerCase();
80
90
  if (res && res.success === false && path) {
81
91
  const isInvalidSequence = errorCode === 'INVALID_SEQUENCE' || msgLower.includes('sequence_add_actor requires a sequence path') || msgLower.includes('sequence not found');
82
92
  if (isInvalidSequence) {
@@ -114,8 +124,8 @@ export async function handleSequenceTools(action, args, tools) {
114
124
  case 'add_actors': {
115
125
  const actorNames = Array.isArray(args.actorNames) ? args.actorNames : [];
116
126
  const res = await tools.sequenceTools.addActors({ actorNames, path: args.path });
117
- const errorCode = String((res && res.error) || '').toUpperCase();
118
- const msgLower = String((res && res.message) || '').toLowerCase();
127
+ const errorCode = getErrorString(res).toUpperCase();
128
+ const msgLower = getMessageString(res).toLowerCase();
119
129
  if (actorNames.length === 0 && res && res.success === false && errorCode === 'INVALID_ARGUMENT') {
120
130
  return cleanObject({
121
131
  success: false,
@@ -172,8 +182,8 @@ export async function handleSequenceTools(action, args, tools) {
172
182
  payload.value = { scale: args.value };
173
183
  }
174
184
  const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
175
- const errorCode = String((res && res.error) || '').toUpperCase();
176
- const msgLower = String((res && res.message) || '').toLowerCase();
185
+ const errorCode = getErrorString(res).toUpperCase();
186
+ const msgLower = getMessageString(res).toLowerCase();
177
187
  if (errorCode === 'INVALID_ARGUMENT' || msgLower.includes('frame number is required')) {
178
188
  return cleanObject(res);
179
189
  }
@@ -237,7 +247,7 @@ export async function handleSequenceTools(action, args, tools) {
237
247
  throw new Error('Invalid speed: must be a positive number');
238
248
  }
239
249
  let res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path });
240
- const errorCode = String((res && res.error) || '').toUpperCase();
250
+ const errorCode = getErrorString(res).toUpperCase();
241
251
  if ((!res || res.success === false) && errorCode === 'EDITOR_NOT_OPEN' && args.path) {
242
252
  await tools.sequenceTools.open({ path: args.path });
243
253
  await new Promise(resolve => setTimeout(resolve, 1000));
@@ -252,7 +262,8 @@ export async function handleSequenceTools(action, args, tools) {
252
262
  case 'duplicate': {
253
263
  const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
254
264
  const destDir = requireNonEmptyString(args.destinationPath, 'destinationPath', 'Missing required parameter: destinationPath');
255
- const newName = requireNonEmptyString(args.newName || path.split('/').pop(), 'newName', 'Missing required parameter: newName');
265
+ const defaultNewName = path.split('/').pop() || '';
266
+ const newName = requireNonEmptyString(args.newName || defaultNewName, 'newName', 'Missing required parameter: newName');
256
267
  const baseDir = destDir.replace(/\/$/, '');
257
268
  const destPath = `${baseDir}/${newName}`;
258
269
  const res = await tools.sequenceTools.duplicate({ path, destinationPath: destPath });
@@ -262,8 +273,8 @@ export async function handleSequenceTools(action, args, tools) {
262
273
  const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
263
274
  const newName = requireNonEmptyString(args.newName, 'newName', 'Missing required parameter: newName');
264
275
  const res = await tools.sequenceTools.rename({ path, newName });
265
- const errorCode = String((res && res.error) || '').toUpperCase();
266
- const msgLower = String((res && res.message) || '').toLowerCase();
276
+ const errorCode = getErrorString(res).toUpperCase();
277
+ const msgLower = getMessageString(res).toLowerCase();
267
278
  if (res && res.success === false && (errorCode === 'OPERATION_FAILED' || msgLower.includes('failed to rename sequence'))) {
268
279
  return cleanObject({
269
280
  success: false,
@@ -31,7 +31,7 @@ export interface PropertyInfo {
31
31
  type: string;
32
32
  value?: any;
33
33
  flags?: string[];
34
- metadata?: Record<string, any>;
34
+ metadata?: Record<string, unknown>;
35
35
  category?: string;
36
36
  tooltip?: string;
37
37
  description?: string;
@@ -339,7 +339,7 @@ export class IntrospectionTools {
339
339
  return res;
340
340
  }
341
341
  catch (err) {
342
- const errorMsg = err?.message || String(err);
342
+ const errorMsg = (err instanceof Error ? err.message : undefined) || String(err);
343
343
  if (errorMsg.includes('404')) {
344
344
  return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
345
345
  }
@@ -24,7 +24,7 @@ export class LevelTools extends BaseTool {
24
24
  formatted = sanitizePath(formatted);
25
25
  }
26
26
  catch (e) {
27
- throw new Error(`Security validation failed for level path: ${e.message}`);
27
+ throw new Error(`Security validation failed for level path: ${e instanceof Error ? e.message : String(e)}`);
28
28
  }
29
29
  formatted = formatted.replace(/\.umap$/i, '');
30
30
  if (formatted.endsWith('/')) {
@@ -261,7 +261,7 @@ export class LevelTools extends BaseTool {
261
261
  };
262
262
  }
263
263
  catch (e) {
264
- return { success: false, error: `Export failed: ${e.message}` };
264
+ return { success: false, error: `Export failed: ${e instanceof Error ? e.message : String(e)}` };
265
265
  }
266
266
  }
267
267
  async importLevel(params) {
@@ -292,7 +292,7 @@ export class LevelTools extends BaseTool {
292
292
  };
293
293
  }
294
294
  catch (e) {
295
- return { success: false, error: `Import failed: ${e.message}` };
295
+ return { success: false, error: `Import failed: ${e instanceof Error ? e.message : String(e)}` };
296
296
  }
297
297
  }
298
298
  async saveLevelAs(params) {
@@ -19,8 +19,16 @@ export declare class LightingTools {
19
19
  castShadows?: boolean;
20
20
  temperature?: number;
21
21
  useAsAtmosphereSunLight?: boolean;
22
- properties?: Record<string, any>;
23
- }): Promise<any>;
22
+ properties?: Record<string, unknown>;
23
+ }): Promise<{
24
+ success: boolean;
25
+ message: string;
26
+ error?: undefined;
27
+ } | {
28
+ success: boolean;
29
+ error: string;
30
+ message?: undefined;
31
+ }>;
24
32
  createPointLight(params: {
25
33
  name: string;
26
34
  location?: [number, number, number];
@@ -34,7 +42,15 @@ export declare class LightingTools {
34
42
  yaw: number;
35
43
  roll: number;
36
44
  };
37
- }): Promise<any>;
45
+ }): Promise<{
46
+ success: boolean;
47
+ message: string;
48
+ error?: undefined;
49
+ } | {
50
+ success: boolean;
51
+ error: string;
52
+ message?: undefined;
53
+ }>;
38
54
  createSpotLight(params: {
39
55
  name: string;
40
56
  location: [number, number, number];
@@ -49,7 +65,15 @@ export declare class LightingTools {
49
65
  radius?: number;
50
66
  color?: [number, number, number];
51
67
  castShadows?: boolean;
52
- }): Promise<any>;
68
+ }): Promise<{
69
+ success: boolean;
70
+ message: string;
71
+ error?: undefined;
72
+ } | {
73
+ success: boolean;
74
+ error: string;
75
+ message?: undefined;
76
+ }>;
53
77
  createRectLight(params: {
54
78
  name: string;
55
79
  location: [number, number, number];
@@ -63,7 +87,15 @@ export declare class LightingTools {
63
87
  intensity?: number;
64
88
  color?: [number, number, number];
65
89
  castShadows?: boolean;
66
- }): Promise<any>;
90
+ }): Promise<{
91
+ success: boolean;
92
+ message: string;
93
+ error?: undefined;
94
+ } | {
95
+ success: boolean;
96
+ error: string;
97
+ message?: undefined;
98
+ }>;
67
99
  createDynamicLight(params: {
68
100
  name?: string;
69
101
  lightType?: 'Point' | 'Spot' | 'Directional' | 'Rect' | string;
@@ -84,7 +116,15 @@ export declare class LightingTools {
84
116
  enabled?: boolean;
85
117
  frequency?: number;
86
118
  };
87
- }): Promise<any>;
119
+ }): Promise<{
120
+ success: boolean;
121
+ message: string;
122
+ error?: undefined;
123
+ } | {
124
+ success: boolean;
125
+ error: string;
126
+ message?: undefined;
127
+ }>;
88
128
  createSkyLight(params: {
89
129
  name: string;
90
130
  sourceType?: 'CapturedScene' | 'SpecifiedCubemap';
@@ -148,7 +188,14 @@ export declare class LightingTools {
148
188
  buildOnlySelected?: boolean;
149
189
  buildReflectionCaptures?: boolean;
150
190
  levelPath?: string;
151
- }): Promise<any>;
191
+ }): Promise<{
192
+ success: boolean;
193
+ error: string;
194
+ } | {
195
+ success: boolean;
196
+ message: string;
197
+ error?: undefined;
198
+ }>;
152
199
  createLightingEnabledLevel(params?: {
153
200
  levelName?: string;
154
201
  copyActors?: boolean;
@@ -128,7 +128,7 @@ export class LightingTools {
128
128
  return { success: true, message: `Directional light '${name}' spawned` };
129
129
  }
130
130
  catch (e) {
131
- return { success: false, error: `Failed to create directional light: ${e?.message ?? e}` };
131
+ return { success: false, error: `Failed to create directional light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
132
132
  }
133
133
  }
134
134
  async createPointLight(params) {
@@ -202,7 +202,7 @@ export class LightingTools {
202
202
  return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
203
203
  }
204
204
  catch (e) {
205
- return { success: false, error: `Failed to create point light: ${e?.message ?? e}` };
205
+ return { success: false, error: `Failed to create point light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
206
206
  }
207
207
  }
208
208
  async createSpotLight(params) {
@@ -302,7 +302,7 @@ export class LightingTools {
302
302
  return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
303
303
  }
304
304
  catch (e) {
305
- return { success: false, error: `Failed to create spot light: ${e?.message ?? e}` };
305
+ return { success: false, error: `Failed to create spot light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
306
306
  }
307
307
  }
308
308
  async createRectLight(params) {
@@ -388,7 +388,7 @@ export class LightingTools {
388
388
  return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
389
389
  }
390
390
  catch (e) {
391
- return { success: false, error: `Failed to create rect light: ${e?.message ?? e}` };
391
+ return { success: false, error: `Failed to create rect light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
392
392
  }
393
393
  }
394
394
  async createDynamicLight(params) {
@@ -43,7 +43,7 @@ export declare class MaterialTools {
43
43
  transport?: undefined;
44
44
  warnings?: undefined;
45
45
  }>;
46
- createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, any>): Promise<any>;
46
+ createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, unknown>): Promise<any>;
47
47
  addNode(params: {
48
48
  materialPath: string;
49
49
  nodeType: string;
@@ -3,6 +3,8 @@ export interface BaseToolResponse {
3
3
  message?: string;
4
4
  error?: string;
5
5
  warning?: string;
6
+ retriable?: boolean;
7
+ scope?: string;
6
8
  }
7
9
  export interface AssetInfo {
8
10
  Name: string;
@@ -206,7 +206,7 @@ export class UnrealBridge {
206
206
  const success = response.success !== false;
207
207
  const rawResult = response.result && typeof response.result === 'object'
208
208
  ? { ...response.result }
209
- : response.result;
209
+ : undefined;
210
210
  const value = rawResult?.value ??
211
211
  rawResult?.propertyValue ??
212
212
  (success ? rawResult : undefined);
@@ -295,7 +295,7 @@ export class UnrealBridge {
295
295
  const success = response.success !== false;
296
296
  const rawResult = response.result && typeof response.result === 'object'
297
297
  ? { ...response.result }
298
- : response.result;
298
+ : undefined;
299
299
  if (success) {
300
300
  return {
301
301
  success: true,
@@ -423,7 +423,7 @@ export class UnrealBridge {
423
423
  const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_engine_version' }, { timeoutMs: 15000 });
424
424
  const raw = resp && typeof resp.result === 'object'
425
425
  ? resp.result
426
- : (resp?.result ?? resp ?? {});
426
+ : resp?.result ?? resp ?? {};
427
427
  const version = typeof raw.version === 'string' ? raw.version : 'unknown';
428
428
  const major = typeof raw.major === 'number' ? raw.major : 0;
429
429
  const minor = typeof raw.minor === 'number' ? raw.minor : 0;
@@ -459,7 +459,7 @@ export class UnrealBridge {
459
459
  const resp = await bridge.sendAutomationRequest('system_control', { action: 'get_feature_flags' }, { timeoutMs: 15000 });
460
460
  const raw = resp && typeof resp.result === 'object'
461
461
  ? resp.result
462
- : (resp?.result ?? resp ?? {});
462
+ : resp?.result ?? resp ?? {};
463
463
  const subs = raw && typeof raw.subsystems === 'object'
464
464
  ? raw.subsystems
465
465
  : {};
@@ -1,12 +1,13 @@
1
1
  export class CommandValidator {
2
2
  static DANGEROUS_COMMANDS = [
3
- 'quit', 'exit', 'delete', 'destroy', 'kill', 'crash',
3
+ 'quit', 'exit', 'kill', 'crash',
4
+ 'r.gpucrash', 'r.crash', 'debug crash', 'forcecrash', 'debug break',
5
+ 'assert false', 'check(false)',
4
6
  'viewmode visualizebuffer basecolor',
5
7
  'viewmode visualizebuffer worldnormal',
6
- 'r.gpucrash',
7
- 'buildpaths',
8
- 'rebuildnavigation',
9
- 'obj garbage', 'obj list', 'memreport'
8
+ 'buildpaths', 'rebuildnavigation',
9
+ 'obj garbage', 'obj list', 'memreport',
10
+ 'delete', 'destroy'
10
11
  ];
11
12
  static FORBIDDEN_TOKENS = [
12
13
  'rm ', 'rm-', 'del ', 'format ', 'shutdown', 'reboot',
@@ -8,8 +8,29 @@ export declare enum ErrorType {
8
8
  TIMEOUT = "TIMEOUT",
9
9
  UNKNOWN = "UNKNOWN"
10
10
  }
11
+ interface ErrorResponseDebug {
12
+ errorType: ErrorType;
13
+ originalError: string;
14
+ stack?: string;
15
+ context?: Record<string, unknown>;
16
+ retriable: boolean;
17
+ scope: string;
18
+ }
19
+ interface ErrorToolResponse extends BaseToolResponse {
20
+ _debug?: ErrorResponseDebug;
21
+ }
22
+ interface ErrorLike {
23
+ message?: string;
24
+ code?: string;
25
+ type?: string;
26
+ errorType?: string;
27
+ stack?: string;
28
+ response?: {
29
+ status?: number;
30
+ };
31
+ }
11
32
  export declare class ErrorHandler {
12
- static createErrorResponse(error: any, toolName: string, context?: any): BaseToolResponse;
33
+ static createErrorResponse(error: unknown, toolName: string, context?: Record<string, unknown>): ErrorToolResponse;
13
34
  private static categorizeError;
14
35
  private static getUserFriendlyMessage;
15
36
  private static isRetriable;
@@ -18,7 +39,8 @@ export declare class ErrorHandler {
18
39
  initialDelay?: number;
19
40
  maxDelay?: number;
20
41
  backoffMultiplier?: number;
21
- shouldRetry?: (error: any) => boolean;
42
+ shouldRetry?: (error: ErrorLike | Error | unknown) => boolean;
22
43
  }): Promise<T>;
23
44
  }
45
+ export {};
24
46
  //# sourceMappingURL=error-handler.d.ts.map
@@ -10,43 +10,75 @@ export var ErrorType;
10
10
  ErrorType["TIMEOUT"] = "TIMEOUT";
11
11
  ErrorType["UNKNOWN"] = "UNKNOWN";
12
12
  })(ErrorType || (ErrorType = {}));
13
+ function normalizeErrorToLike(error) {
14
+ if (error instanceof Error) {
15
+ return {
16
+ message: error.message,
17
+ stack: error.stack,
18
+ code: error.code
19
+ };
20
+ }
21
+ if (typeof error === 'object' && error !== null) {
22
+ const obj = error;
23
+ return {
24
+ message: typeof obj.message === 'string' ? obj.message : undefined,
25
+ code: typeof obj.code === 'string' ? obj.code : undefined,
26
+ type: typeof obj.type === 'string' ? obj.type : undefined,
27
+ errorType: typeof obj.errorType === 'string' ? obj.errorType : undefined,
28
+ stack: typeof obj.stack === 'string' ? obj.stack : undefined,
29
+ response: typeof obj.response === 'object' && obj.response !== null
30
+ ? {
31
+ status: typeof obj.response.status === 'number'
32
+ ? obj.response.status
33
+ : undefined
34
+ }
35
+ : undefined
36
+ };
37
+ }
38
+ return { message: String(error) };
39
+ }
13
40
  export class ErrorHandler {
14
41
  static createErrorResponse(error, toolName, context) {
15
- const errorType = this.categorizeError(error);
16
- const userMessage = this.getUserFriendlyMessage(errorType, error);
17
- const retriable = this.isRetriable(error);
42
+ const errorObj = normalizeErrorToLike(error);
43
+ const errorType = this.categorizeError(errorObj);
44
+ const userMessage = this.getUserFriendlyMessage(errorType, errorObj);
45
+ const retriable = this.isRetriable(errorObj);
18
46
  const scope = context?.scope || `tool-call/${toolName}`;
47
+ const errorMessage = errorObj.message || String(error);
48
+ const errorStack = errorObj.stack;
19
49
  log.error(`Tool ${toolName} failed:`, {
20
50
  type: errorType,
21
- message: error.message || error,
51
+ message: errorMessage,
22
52
  retriable,
23
53
  scope,
24
54
  context
25
55
  });
26
- return {
56
+ const response = {
27
57
  success: false,
28
58
  error: userMessage,
29
59
  message: `Failed to execute ${toolName}: ${userMessage}`,
30
- retriable: retriable,
31
- scope: scope,
32
- ...(process.env.NODE_ENV === 'development' && {
33
- _debug: {
34
- errorType,
35
- originalError: error.message || String(error),
36
- stack: error.stack,
37
- context,
38
- retriable,
39
- scope
40
- }
41
- })
60
+ retriable,
61
+ scope
42
62
  };
63
+ if (process.env.NODE_ENV === 'development') {
64
+ response._debug = {
65
+ errorType,
66
+ originalError: errorMessage,
67
+ stack: errorStack,
68
+ context,
69
+ retriable,
70
+ scope
71
+ };
72
+ }
73
+ return response;
43
74
  }
44
75
  static categorizeError(error) {
45
- const explicitType = (error?.type || error?.errorType || '').toString().toUpperCase();
76
+ const errorObj = typeof error === 'object' ? error : null;
77
+ const explicitType = (errorObj?.type || errorObj?.errorType || '').toString().toUpperCase();
46
78
  if (explicitType && Object.values(ErrorType).includes(explicitType)) {
47
79
  return explicitType;
48
80
  }
49
- const errorMessage = error?.message?.toLowerCase() || String(error).toLowerCase();
81
+ const errorMessage = (errorObj?.message || String(error)).toLowerCase();
50
82
  if (errorMessage.includes('econnrefused') ||
51
83
  errorMessage.includes('timeout') ||
52
84
  errorMessage.includes('connection') ||
@@ -77,7 +109,9 @@ export class ErrorHandler {
77
109
  return ErrorType.UNKNOWN;
78
110
  }
79
111
  static getUserFriendlyMessage(type, error) {
80
- const originalMessage = error.message || String(error);
112
+ const originalMessage = (typeof error === 'object' && error !== null && 'message' in error)
113
+ ? error.message || String(error)
114
+ : String(error);
81
115
  switch (type) {
82
116
  case ErrorType.CONNECTION:
83
117
  return 'Failed to connect to Unreal Engine. Please ensure the Automation Bridge plugin is active and the editor is running.';
@@ -97,9 +131,10 @@ export class ErrorHandler {
97
131
  }
98
132
  static isRetriable(error) {
99
133
  try {
100
- const code = (error?.code || '').toString().toUpperCase();
101
- const msg = (error?.message || String(error) || '').toLowerCase();
102
- const status = Number((error?.response?.status));
134
+ const errorObj = typeof error === 'object' ? error : null;
135
+ const code = (errorObj?.code || '').toString().toUpperCase();
136
+ const msg = (errorObj?.message || String(error) || '').toLowerCase();
137
+ const status = Number(errorObj?.response?.status);
103
138
  if (['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE'].includes(code))
104
139
  return true;
105
140
  if (/timeout|timed out|network|connection|closed|unavailable|busy|temporar/.test(msg))
@@ -10,11 +10,14 @@ export interface Rot3Obj {
10
10
  }
11
11
  export type Vec3Tuple = [number, number, number];
12
12
  export type Rot3Tuple = [number, number, number];
13
- export declare function toVec3Object(input: any): Vec3Obj | null;
14
- export declare function toRotObject(input: any): Rot3Obj | null;
15
- export declare function toVec3Tuple(input: any): Vec3Tuple | null;
16
- export declare function toRotTuple(input: any): Rot3Tuple | null;
13
+ type VectorInput = Vec3Obj | Vec3Tuple | Record<string, unknown> | unknown[];
14
+ type RotationInput = Rot3Obj | Rot3Tuple | Record<string, unknown> | unknown[];
15
+ export declare function toVec3Object(input: VectorInput | unknown): Vec3Obj | null;
16
+ export declare function toRotObject(input: RotationInput | unknown): Rot3Obj | null;
17
+ export declare function toVec3Tuple(input: VectorInput | unknown): Vec3Tuple | null;
18
+ export declare function toRotTuple(input: RotationInput | unknown): Rot3Tuple | null;
17
19
  export declare function toFiniteNumber(raw: unknown): number | undefined;
18
20
  export declare function normalizePartialVector(value: any, alternateKeys?: string[]): Record<string, number> | undefined;
19
21
  export declare function normalizeTransformInput(transform: any): Record<string, unknown> | undefined;
22
+ export {};
20
23
  //# sourceMappingURL=normalize.d.ts.map
@@ -3,13 +3,14 @@ export function toVec3Object(input) {
3
3
  if (Array.isArray(input) && input.length === 3) {
4
4
  const [x, y, z] = input;
5
5
  if ([x, y, z].every(v => typeof v === 'number' && isFinite(v))) {
6
- return { x, y, z };
6
+ return { x: x, y: y, z: z };
7
7
  }
8
8
  }
9
- if (input && typeof input === 'object') {
10
- const x = Number(input.x ?? input.X);
11
- const y = Number(input.y ?? input.Y);
12
- const z = Number(input.z ?? input.Z);
9
+ if (input && typeof input === 'object' && !Array.isArray(input)) {
10
+ const obj = input;
11
+ const x = Number(obj.x ?? obj.X);
12
+ const y = Number(obj.y ?? obj.Y);
13
+ const z = Number(obj.z ?? obj.Z);
13
14
  if ([x, y, z].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
14
15
  return { x, y, z };
15
16
  }
@@ -23,13 +24,14 @@ export function toRotObject(input) {
23
24
  if (Array.isArray(input) && input.length === 3) {
24
25
  const [pitch, yaw, roll] = input;
25
26
  if ([pitch, yaw, roll].every(v => typeof v === 'number' && isFinite(v))) {
26
- return { pitch, yaw, roll };
27
+ return { pitch: pitch, yaw: yaw, roll: roll };
27
28
  }
28
29
  }
29
- if (input && typeof input === 'object') {
30
- const pitch = Number(input.pitch ?? input.Pitch);
31
- const yaw = Number(input.yaw ?? input.Yaw);
32
- const roll = Number(input.roll ?? input.Roll);
30
+ if (input && typeof input === 'object' && !Array.isArray(input)) {
31
+ const obj = input;
32
+ const pitch = Number(obj.pitch ?? obj.Pitch);
33
+ const yaw = Number(obj.yaw ?? obj.Yaw);
34
+ const roll = Number(obj.roll ?? obj.Roll);
33
35
  if ([pitch, yaw, roll].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
34
36
  return { pitch, yaw, roll };
35
37
  }