unreal-engine-mcp-server 0.5.0 → 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.
- package/.env.example +1 -1
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/greetings.yml +5 -1
- package/.github/workflows/labeler.yml +2 -1
- package/.github/workflows/publish-mcp.yml +2 -4
- package/.github/workflows/release-drafter.yml +3 -2
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +109 -0
- package/CONTRIBUTING.md +1 -1
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +166 -200
- package/dist/automation/bridge.d.ts +1 -2
- package/dist/automation/bridge.js +24 -23
- package/dist/automation/connection-manager.d.ts +1 -0
- package/dist/automation/connection-manager.js +10 -0
- package/dist/automation/message-handler.js +5 -4
- package/dist/automation/request-tracker.d.ts +4 -0
- package/dist/automation/request-tracker.js +11 -3
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +3 -3
- package/dist/graphql/resolvers.js +33 -30
- package/dist/graphql/server.js +3 -1
- package/dist/graphql/types.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/dist/server-setup.d.ts +0 -1
- package/dist/server-setup.js +0 -40
- package/dist/tools/actors.d.ts +58 -24
- package/dist/tools/actors.js +22 -6
- package/dist/tools/assets.d.ts +19 -71
- package/dist/tools/assets.js +28 -22
- package/dist/tools/base-tool.d.ts +4 -4
- package/dist/tools/base-tool.js +1 -1
- package/dist/tools/blueprint.d.ts +45 -61
- package/dist/tools/blueprint.js +43 -14
- package/dist/tools/consolidated-tool-definitions.js +2 -1
- package/dist/tools/consolidated-tool-handlers.js +96 -110
- package/dist/tools/dynamic-handler-registry.d.ts +11 -9
- package/dist/tools/dynamic-handler-registry.js +17 -95
- package/dist/tools/editor.d.ts +19 -193
- package/dist/tools/editor.js +11 -2
- package/dist/tools/environment.d.ts +8 -14
- package/dist/tools/foliage.d.ts +18 -143
- package/dist/tools/foliage.js +4 -2
- package/dist/tools/handlers/actor-handlers.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.js +14 -13
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/sequence-handlers.js +24 -13
- package/dist/tools/introspection.d.ts +1 -1
- package/dist/tools/introspection.js +1 -1
- package/dist/tools/landscape.d.ts +16 -116
- package/dist/tools/landscape.js +7 -3
- package/dist/tools/level.d.ts +22 -103
- package/dist/tools/level.js +26 -18
- package/dist/tools/lighting.d.ts +54 -7
- package/dist/tools/lighting.js +9 -5
- package/dist/tools/materials.d.ts +1 -1
- package/dist/tools/materials.js +5 -1
- package/dist/tools/niagara.js +37 -2
- package/dist/tools/performance.d.ts +0 -1
- package/dist/tools/performance.js +0 -1
- package/dist/tools/physics.js +5 -1
- package/dist/tools/sequence.d.ts +24 -24
- package/dist/tools/sequence.js +13 -0
- package/dist/tools/ui.d.ts +0 -2
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +135 -135
- package/dist/types/tool-types.d.ts +2 -0
- package/dist/unreal-bridge.js +4 -4
- package/dist/utils/command-validator.js +7 -5
- package/dist/utils/error-handler.d.ts +24 -2
- package/dist/utils/error-handler.js +58 -23
- package/dist/utils/normalize.d.ts +7 -4
- package/dist/utils/normalize.js +12 -10
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +4 -4
- package/dist/utils/response-factory.js +15 -21
- package/dist/utils/response-validator.js +88 -73
- package/dist/utils/unreal-command-queue.d.ts +2 -0
- package/dist/utils/unreal-command-queue.js +8 -1
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/handler-mapping.md +4 -2
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/automation/bridge.ts +27 -25
- package/src/automation/connection-manager.ts +18 -0
- package/src/automation/message-handler.ts +33 -8
- package/src/automation/request-tracker.ts +39 -7
- package/src/config.ts +1 -1
- package/src/constants.ts +7 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +47 -49
- package/src/graphql/server.ts +3 -1
- package/src/graphql/types.ts +3 -0
- package/src/index.ts +15 -2
- package/src/resources/assets.ts +5 -4
- package/src/server/tool-registry.ts +3 -3
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +77 -44
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +76 -65
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +170 -104
- package/src/tools/consolidated-tool-definitions.ts +2 -1
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +43 -29
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +16 -17
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +85 -62
- package/src/tools/introspection.ts +7 -7
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +100 -80
- package/src/tools/lighting.ts +25 -20
- package/src/tools/materials.ts +9 -3
- package/src/tools/niagara.ts +44 -2
- package/src/tools/performance.ts +1 -2
- package/src/tools/physics.ts +7 -1
- package/src/tools/sequence.ts +42 -26
- package/src/tools/ui.ts +1 -3
- package/src/types/automation-responses.ts +119 -0
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +135 -135
- package/src/types/tool-types.ts +4 -0
- package/src/unreal-bridge.ts +71 -26
- package/src/utils/command-validator.ts +47 -5
- package/src/utils/error-handler.ts +128 -45
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/normalize.ts +38 -16
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/response-validator.ts +103 -87
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/unreal-command-queue.ts +13 -1
- package/src/utils/validation.test.ts +184 -0
- package/tests/test-animation.mjs +358 -33
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +314 -116
- package/tests/test-behavior-tree.mjs +327 -144
- package/tests/test-blueprint-graph.mjs +343 -12
- package/tests/test-control-editor.mjs +85 -53
- package/tests/test-graphql.mjs +58 -8
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +291 -61
- package/tests/test-landscape.mjs +304 -48
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-level.mjs +70 -51
- package/tests/test-performance.mjs +539 -0
- package/tests/test-sequence.mjs +82 -46
- package/tests/test-system.mjs +72 -33
- package/tests/test-wasm.mjs +98 -8
- package/vitest.config.ts +35 -0
- package/.github/release-drafter.yml +0 -148
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/blueprint/helpers.d.ts +0 -29
- package/dist/tools/blueprint/helpers.js +0 -182
- package/src/prompts/index.ts +0 -249
- package/src/tools/blueprint/helpers.ts +0 -189
- package/tests/test-blueprint-events.mjs +0 -35
- package/tests/test-extra-tools.mjs +0 -38
- package/tests/test-render.mjs +0 -33
- package/tests/test-search-assets.mjs +0 -66
package/dist/unreal-bridge.js
CHANGED
|
@@ -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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
-
:
|
|
462
|
+
: resp?.result ?? resp ?? {};
|
|
463
463
|
const subs = raw && typeof raw.subsystems === 'object'
|
|
464
464
|
? raw.subsystems
|
|
465
465
|
: {};
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
export class CommandValidator {
|
|
2
2
|
static DANGEROUS_COMMANDS = [
|
|
3
|
-
'quit', 'exit', '
|
|
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
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
8
|
+
'buildpaths', 'rebuildnavigation',
|
|
9
|
+
'obj garbage', 'obj list', 'memreport',
|
|
10
|
+
'delete', 'destroy'
|
|
9
11
|
];
|
|
10
12
|
static FORBIDDEN_TOKENS = [
|
|
11
13
|
'rm ', 'rm-', 'del ', 'format ', 'shutdown', 'reboot',
|
|
12
14
|
'rmdir', 'mklink', 'copy ', 'move ', 'start "', 'system(',
|
|
13
15
|
'import os', 'import subprocess', 'subprocess.', 'os.system',
|
|
14
16
|
'exec(', 'eval(', '__import__', 'import sys', 'import importlib',
|
|
15
|
-
'with open', 'open('
|
|
17
|
+
'with open', 'open(', 'write(', 'read('
|
|
16
18
|
];
|
|
17
19
|
static INVALID_PATTERNS = [
|
|
18
20
|
/^\d+$/,
|
|
@@ -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:
|
|
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:
|
|
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
|
|
16
|
-
const
|
|
17
|
-
const
|
|
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:
|
|
51
|
+
message: errorMessage,
|
|
22
52
|
retriable,
|
|
23
53
|
scope,
|
|
24
54
|
context
|
|
25
55
|
});
|
|
26
|
-
|
|
56
|
+
const response = {
|
|
27
57
|
success: false,
|
|
28
58
|
error: userMessage,
|
|
29
59
|
message: `Failed to execute ${toolName}: ${userMessage}`,
|
|
30
|
-
retriable
|
|
31
|
-
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
|
|
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 =
|
|
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
|
|
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
|
|
101
|
-
const
|
|
102
|
-
const
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function
|
|
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
|
package/dist/utils/normalize.js
CHANGED
|
@@ -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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
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
|
|
31
|
-
const
|
|
32
|
-
const
|
|
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
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function sanitizePath(path, allowedRoots = ['/Game', '/Engine']) {
|
|
2
|
+
if (!path || typeof path !== 'string') {
|
|
3
|
+
throw new Error('Invalid path: must be a non-empty string');
|
|
4
|
+
}
|
|
5
|
+
const trimmed = path.trim();
|
|
6
|
+
if (trimmed.length === 0) {
|
|
7
|
+
throw new Error('Invalid path: cannot be empty');
|
|
8
|
+
}
|
|
9
|
+
const normalized = trimmed.replace(/\\/g, '/');
|
|
10
|
+
if (normalized.includes('..')) {
|
|
11
|
+
throw new Error('Invalid path: directory traversal (..) is not allowed');
|
|
12
|
+
}
|
|
13
|
+
const isAllowed = allowedRoots.some(root => normalized.toLowerCase() === root.toLowerCase() ||
|
|
14
|
+
normalized.toLowerCase().startsWith(`${root.toLowerCase()}/`));
|
|
15
|
+
if (!isAllowed) {
|
|
16
|
+
throw new Error(`Invalid path: must start with one of [${allowedRoots.join(', ')}]`);
|
|
17
|
+
}
|
|
18
|
+
const invalidChars = /[<>:"|?*\x00-\x1f]/;
|
|
19
|
+
if (invalidChars.test(normalized)) {
|
|
20
|
+
throw new Error('Invalid path: contains illegal characters');
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=path-security.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StandardActionResponse } from '../types/tool-interfaces.js';
|
|
2
2
|
export declare class ResponseFactory {
|
|
3
|
-
static success(
|
|
4
|
-
static error(
|
|
5
|
-
static
|
|
3
|
+
static success(data: any, message?: string): StandardActionResponse;
|
|
4
|
+
static error(error: any, defaultMessage?: string): StandardActionResponse;
|
|
5
|
+
static validationError(message: string): StandardActionResponse;
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=response-factory.d.ts.map
|
|
@@ -1,32 +1,26 @@
|
|
|
1
|
+
import { cleanObject } from './safe-json.js';
|
|
1
2
|
export class ResponseFactory {
|
|
2
|
-
static success(
|
|
3
|
-
const content = [{ type: 'text', text: message }];
|
|
4
|
-
if (data) {
|
|
5
|
-
content.push({
|
|
6
|
-
type: 'text',
|
|
7
|
-
text: JSON.stringify(data, null, 2)
|
|
8
|
-
});
|
|
9
|
-
}
|
|
3
|
+
static success(data, message = 'Operation successful') {
|
|
10
4
|
return {
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
success: true,
|
|
6
|
+
message,
|
|
7
|
+
data: cleanObject(data)
|
|
13
8
|
};
|
|
14
9
|
}
|
|
15
|
-
static error(
|
|
16
|
-
const
|
|
17
|
-
|
|
10
|
+
static error(error, defaultMessage = 'Operation failed') {
|
|
11
|
+
const errorMessage = error instanceof Error ? error.message : String(error || defaultMessage);
|
|
12
|
+
console.error('[ResponseFactory] Error:', error);
|
|
18
13
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
success: false,
|
|
15
|
+
message: errorMessage,
|
|
16
|
+
data: null
|
|
21
17
|
};
|
|
22
18
|
}
|
|
23
|
-
static
|
|
19
|
+
static validationError(message) {
|
|
24
20
|
return {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}],
|
|
29
|
-
isError: false
|
|
21
|
+
success: false,
|
|
22
|
+
message: `Validation Error: ${message}`,
|
|
23
|
+
data: null
|
|
30
24
|
};
|
|
31
25
|
}
|
|
32
26
|
}
|
|
@@ -21,91 +21,106 @@ function buildSummaryText(toolName, payload) {
|
|
|
21
21
|
return `${toolName} responded`;
|
|
22
22
|
}
|
|
23
23
|
const effectivePayload = { ...payload };
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const listKeys = ['actors', 'levels', 'assets', 'folders', 'blueprints', 'components', 'pawnClasses', 'foliageTypes', 'nodes', 'tracks', 'bindings', 'keys'];
|
|
32
|
-
for (const key of listKeys) {
|
|
33
|
-
if (Array.isArray(effectivePayload[key])) {
|
|
34
|
-
const arr = effectivePayload[key];
|
|
35
|
-
const names = arr.map(i => isRecord(i) ? (i.name || i.path || i.id || i.assetName || i.objectPath || i.packageName || i.nodeName || '<?>') : String(i));
|
|
36
|
-
const count = arr.length;
|
|
37
|
-
const preview = names.slice(0, 100).join(', ');
|
|
38
|
-
const suffix = count > 100 ? `, ... (+${count - 100} more)` : '';
|
|
39
|
-
parts.push(`${key}: ${preview}${suffix} (Total: ${count})`);
|
|
24
|
+
const flattenWrappers = (obj, depth = 0) => {
|
|
25
|
+
if (depth > 5)
|
|
26
|
+
return;
|
|
27
|
+
if (isRecord(obj.data)) {
|
|
28
|
+
Object.assign(obj, obj.data);
|
|
29
|
+
delete obj.data;
|
|
30
|
+
flattenWrappers(obj, depth + 1);
|
|
40
31
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'package', 'dependencies', 'graph', 'tags', 'metadata', 'properties'
|
|
67
|
-
];
|
|
68
|
-
for (const key of usefulKeys) {
|
|
69
|
-
if (effectivePayload[key] !== undefined && effectivePayload[key] !== null) {
|
|
70
|
-
const val = effectivePayload[key];
|
|
71
|
-
if (typeof val === 'object') {
|
|
72
|
-
if (key === 'metadata' || key === 'properties' || key === 'tags') {
|
|
73
|
-
const entries = Object.entries(val);
|
|
74
|
-
const formatted = entries.map(([k, v]) => `${k}=${v}`);
|
|
75
|
-
const limit = 50;
|
|
76
|
-
parts.push(`${key}: { ${formatted.slice(0, limit).join(', ')}${formatted.length > limit ? '...' : ''} }`);
|
|
77
|
-
continue;
|
|
32
|
+
if (isRecord(obj.result)) {
|
|
33
|
+
Object.assign(obj, obj.result);
|
|
34
|
+
delete obj.result;
|
|
35
|
+
flattenWrappers(obj, depth + 1);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
flattenWrappers(effectivePayload);
|
|
39
|
+
const parts = [];
|
|
40
|
+
const addedKeys = new Set();
|
|
41
|
+
const skipKeys = new Set(['requestId', 'type', 'data', 'result', 'warnings']);
|
|
42
|
+
const formatValue = (val) => {
|
|
43
|
+
if (val === null || val === undefined)
|
|
44
|
+
return '';
|
|
45
|
+
if (typeof val === 'string')
|
|
46
|
+
return val.length > 150 ? val.slice(0, 150) + '...' : val;
|
|
47
|
+
if (typeof val === 'number' || typeof val === 'boolean')
|
|
48
|
+
return String(val);
|
|
49
|
+
if (Array.isArray(val)) {
|
|
50
|
+
if (val.length === 0)
|
|
51
|
+
return '[] (0)';
|
|
52
|
+
const items = val.slice(0, 30).map(v => {
|
|
53
|
+
if (isRecord(v)) {
|
|
54
|
+
return v.name || v.path || v.id || v.nodeId || v.nodeName || v.className ||
|
|
55
|
+
v.displayName || v.type || v.assetPath || v.objectPath ||
|
|
56
|
+
JSON.stringify(v).slice(0, 50);
|
|
78
57
|
}
|
|
79
|
-
|
|
58
|
+
return String(v);
|
|
59
|
+
});
|
|
60
|
+
const suffix = val.length > 30 ? `, ... (+${val.length - 30} more)` : '';
|
|
61
|
+
return `[${items.join(', ')}${suffix}] (${val.length})`;
|
|
62
|
+
}
|
|
63
|
+
if (isRecord(val)) {
|
|
64
|
+
const keys = Object.keys(val);
|
|
65
|
+
if (keys.some(k => ['x', 'y', 'z', 'pitch', 'yaw', 'roll'].includes(k))) {
|
|
66
|
+
const x = val.x ?? val.pitch ?? 0;
|
|
67
|
+
const y = val.y ?? val.yaw ?? 0;
|
|
68
|
+
const z = val.z ?? val.roll ?? 0;
|
|
69
|
+
return `[${x}, ${y}, ${z}]`;
|
|
80
70
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
const entries = Object.entries(val).slice(0, 8);
|
|
72
|
+
const formatted = entries.map(([k, v]) => {
|
|
73
|
+
const vStr = typeof v === 'object' ? JSON.stringify(v).slice(0, 40) : String(v);
|
|
74
|
+
return `${k}=${vStr}`;
|
|
75
|
+
});
|
|
76
|
+
return `{ ${formatted.join(', ')}${keys.length > 8 ? ' ...' : ''} }`;
|
|
77
|
+
}
|
|
78
|
+
return String(val);
|
|
79
|
+
};
|
|
80
|
+
for (const key of ['success', 'error']) {
|
|
81
|
+
if (effectivePayload[key] !== undefined && !addedKeys.has(key)) {
|
|
82
|
+
const formatted = formatValue(effectivePayload[key]);
|
|
83
|
+
if (formatted) {
|
|
84
|
+
parts.push(`${key}: ${formatted}`);
|
|
85
|
+
addedKeys.add(key);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
89
|
+
let hasArrays = false;
|
|
90
|
+
for (const [key, val] of Object.entries(effectivePayload)) {
|
|
91
|
+
if (addedKeys.has(key))
|
|
92
|
+
continue;
|
|
93
|
+
if (skipKeys.has(key))
|
|
94
|
+
continue;
|
|
95
|
+
if (val === undefined || val === null)
|
|
96
|
+
continue;
|
|
97
|
+
if (typeof val === 'string' && val.trim() === '')
|
|
98
|
+
continue;
|
|
99
|
+
if (key === 'message')
|
|
100
|
+
continue;
|
|
101
|
+
if (Array.isArray(val) && val.length > 0)
|
|
102
|
+
hasArrays = true;
|
|
103
|
+
if ((key === 'count' || key === 'totalCount') && hasArrays)
|
|
104
|
+
continue;
|
|
105
|
+
const formatted = formatValue(val);
|
|
106
|
+
if (formatted) {
|
|
107
|
+
parts.push(`${key}: ${formatted}`);
|
|
108
|
+
addedKeys.add(key);
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
const message = typeof effectivePayload.message === 'string' ? normalizeText(effectivePayload.message) : '';
|
|
112
|
+
if (message && message.toLowerCase() !== 'success') {
|
|
113
|
+
const isDuplicateInfo = /^(found|listed|retrieved|got|loaded|created|deleted|saved|spawned)\s+\d+/i.test(message) ||
|
|
114
|
+
/Folders:\s*\[/.test(message) ||
|
|
115
|
+
/\d+\s+(assets?|folders?|items?|actors?|components?)\s+(and|in|at)/i.test(message);
|
|
116
|
+
const messageInParts = parts.some(p => p.toLowerCase().includes(message.toLowerCase().slice(0, 30)));
|
|
117
|
+
if (!isDuplicateInfo && !messageInParts) {
|
|
99
118
|
parts.push(message);
|
|
100
|
-
if (error)
|
|
101
|
-
parts.push(`Error: ${error}`);
|
|
102
|
-
if (parts.length === 0 && success !== undefined) {
|
|
103
|
-
parts.push(success ? 'Success' : 'Failed');
|
|
104
119
|
}
|
|
105
120
|
}
|
|
106
|
-
const warnings = Array.isArray(
|
|
121
|
+
const warnings = Array.isArray(effectivePayload.warnings) ? effectivePayload.warnings : [];
|
|
107
122
|
if (warnings.length > 0) {
|
|
108
|
-
parts.push(`Warnings: ${warnings.
|
|
123
|
+
parts.push(`Warnings: ${warnings.map((w) => typeof w === 'string' ? w : JSON.stringify(w)).join('; ')}`);
|
|
109
124
|
}
|
|
110
125
|
return parts.length > 0 ? parts.join(' | ') : `${toolName} responded`;
|
|
111
126
|
}
|
|
@@ -11,6 +11,7 @@ export declare class UnrealCommandQueue {
|
|
|
11
11
|
private isProcessing;
|
|
12
12
|
private lastCommandTime;
|
|
13
13
|
private lastStatCommandTime;
|
|
14
|
+
private processorInterval?;
|
|
14
15
|
private readonly MIN_COMMAND_DELAY;
|
|
15
16
|
private readonly MAX_COMMAND_DELAY;
|
|
16
17
|
private readonly STAT_COMMAND_DELAY;
|
|
@@ -19,6 +20,7 @@ export declare class UnrealCommandQueue {
|
|
|
19
20
|
private processQueue;
|
|
20
21
|
private calculateDelay;
|
|
21
22
|
private startProcessor;
|
|
23
|
+
stopProcessor(): void;
|
|
22
24
|
private delay;
|
|
23
25
|
}
|
|
24
26
|
//# sourceMappingURL=unreal-command-queue.d.ts.map
|
|
@@ -5,6 +5,7 @@ export class UnrealCommandQueue {
|
|
|
5
5
|
isProcessing = false;
|
|
6
6
|
lastCommandTime = 0;
|
|
7
7
|
lastStatCommandTime = 0;
|
|
8
|
+
processorInterval;
|
|
8
9
|
MIN_COMMAND_DELAY = 100;
|
|
9
10
|
MAX_COMMAND_DELAY = 500;
|
|
10
11
|
STAT_COMMAND_DELAY = 300;
|
|
@@ -107,12 +108,18 @@ export class UnrealCommandQueue {
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
startProcessor() {
|
|
110
|
-
setInterval(() => {
|
|
111
|
+
this.processorInterval = setInterval(() => {
|
|
111
112
|
if (!this.isProcessing && this.queue.length > 0) {
|
|
112
113
|
this.processQueue();
|
|
113
114
|
}
|
|
114
115
|
}, 1000);
|
|
115
116
|
}
|
|
117
|
+
stopProcessor() {
|
|
118
|
+
if (this.processorInterval) {
|
|
119
|
+
clearInterval(this.processorInterval);
|
|
120
|
+
this.processorInterval = undefined;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
116
123
|
delay(ms) {
|
|
117
124
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
118
125
|
}
|
|
@@ -652,18 +652,10 @@ echo $WASM_ENABLED # Should be "true"
|
|
|
652
652
|
### Documentation
|
|
653
653
|
- [GraphQL API Documentation](GraphQL-API.md)
|
|
654
654
|
- [WebAssembly Integration Guide](WebAssembly-Integration.md)
|
|
655
|
-
- Added **GraphQL API** (optional) for complex queries.
|
|
656
|
-
- Disabled by default (`GRAPHQL_ENABLED=false`).
|
|
657
|
-
- Uses Apollo Server.
|
|
658
|
-
- Integrated with **Apollo Sandbox** (local IDE) or [Apollo Studio](https://studio.apollographql.com). - GraphQL platform
|
|
659
|
-
- [Postman](https://www.postman.com/) - API testing
|
|
660
|
-
|
|
661
|
-
### Learning
|
|
662
|
-
- [GraphQL.org](https://graphql.org/) - Official GraphQL documentation
|
|
663
|
-
- [WebAssembly.org](https://webassembly.org/) - WebAssembly specification
|
|
664
655
|
|
|
665
656
|
### Tools
|
|
666
657
|
- [GraphiQL](https://github.com/graphql/graphiql) - In-browser GraphQL IDE
|
|
658
|
+
- [Apollo Studio](https://studio.apollographql.com) - GraphQL platform
|
|
667
659
|
- [Postman](https://www.postman.com/) - API testing
|
|
668
660
|
|
|
669
661
|
### Learning
|