unreal-engine-mcp-server 0.2.1
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/.dockerignore +57 -0
- package/.env.production +25 -0
- package/.eslintrc.json +54 -0
- package/.github/workflows/publish-mcp.yml +75 -0
- package/Dockerfile +54 -0
- package/LICENSE +21 -0
- package/Public/icon.png +0 -0
- package/README.md +209 -0
- package/claude_desktop_config_example.json +13 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +7 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +484 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +38 -0
- package/dist/python-utils.d.ts +29 -0
- package/dist/python-utils.js +54 -0
- package/dist/resources/actors.d.ts +13 -0
- package/dist/resources/actors.js +83 -0
- package/dist/resources/assets.d.ts +23 -0
- package/dist/resources/assets.js +245 -0
- package/dist/resources/levels.d.ts +17 -0
- package/dist/resources/levels.js +94 -0
- package/dist/tools/actors.d.ts +51 -0
- package/dist/tools/actors.js +459 -0
- package/dist/tools/animation.d.ts +196 -0
- package/dist/tools/animation.js +579 -0
- package/dist/tools/assets.d.ts +21 -0
- package/dist/tools/assets.js +304 -0
- package/dist/tools/audio.d.ts +170 -0
- package/dist/tools/audio.js +416 -0
- package/dist/tools/blueprint.d.ts +144 -0
- package/dist/tools/blueprint.js +652 -0
- package/dist/tools/build_environment_advanced.d.ts +66 -0
- package/dist/tools/build_environment_advanced.js +484 -0
- package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
- package/dist/tools/consolidated-tool-definitions.js +607 -0
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
- package/dist/tools/consolidated-tool-handlers.js +1050 -0
- package/dist/tools/debug.d.ts +185 -0
- package/dist/tools/debug.js +265 -0
- package/dist/tools/editor.d.ts +88 -0
- package/dist/tools/editor.js +365 -0
- package/dist/tools/engine.d.ts +30 -0
- package/dist/tools/engine.js +36 -0
- package/dist/tools/foliage.d.ts +155 -0
- package/dist/tools/foliage.js +525 -0
- package/dist/tools/introspection.d.ts +98 -0
- package/dist/tools/introspection.js +683 -0
- package/dist/tools/landscape.d.ts +158 -0
- package/dist/tools/landscape.js +375 -0
- package/dist/tools/level.d.ts +110 -0
- package/dist/tools/level.js +362 -0
- package/dist/tools/lighting.d.ts +159 -0
- package/dist/tools/lighting.js +1179 -0
- package/dist/tools/materials.d.ts +34 -0
- package/dist/tools/materials.js +146 -0
- package/dist/tools/niagara.d.ts +145 -0
- package/dist/tools/niagara.js +289 -0
- package/dist/tools/performance.d.ts +163 -0
- package/dist/tools/performance.js +412 -0
- package/dist/tools/physics.d.ts +189 -0
- package/dist/tools/physics.js +784 -0
- package/dist/tools/rc.d.ts +110 -0
- package/dist/tools/rc.js +363 -0
- package/dist/tools/sequence.d.ts +112 -0
- package/dist/tools/sequence.js +675 -0
- package/dist/tools/tool-definitions.d.ts +4919 -0
- package/dist/tools/tool-definitions.js +891 -0
- package/dist/tools/tool-handlers.d.ts +47 -0
- package/dist/tools/tool-handlers.js +830 -0
- package/dist/tools/ui.d.ts +171 -0
- package/dist/tools/ui.js +337 -0
- package/dist/tools/visual.d.ts +29 -0
- package/dist/tools/visual.js +67 -0
- package/dist/types/env.d.ts +10 -0
- package/dist/types/env.js +18 -0
- package/dist/types/index.d.ts +323 -0
- package/dist/types/index.js +28 -0
- package/dist/types/tool-types.d.ts +274 -0
- package/dist/types/tool-types.js +13 -0
- package/dist/unreal-bridge.d.ts +126 -0
- package/dist/unreal-bridge.js +992 -0
- package/dist/utils/cache-manager.d.ts +64 -0
- package/dist/utils/cache-manager.js +176 -0
- package/dist/utils/error-handler.d.ts +66 -0
- package/dist/utils/error-handler.js +243 -0
- package/dist/utils/errors.d.ts +133 -0
- package/dist/utils/errors.js +256 -0
- package/dist/utils/http.d.ts +26 -0
- package/dist/utils/http.js +135 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/normalize.d.ts +17 -0
- package/dist/utils/normalize.js +49 -0
- package/dist/utils/response-validator.d.ts +34 -0
- package/dist/utils/response-validator.js +121 -0
- package/dist/utils/safe-json.d.ts +4 -0
- package/dist/utils/safe-json.js +97 -0
- package/dist/utils/stdio-redirect.d.ts +2 -0
- package/dist/utils/stdio-redirect.js +20 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +173 -0
- package/mcp-config-example.json +14 -0
- package/package.json +63 -0
- package/server.json +60 -0
- package/src/cli.ts +7 -0
- package/src/index.ts +543 -0
- package/src/prompts/index.ts +51 -0
- package/src/python/editor_compat.py +181 -0
- package/src/python-utils.ts +57 -0
- package/src/resources/actors.ts +92 -0
- package/src/resources/assets.ts +251 -0
- package/src/resources/levels.ts +83 -0
- package/src/tools/actors.ts +480 -0
- package/src/tools/animation.ts +713 -0
- package/src/tools/assets.ts +305 -0
- package/src/tools/audio.ts +548 -0
- package/src/tools/blueprint.ts +736 -0
- package/src/tools/build_environment_advanced.ts +526 -0
- package/src/tools/consolidated-tool-definitions.ts +619 -0
- package/src/tools/consolidated-tool-handlers.ts +1093 -0
- package/src/tools/debug.ts +368 -0
- package/src/tools/editor.ts +360 -0
- package/src/tools/engine.ts +32 -0
- package/src/tools/foliage.ts +652 -0
- package/src/tools/introspection.ts +778 -0
- package/src/tools/landscape.ts +523 -0
- package/src/tools/level.ts +410 -0
- package/src/tools/lighting.ts +1316 -0
- package/src/tools/materials.ts +148 -0
- package/src/tools/niagara.ts +312 -0
- package/src/tools/performance.ts +549 -0
- package/src/tools/physics.ts +924 -0
- package/src/tools/rc.ts +437 -0
- package/src/tools/sequence.ts +791 -0
- package/src/tools/tool-definitions.ts +907 -0
- package/src/tools/tool-handlers.ts +941 -0
- package/src/tools/ui.ts +499 -0
- package/src/tools/visual.ts +60 -0
- package/src/types/env.ts +27 -0
- package/src/types/index.ts +414 -0
- package/src/types/tool-types.ts +343 -0
- package/src/unreal-bridge.ts +1118 -0
- package/src/utils/cache-manager.ts +213 -0
- package/src/utils/error-handler.ts +320 -0
- package/src/utils/errors.ts +312 -0
- package/src/utils/http.ts +184 -0
- package/src/utils/logger.ts +30 -0
- package/src/utils/normalize.ts +54 -0
- package/src/utils/response-validator.ts +145 -0
- package/src/utils/safe-json.ts +112 -0
- package/src/utils/stdio-redirect.ts +18 -0
- package/src/utils/validation.ts +212 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Manager for API responses
|
|
3
|
+
* Implements LRU cache with TTL support for optimizing repeated API calls
|
|
4
|
+
*/
|
|
5
|
+
interface CacheOptions {
|
|
6
|
+
maxSize?: number;
|
|
7
|
+
defaultTTL?: number;
|
|
8
|
+
enableMetrics?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface CacheMetrics {
|
|
11
|
+
hits: number;
|
|
12
|
+
misses: number;
|
|
13
|
+
evictions: number;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class CacheManager<T = any> {
|
|
17
|
+
private cache;
|
|
18
|
+
private readonly maxSize;
|
|
19
|
+
private readonly defaultTTL;
|
|
20
|
+
private readonly enableMetrics;
|
|
21
|
+
private metrics;
|
|
22
|
+
constructor(options?: CacheOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Get item from cache
|
|
25
|
+
*/
|
|
26
|
+
get(key: string): T | null;
|
|
27
|
+
/**
|
|
28
|
+
* Set item in cache
|
|
29
|
+
*/
|
|
30
|
+
set(key: string, data: T): void;
|
|
31
|
+
/**
|
|
32
|
+
* Check if key exists and is valid
|
|
33
|
+
*/
|
|
34
|
+
has(key: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Clear specific key or all cache
|
|
37
|
+
*/
|
|
38
|
+
clear(key?: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get cache metrics
|
|
41
|
+
*/
|
|
42
|
+
getMetrics(): CacheMetrics;
|
|
43
|
+
/**
|
|
44
|
+
* Get cache hit rate
|
|
45
|
+
*/
|
|
46
|
+
getHitRate(): number;
|
|
47
|
+
/**
|
|
48
|
+
* Wrap async function with cache
|
|
49
|
+
*/
|
|
50
|
+
wrap<R = T>(key: string, fn: () => Promise<R>): Promise<R>;
|
|
51
|
+
/**
|
|
52
|
+
* Batch get multiple keys
|
|
53
|
+
*/
|
|
54
|
+
getBatch(keys: string[]): Map<string, T | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Invalidate cache entries by pattern
|
|
57
|
+
*/
|
|
58
|
+
invalidatePattern(pattern: RegExp): number;
|
|
59
|
+
}
|
|
60
|
+
export declare const assetCache: CacheManager<any>;
|
|
61
|
+
export declare const engineCache: CacheManager<any>;
|
|
62
|
+
export declare const commandCache: CacheManager<any>;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=cache-manager.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Manager for API responses
|
|
3
|
+
* Implements LRU cache with TTL support for optimizing repeated API calls
|
|
4
|
+
*/
|
|
5
|
+
export class CacheManager {
|
|
6
|
+
cache;
|
|
7
|
+
maxSize;
|
|
8
|
+
defaultTTL;
|
|
9
|
+
enableMetrics;
|
|
10
|
+
metrics;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.cache = new Map();
|
|
13
|
+
this.maxSize = options.maxSize || 100;
|
|
14
|
+
this.defaultTTL = options.defaultTTL || 60000; // 1 minute default
|
|
15
|
+
this.enableMetrics = options.enableMetrics || false;
|
|
16
|
+
this.metrics = {
|
|
17
|
+
hits: 0,
|
|
18
|
+
misses: 0,
|
|
19
|
+
evictions: 0,
|
|
20
|
+
size: 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get item from cache
|
|
25
|
+
*/
|
|
26
|
+
get(key) {
|
|
27
|
+
const entry = this.cache.get(key);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
if (this.enableMetrics)
|
|
30
|
+
this.metrics.misses++;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
// Check if expired
|
|
34
|
+
if (Date.now() - entry.timestamp > this.defaultTTL) {
|
|
35
|
+
this.cache.delete(key);
|
|
36
|
+
if (this.enableMetrics) {
|
|
37
|
+
this.metrics.misses++;
|
|
38
|
+
this.metrics.size--;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// Update hit count and move to end (LRU)
|
|
43
|
+
entry.hits++;
|
|
44
|
+
this.cache.delete(key);
|
|
45
|
+
this.cache.set(key, entry);
|
|
46
|
+
if (this.enableMetrics)
|
|
47
|
+
this.metrics.hits++;
|
|
48
|
+
return entry.data;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Set item in cache
|
|
52
|
+
*/
|
|
53
|
+
set(key, data) {
|
|
54
|
+
// Evict oldest if at max size
|
|
55
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
|
|
56
|
+
const firstKey = this.cache.keys().next().value;
|
|
57
|
+
if (firstKey) {
|
|
58
|
+
this.cache.delete(firstKey);
|
|
59
|
+
if (this.enableMetrics) {
|
|
60
|
+
this.metrics.evictions++;
|
|
61
|
+
this.metrics.size--;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
data,
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
hits: 0
|
|
69
|
+
};
|
|
70
|
+
this.cache.set(key, entry);
|
|
71
|
+
if (this.enableMetrics)
|
|
72
|
+
this.metrics.size = this.cache.size;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if key exists and is valid
|
|
76
|
+
*/
|
|
77
|
+
has(key) {
|
|
78
|
+
const entry = this.cache.get(key);
|
|
79
|
+
if (!entry)
|
|
80
|
+
return false;
|
|
81
|
+
// Check expiration
|
|
82
|
+
if (Date.now() - entry.timestamp > this.defaultTTL) {
|
|
83
|
+
this.cache.delete(key);
|
|
84
|
+
if (this.enableMetrics)
|
|
85
|
+
this.metrics.size--;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Clear specific key or all cache
|
|
92
|
+
*/
|
|
93
|
+
clear(key) {
|
|
94
|
+
if (key) {
|
|
95
|
+
this.cache.delete(key);
|
|
96
|
+
if (this.enableMetrics)
|
|
97
|
+
this.metrics.size = this.cache.size;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.cache.clear();
|
|
101
|
+
if (this.enableMetrics) {
|
|
102
|
+
this.metrics.size = 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get cache metrics
|
|
108
|
+
*/
|
|
109
|
+
getMetrics() {
|
|
110
|
+
return { ...this.metrics };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get cache hit rate
|
|
114
|
+
*/
|
|
115
|
+
getHitRate() {
|
|
116
|
+
const total = this.metrics.hits + this.metrics.misses;
|
|
117
|
+
return total > 0 ? this.metrics.hits / total : 0;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Wrap async function with cache
|
|
121
|
+
*/
|
|
122
|
+
async wrap(key, fn) {
|
|
123
|
+
// Check cache first
|
|
124
|
+
const cached = this.get(key);
|
|
125
|
+
if (cached !== null) {
|
|
126
|
+
return cached;
|
|
127
|
+
}
|
|
128
|
+
// Execute function and cache result
|
|
129
|
+
const result = await fn();
|
|
130
|
+
this.set(key, result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Batch get multiple keys
|
|
135
|
+
*/
|
|
136
|
+
getBatch(keys) {
|
|
137
|
+
const results = new Map();
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
results.set(key, this.get(key));
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Invalidate cache entries by pattern
|
|
145
|
+
*/
|
|
146
|
+
invalidatePattern(pattern) {
|
|
147
|
+
let count = 0;
|
|
148
|
+
for (const key of this.cache.keys()) {
|
|
149
|
+
if (pattern.test(key)) {
|
|
150
|
+
this.cache.delete(key);
|
|
151
|
+
count++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (this.enableMetrics) {
|
|
155
|
+
this.metrics.size = this.cache.size;
|
|
156
|
+
}
|
|
157
|
+
return count;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Global cache instances for different purposes
|
|
161
|
+
export const assetCache = new CacheManager({
|
|
162
|
+
maxSize: 500,
|
|
163
|
+
defaultTTL: 300000, // 5 minutes for assets
|
|
164
|
+
enableMetrics: true
|
|
165
|
+
});
|
|
166
|
+
export const engineCache = new CacheManager({
|
|
167
|
+
maxSize: 50,
|
|
168
|
+
defaultTTL: 600000, // 10 minutes for engine info
|
|
169
|
+
enableMetrics: true
|
|
170
|
+
});
|
|
171
|
+
export const commandCache = new CacheManager({
|
|
172
|
+
maxSize: 100,
|
|
173
|
+
defaultTTL: 30000, // 30 seconds for commands
|
|
174
|
+
enableMetrics: true
|
|
175
|
+
});
|
|
176
|
+
//# sourceMappingURL=cache-manager.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BaseToolResponse } from '../types/tool-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error types for categorization
|
|
4
|
+
*/
|
|
5
|
+
export declare enum ErrorType {
|
|
6
|
+
VALIDATION = "VALIDATION",
|
|
7
|
+
CONNECTION = "CONNECTION",
|
|
8
|
+
UNREAL_ENGINE = "UNREAL_ENGINE",
|
|
9
|
+
PARAMETER = "PARAMETER",
|
|
10
|
+
EXECUTION = "EXECUTION",
|
|
11
|
+
TIMEOUT = "TIMEOUT",
|
|
12
|
+
UNKNOWN = "UNKNOWN"
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Custom error class for MCP tools
|
|
16
|
+
*/
|
|
17
|
+
export declare class ToolError extends Error {
|
|
18
|
+
type: ErrorType;
|
|
19
|
+
toolName: string;
|
|
20
|
+
originalError?: any | undefined;
|
|
21
|
+
constructor(type: ErrorType, toolName: string, message: string, originalError?: any | undefined);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Consistent error handling for all tools
|
|
25
|
+
*/
|
|
26
|
+
export declare class ErrorHandler {
|
|
27
|
+
/**
|
|
28
|
+
* Create a standardized error response
|
|
29
|
+
*/
|
|
30
|
+
static createErrorResponse(error: any, toolName: string, context?: any): BaseToolResponse;
|
|
31
|
+
/**
|
|
32
|
+
* Create a standardized warning response
|
|
33
|
+
*/
|
|
34
|
+
static createWarningResponse(message: string, result: any, toolName: string): BaseToolResponse;
|
|
35
|
+
/**
|
|
36
|
+
* Create a standardized success response
|
|
37
|
+
*/
|
|
38
|
+
static createSuccessResponse(message: string, data?: any): BaseToolResponse;
|
|
39
|
+
/**
|
|
40
|
+
* Categorize error by type
|
|
41
|
+
*/
|
|
42
|
+
private static categorizeError;
|
|
43
|
+
/**
|
|
44
|
+
* Get user-friendly error message
|
|
45
|
+
*/
|
|
46
|
+
private static getUserFriendlyMessage;
|
|
47
|
+
/** Determine if an error is likely retriable */
|
|
48
|
+
private static isRetriable;
|
|
49
|
+
/**
|
|
50
|
+
* Wrap async function with error handling
|
|
51
|
+
*/
|
|
52
|
+
static wrapAsync<T extends BaseToolResponse>(toolName: string, fn: () => Promise<T>, context?: any): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Validate required parameters
|
|
55
|
+
*/
|
|
56
|
+
static validateParams(params: any, required: string[], toolName: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Handle Unreal Engine specific errors
|
|
59
|
+
*/
|
|
60
|
+
static handleUnrealError(error: any, operation: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Create operation result with consistent structure
|
|
63
|
+
*/
|
|
64
|
+
static createResult<T extends BaseToolResponse>(success: boolean, message: string, data?: Partial<T>): T;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
const log = new Logger('ErrorHandler');
|
|
3
|
+
/**
|
|
4
|
+
* Error types for categorization
|
|
5
|
+
*/
|
|
6
|
+
export var ErrorType;
|
|
7
|
+
(function (ErrorType) {
|
|
8
|
+
ErrorType["VALIDATION"] = "VALIDATION";
|
|
9
|
+
ErrorType["CONNECTION"] = "CONNECTION";
|
|
10
|
+
ErrorType["UNREAL_ENGINE"] = "UNREAL_ENGINE";
|
|
11
|
+
ErrorType["PARAMETER"] = "PARAMETER";
|
|
12
|
+
ErrorType["EXECUTION"] = "EXECUTION";
|
|
13
|
+
ErrorType["TIMEOUT"] = "TIMEOUT";
|
|
14
|
+
ErrorType["UNKNOWN"] = "UNKNOWN";
|
|
15
|
+
})(ErrorType || (ErrorType = {}));
|
|
16
|
+
/**
|
|
17
|
+
* Custom error class for MCP tools
|
|
18
|
+
*/
|
|
19
|
+
export class ToolError extends Error {
|
|
20
|
+
type;
|
|
21
|
+
toolName;
|
|
22
|
+
originalError;
|
|
23
|
+
constructor(type, toolName, message, originalError) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.type = type;
|
|
26
|
+
this.toolName = toolName;
|
|
27
|
+
this.originalError = originalError;
|
|
28
|
+
this.name = 'ToolError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Consistent error handling for all tools
|
|
33
|
+
*/
|
|
34
|
+
export class ErrorHandler {
|
|
35
|
+
/**
|
|
36
|
+
* Create a standardized error response
|
|
37
|
+
*/
|
|
38
|
+
static createErrorResponse(error, toolName, context) {
|
|
39
|
+
const errorType = this.categorizeError(error);
|
|
40
|
+
const userMessage = this.getUserFriendlyMessage(errorType, error);
|
|
41
|
+
const retriable = this.isRetriable(error);
|
|
42
|
+
const scope = context?.scope || `tool-call/${toolName}`;
|
|
43
|
+
log.error(`Tool ${toolName} failed:`, {
|
|
44
|
+
type: errorType,
|
|
45
|
+
message: error.message || error,
|
|
46
|
+
retriable,
|
|
47
|
+
scope,
|
|
48
|
+
context
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: userMessage,
|
|
53
|
+
message: `Failed to execute ${toolName}: ${userMessage}`,
|
|
54
|
+
retriable: retriable,
|
|
55
|
+
scope: scope,
|
|
56
|
+
// Add debug info in development
|
|
57
|
+
...(process.env.NODE_ENV === 'development' && {
|
|
58
|
+
_debug: {
|
|
59
|
+
errorType,
|
|
60
|
+
originalError: error.message || String(error),
|
|
61
|
+
stack: error.stack,
|
|
62
|
+
context,
|
|
63
|
+
retriable,
|
|
64
|
+
scope
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a standardized warning response
|
|
71
|
+
*/
|
|
72
|
+
static createWarningResponse(message, result, toolName) {
|
|
73
|
+
log.warn(`Tool ${toolName} warning: ${message}`);
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
warning: message,
|
|
77
|
+
message: `${toolName} completed with warnings`,
|
|
78
|
+
...result
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a standardized success response
|
|
83
|
+
*/
|
|
84
|
+
static createSuccessResponse(message, data = {}) {
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
message,
|
|
88
|
+
...data
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Categorize error by type
|
|
93
|
+
*/
|
|
94
|
+
static categorizeError(error) {
|
|
95
|
+
if (error instanceof ToolError) {
|
|
96
|
+
return error.type;
|
|
97
|
+
}
|
|
98
|
+
const errorMessage = error.message?.toLowerCase() || String(error).toLowerCase();
|
|
99
|
+
// Connection errors
|
|
100
|
+
if (errorMessage.includes('econnrefused') ||
|
|
101
|
+
errorMessage.includes('timeout') ||
|
|
102
|
+
errorMessage.includes('connection') ||
|
|
103
|
+
errorMessage.includes('network')) {
|
|
104
|
+
return ErrorType.CONNECTION;
|
|
105
|
+
}
|
|
106
|
+
// Validation errors
|
|
107
|
+
if (errorMessage.includes('invalid') ||
|
|
108
|
+
errorMessage.includes('required') ||
|
|
109
|
+
errorMessage.includes('must be') ||
|
|
110
|
+
errorMessage.includes('validation')) {
|
|
111
|
+
return ErrorType.VALIDATION;
|
|
112
|
+
}
|
|
113
|
+
// Unreal Engine specific errors
|
|
114
|
+
if (errorMessage.includes('unreal') ||
|
|
115
|
+
errorMessage.includes('remote control') ||
|
|
116
|
+
errorMessage.includes('blueprint') ||
|
|
117
|
+
errorMessage.includes('actor') ||
|
|
118
|
+
errorMessage.includes('asset')) {
|
|
119
|
+
return ErrorType.UNREAL_ENGINE;
|
|
120
|
+
}
|
|
121
|
+
// Parameter errors
|
|
122
|
+
if (errorMessage.includes('parameter') ||
|
|
123
|
+
errorMessage.includes('argument') ||
|
|
124
|
+
errorMessage.includes('missing')) {
|
|
125
|
+
return ErrorType.PARAMETER;
|
|
126
|
+
}
|
|
127
|
+
// Timeout errors
|
|
128
|
+
if (errorMessage.includes('timeout')) {
|
|
129
|
+
return ErrorType.TIMEOUT;
|
|
130
|
+
}
|
|
131
|
+
return ErrorType.UNKNOWN;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get user-friendly error message
|
|
135
|
+
*/
|
|
136
|
+
static getUserFriendlyMessage(type, error) {
|
|
137
|
+
const originalMessage = error.message || String(error);
|
|
138
|
+
switch (type) {
|
|
139
|
+
case ErrorType.CONNECTION:
|
|
140
|
+
return 'Failed to connect to Unreal Engine. Please ensure Remote Control is enabled and the engine is running.';
|
|
141
|
+
case ErrorType.VALIDATION:
|
|
142
|
+
return `Invalid input: ${originalMessage}`;
|
|
143
|
+
case ErrorType.UNREAL_ENGINE:
|
|
144
|
+
return `Unreal Engine error: ${originalMessage}`;
|
|
145
|
+
case ErrorType.PARAMETER:
|
|
146
|
+
return `Invalid parameters: ${originalMessage}`;
|
|
147
|
+
case ErrorType.TIMEOUT:
|
|
148
|
+
return 'Operation timed out. Unreal Engine may be busy or unresponsive.';
|
|
149
|
+
case ErrorType.EXECUTION:
|
|
150
|
+
return `Execution failed: ${originalMessage}`;
|
|
151
|
+
default:
|
|
152
|
+
return originalMessage;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Determine if an error is likely retriable */
|
|
156
|
+
static isRetriable(error) {
|
|
157
|
+
try {
|
|
158
|
+
const code = (error?.code || '').toString().toUpperCase();
|
|
159
|
+
const msg = (error?.message || String(error) || '').toLowerCase();
|
|
160
|
+
const status = Number((error?.response?.status));
|
|
161
|
+
if (['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE'].includes(code))
|
|
162
|
+
return true;
|
|
163
|
+
if (/timeout|timed out|network|connection|closed|unavailable|busy|temporar/.test(msg))
|
|
164
|
+
return true;
|
|
165
|
+
if (!isNaN(status) && (status === 429 || (status >= 500 && status < 600)))
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
catch { }
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Wrap async function with error handling
|
|
173
|
+
*/
|
|
174
|
+
static async wrapAsync(toolName, fn, context) {
|
|
175
|
+
try {
|
|
176
|
+
const result = await fn();
|
|
177
|
+
// Ensure result has success field
|
|
178
|
+
if (typeof result === 'object' && result !== null) {
|
|
179
|
+
if (!('success' in result)) {
|
|
180
|
+
result.success = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
return this.createErrorResponse(error, toolName, context);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Validate required parameters
|
|
191
|
+
*/
|
|
192
|
+
static validateParams(params, required, toolName) {
|
|
193
|
+
if (!params || typeof params !== 'object') {
|
|
194
|
+
throw new ToolError(ErrorType.PARAMETER, toolName, 'Invalid parameters: expected object');
|
|
195
|
+
}
|
|
196
|
+
for (const field of required) {
|
|
197
|
+
if (!(field in params) || params[field] === undefined || params[field] === null) {
|
|
198
|
+
throw new ToolError(ErrorType.PARAMETER, toolName, `Missing required parameter: ${field}`);
|
|
199
|
+
}
|
|
200
|
+
// Additional validation for common types
|
|
201
|
+
if (field.includes('Path') || field.includes('Name')) {
|
|
202
|
+
if (typeof params[field] !== 'string' || params[field].trim() === '') {
|
|
203
|
+
throw new ToolError(ErrorType.PARAMETER, toolName, `Invalid ${field}: must be a non-empty string`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Handle Unreal Engine specific errors
|
|
210
|
+
*/
|
|
211
|
+
static handleUnrealError(error, operation) {
|
|
212
|
+
const errorStr = String(error.message || error).toLowerCase();
|
|
213
|
+
// Common Unreal errors
|
|
214
|
+
if (errorStr.includes('worldcontext')) {
|
|
215
|
+
return `${operation} completed (WorldContext warnings are normal)`;
|
|
216
|
+
}
|
|
217
|
+
if (errorStr.includes('does not exist')) {
|
|
218
|
+
return `Asset or object not found for ${operation}`;
|
|
219
|
+
}
|
|
220
|
+
if (errorStr.includes('access denied') || errorStr.includes('read-only')) {
|
|
221
|
+
return `Permission denied for ${operation}. Check file permissions.`;
|
|
222
|
+
}
|
|
223
|
+
if (errorStr.includes('already exists')) {
|
|
224
|
+
return `Object already exists for ${operation}`;
|
|
225
|
+
}
|
|
226
|
+
return `Unreal Engine error during ${operation}: ${error.message || error}`;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Create operation result with consistent structure
|
|
230
|
+
*/
|
|
231
|
+
static createResult(success, message, data) {
|
|
232
|
+
const result = {
|
|
233
|
+
success,
|
|
234
|
+
message,
|
|
235
|
+
...(data || {})
|
|
236
|
+
};
|
|
237
|
+
if (!success && !result.error) {
|
|
238
|
+
result.error = message;
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced error types for better error handling and recovery
|
|
3
|
+
*/
|
|
4
|
+
export declare enum ErrorCode {
|
|
5
|
+
CONNECTION_FAILED = "CONNECTION_FAILED",
|
|
6
|
+
CONNECTION_TIMEOUT = "CONNECTION_TIMEOUT",
|
|
7
|
+
CONNECTION_REFUSED = "CONNECTION_REFUSED",
|
|
8
|
+
API_ERROR = "API_ERROR",
|
|
9
|
+
INVALID_RESPONSE = "INVALID_RESPONSE",
|
|
10
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
11
|
+
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
12
|
+
INVALID_PARAMETERS = "INVALID_PARAMETERS",
|
|
13
|
+
MISSING_REQUIRED_FIELD = "MISSING_REQUIRED_FIELD",
|
|
14
|
+
RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND",
|
|
15
|
+
RESOURCE_LOCKED = "RESOURCE_LOCKED",
|
|
16
|
+
RESOURCE_UNAVAILABLE = "RESOURCE_UNAVAILABLE",
|
|
17
|
+
UNAUTHORIZED = "UNAUTHORIZED",
|
|
18
|
+
FORBIDDEN = "FORBIDDEN",
|
|
19
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
20
|
+
CIRCUIT_BREAKER_OPEN = "CIRCUIT_BREAKER_OPEN",
|
|
21
|
+
SERVICE_UNAVAILABLE = "SERVICE_UNAVAILABLE"
|
|
22
|
+
}
|
|
23
|
+
export interface ErrorMetadata {
|
|
24
|
+
code: ErrorCode;
|
|
25
|
+
statusCode?: number;
|
|
26
|
+
retriable: boolean;
|
|
27
|
+
context?: Record<string, any>;
|
|
28
|
+
timestamp: Date;
|
|
29
|
+
correlationId?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Base application error with metadata
|
|
33
|
+
*/
|
|
34
|
+
export declare class AppError extends Error {
|
|
35
|
+
readonly metadata: ErrorMetadata;
|
|
36
|
+
constructor(message: string, metadata?: Partial<ErrorMetadata>);
|
|
37
|
+
toJSON(): {
|
|
38
|
+
stack: string | undefined;
|
|
39
|
+
code: ErrorCode;
|
|
40
|
+
statusCode?: number;
|
|
41
|
+
retriable: boolean;
|
|
42
|
+
context?: Record<string, any>;
|
|
43
|
+
timestamp: Date;
|
|
44
|
+
correlationId?: string;
|
|
45
|
+
name: string;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Connection-related errors
|
|
51
|
+
*/
|
|
52
|
+
export declare class ConnectionError extends AppError {
|
|
53
|
+
constructor(message: string, metadata?: Partial<ErrorMetadata>);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* API-related errors
|
|
57
|
+
*/
|
|
58
|
+
export declare class ApiError extends AppError {
|
|
59
|
+
constructor(message: string, statusCode: number, metadata?: Partial<ErrorMetadata>);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validation errors
|
|
63
|
+
*/
|
|
64
|
+
export declare class ValidationError extends AppError {
|
|
65
|
+
constructor(message: string, metadata?: Partial<ErrorMetadata>);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resource errors
|
|
69
|
+
*/
|
|
70
|
+
export declare class ResourceError extends AppError {
|
|
71
|
+
constructor(message: string, code: ErrorCode, metadata?: Partial<ErrorMetadata>);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Circuit Breaker implementation for fault tolerance
|
|
75
|
+
*/
|
|
76
|
+
export declare enum CircuitState {
|
|
77
|
+
CLOSED = "CLOSED",
|
|
78
|
+
OPEN = "OPEN",
|
|
79
|
+
HALF_OPEN = "HALF_OPEN"
|
|
80
|
+
}
|
|
81
|
+
interface CircuitBreakerOptions {
|
|
82
|
+
threshold: number;
|
|
83
|
+
timeout: number;
|
|
84
|
+
resetTimeout: number;
|
|
85
|
+
onStateChange?: (oldState: CircuitState, newState: CircuitState) => void;
|
|
86
|
+
}
|
|
87
|
+
export declare class CircuitBreaker {
|
|
88
|
+
private state;
|
|
89
|
+
private failures;
|
|
90
|
+
private successCount;
|
|
91
|
+
private lastFailureTime?;
|
|
92
|
+
private readonly options;
|
|
93
|
+
constructor(options?: Partial<CircuitBreakerOptions>);
|
|
94
|
+
/**
|
|
95
|
+
* Execute function with circuit breaker protection
|
|
96
|
+
*/
|
|
97
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
98
|
+
private onSuccess;
|
|
99
|
+
private onFailure;
|
|
100
|
+
private shouldAttemptReset;
|
|
101
|
+
private transitionTo;
|
|
102
|
+
getState(): CircuitState;
|
|
103
|
+
getMetrics(): {
|
|
104
|
+
state: CircuitState;
|
|
105
|
+
failures: number;
|
|
106
|
+
successCount: number;
|
|
107
|
+
lastFailureTime: Date | undefined;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Error recovery strategies
|
|
112
|
+
*/
|
|
113
|
+
export declare class ErrorRecovery {
|
|
114
|
+
private static circuitBreakers;
|
|
115
|
+
/**
|
|
116
|
+
* Get or create circuit breaker for a service
|
|
117
|
+
*/
|
|
118
|
+
static getCircuitBreaker(service: string, options?: Partial<CircuitBreakerOptions>): CircuitBreaker;
|
|
119
|
+
/**
|
|
120
|
+
* Wrap function with error recovery
|
|
121
|
+
*/
|
|
122
|
+
static withRecovery<T>(fn: () => Promise<T>, options: {
|
|
123
|
+
service: string;
|
|
124
|
+
fallback?: () => T | Promise<T>;
|
|
125
|
+
onError?: (error: Error) => void;
|
|
126
|
+
}): Promise<T>;
|
|
127
|
+
/**
|
|
128
|
+
* Check if error is retriable
|
|
129
|
+
*/
|
|
130
|
+
static isRetriable(error: Error): boolean;
|
|
131
|
+
}
|
|
132
|
+
export {};
|
|
133
|
+
//# sourceMappingURL=errors.d.ts.map
|