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.
Files changed (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
@@ -0,0 +1,145 @@
1
+ import Ajv from 'ajv';
2
+ import { Logger } from './logger.js';
3
+ import { cleanObject } from './safe-json.js';
4
+
5
+ const log = new Logger('ResponseValidator');
6
+
7
+ /**
8
+ * Response Validator for MCP Tool Outputs
9
+ * Validates tool responses against their defined output schemas
10
+ */
11
+ export class ResponseValidator {
12
+ private ajv: Ajv;
13
+ private validators: Map<string, any> = new Map();
14
+
15
+ constructor() {
16
+ this.ajv = new Ajv({
17
+ allErrors: true,
18
+ verbose: true,
19
+ strict: false // Allow additional properties for flexibility
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Register a tool's output schema for validation
25
+ */
26
+ registerSchema(toolName: string, outputSchema: any) {
27
+ if (!outputSchema) {
28
+ log.warn(`No output schema defined for tool: ${toolName}`);
29
+ return;
30
+ }
31
+
32
+ try {
33
+ const validator = this.ajv.compile(outputSchema);
34
+ this.validators.set(toolName, validator);
35
+ log.info(`Registered output schema for tool: ${toolName}`);
36
+ } catch (error) {
37
+ log.error(`Failed to compile output schema for ${toolName}:`, error);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Validate a tool's response against its schema
43
+ */
44
+ validateResponse(toolName: string, response: any): {
45
+ valid: boolean;
46
+ errors?: string[];
47
+ structuredContent?: any;
48
+ } {
49
+ const validator = this.validators.get(toolName);
50
+
51
+ if (!validator) {
52
+ log.debug(`No validator found for tool: ${toolName}`);
53
+ return { valid: true }; // Pass through if no schema defined
54
+ }
55
+
56
+ // Extract structured content from response
57
+ let structuredContent = response;
58
+
59
+ // If response has MCP format with content array
60
+ if (response.content && Array.isArray(response.content)) {
61
+ // Try to extract structured data from text content
62
+ const textContent = response.content.find((c: any) => c.type === 'text');
63
+ if (textContent?.text) {
64
+ try {
65
+ // Check if text is JSON
66
+ structuredContent = JSON.parse(textContent.text);
67
+ } catch {
68
+ // Not JSON, use the full response
69
+ structuredContent = response;
70
+ }
71
+ }
72
+ }
73
+
74
+ const valid = validator(structuredContent);
75
+
76
+ if (!valid) {
77
+ const errors = validator.errors?.map((err: any) =>
78
+ `${err.instancePath || 'root'}: ${err.message}`
79
+ );
80
+
81
+ log.warn(`Response validation failed for ${toolName}:`, errors);
82
+
83
+ return {
84
+ valid: false,
85
+ errors,
86
+ structuredContent
87
+ };
88
+ }
89
+
90
+ return {
91
+ valid: true,
92
+ structuredContent
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Wrap a tool response with validation
98
+ */
99
+ wrapResponse(toolName: string, response: any): any {
100
+ // Ensure response is safe to serialize first
101
+ try {
102
+ // The response should already be cleaned, but double-check
103
+ if (response && typeof response === 'object') {
104
+ // Make sure we can serialize it
105
+ JSON.stringify(response);
106
+ }
107
+ } catch (error) {
108
+ log.error(`Response for ${toolName} contains circular references, cleaning...`);
109
+ response = cleanObject(response);
110
+ }
111
+
112
+ const validation = this.validateResponse(toolName, response);
113
+
114
+ // Add validation metadata
115
+ if (!validation.valid) {
116
+ log.warn(`Tool ${toolName} response validation failed:`, validation.errors);
117
+
118
+ // Add warning to response but don't fail
119
+ if (response && typeof response === 'object') {
120
+ response._validation = {
121
+ valid: false,
122
+ errors: validation.errors
123
+ };
124
+ }
125
+ }
126
+
127
+ // Don't add structuredContent to the response - it's for internal validation only
128
+ // Adding it can cause circular references
129
+
130
+ return response;
131
+ }
132
+
133
+ /**
134
+ * Get validation statistics
135
+ */
136
+ getStats() {
137
+ return {
138
+ totalSchemas: this.validators.size,
139
+ tools: Array.from(this.validators.keys())
140
+ };
141
+ }
142
+ }
143
+
144
+ // Singleton instance
145
+ export const responseValidator = new ResponseValidator();
@@ -0,0 +1,112 @@
1
+ // Utility to safely serialize objects with circular references
2
+ export function safeStringify(obj: any, space?: number): string {
3
+ const seen = new WeakSet();
4
+
5
+ return JSON.stringify(obj, (key, value) => {
6
+ // Handle undefined, functions, symbols
7
+ if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
8
+ return undefined;
9
+ }
10
+
11
+ // Handle circular references
12
+ if (typeof value === 'object' && value !== null) {
13
+ if (seen.has(value)) {
14
+ return '[Circular Reference]';
15
+ }
16
+ seen.add(value);
17
+ }
18
+
19
+ // Handle BigInt
20
+ if (typeof value === 'bigint') {
21
+ return value.toString();
22
+ }
23
+
24
+ return value;
25
+ }, space);
26
+ }
27
+
28
+ export function sanitizeResponse(response: any): any {
29
+ if (!response || typeof response !== 'object') {
30
+ return response;
31
+ }
32
+
33
+ // Create a clean copy without circular references
34
+ try {
35
+ const jsonStr = safeStringify(response);
36
+ return JSON.parse(jsonStr);
37
+ } catch (error) {
38
+ console.error('Failed to sanitize response:', error);
39
+
40
+ // Fallback: return a simple error response
41
+ return {
42
+ content: [{
43
+ type: 'text',
44
+ text: 'Response contained unserializable data'
45
+ }],
46
+ error: 'Serialization error'
47
+ };
48
+ }
49
+ }
50
+
51
+ // Remove circular references and non-serializable properties from an object
52
+ export function cleanObject(obj: any, maxDepth: number = 10): any {
53
+ const seen = new WeakSet();
54
+
55
+ function clean(value: any, depth: number, path: string = 'root'): any {
56
+ // Prevent infinite recursion
57
+ if (depth > maxDepth) {
58
+ return '[Max depth reached]';
59
+ }
60
+
61
+ // Handle primitives
62
+ if (value === null || value === undefined) {
63
+ return value;
64
+ }
65
+
66
+ if (typeof value !== 'object') {
67
+ if (typeof value === 'function' || typeof value === 'symbol') {
68
+ return undefined;
69
+ }
70
+ if (typeof value === 'bigint') {
71
+ return value.toString();
72
+ }
73
+ return value;
74
+ }
75
+
76
+ // Check for circular reference
77
+ if (seen.has(value)) {
78
+ return '[Circular Reference]';
79
+ }
80
+
81
+ seen.add(value);
82
+
83
+ // Handle arrays
84
+ if (Array.isArray(value)) {
85
+ const result = value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
86
+ seen.delete(value); // Remove from seen after processing
87
+ return result;
88
+ }
89
+
90
+ // Handle objects
91
+ const cleaned: any = {};
92
+
93
+ // Use Object.keys to avoid prototype properties
94
+ const keys = Object.keys(value);
95
+ for (const key of keys) {
96
+ try {
97
+ const cleanedValue = clean(value[key], depth + 1, `${path}.${key}`);
98
+ if (cleanedValue !== undefined) {
99
+ cleaned[key] = cleanedValue;
100
+ }
101
+ } catch (e) {
102
+ // Skip properties that throw errors when accessed
103
+ console.error(`Error cleaning property ${path}.${key}:`, e);
104
+ }
105
+ }
106
+
107
+ seen.delete(value); // Remove from seen after processing
108
+ return cleaned;
109
+ }
110
+
111
+ return clean(obj, 0);
112
+ }
@@ -0,0 +1,18 @@
1
+ export function routeStdoutLogsToStderr(): void {
2
+ // Enable by default. Allow opt-out with LOG_TO_STDERR=false or JSON_STDOUT_MODE=false
3
+ const flagRaw = String(process.env.LOG_TO_STDERR ?? process.env.JSON_STDOUT_MODE ?? 'true').toLowerCase();
4
+ const enabled = !(flagRaw === 'false' || flagRaw === '0' || flagRaw === 'off' || flagRaw === 'no');
5
+ if (!enabled) return;
6
+
7
+ try {
8
+ const toErr = console.error.bind(console) as (...args: any[]) => void;
9
+ // Route common stdout channels to stderr to keep stdout JSON-only for MCP
10
+ console.log = toErr as any;
11
+ console.info = toErr as any;
12
+ console.debug = toErr as any;
13
+ // Be explicit with trace as well
14
+ console.trace = toErr as any;
15
+ } catch {
16
+ // If overriding fails for any reason, just continue silently.
17
+ }
18
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Validation and sanitization utilities for Unreal Engine assets
3
+ */
4
+
5
+ /**
6
+ * Maximum path length allowed in Unreal Engine
7
+ */
8
+ const MAX_PATH_LENGTH = 260;
9
+
10
+ /**
11
+ * Invalid characters for Unreal Engine asset names
12
+ * Note: Dashes are allowed in Unreal asset names
13
+ */
14
+ // eslint-disable-next-line no-useless-escape
15
+ const INVALID_CHARS = /[@#%$&*()+=\[\]{}<>?|\\;:'"`,~!\s]/g;
16
+
17
+ /**
18
+ * Reserved keywords that shouldn't be used as names
19
+ */
20
+ const RESERVED_KEYWORDS = [
21
+ 'None', 'null', 'undefined', 'true', 'false',
22
+ 'class', 'struct', 'enum', 'interface',
23
+ 'default', 'transient', 'native'
24
+ ];
25
+
26
+ /**
27
+ * Sanitize an asset name for Unreal Engine
28
+ * @param name The name to sanitize
29
+ * @returns Sanitized name
30
+ */
31
+ export function sanitizeAssetName(name: string): string {
32
+ if (!name || typeof name !== 'string') {
33
+ return 'Asset';
34
+ }
35
+
36
+ // Remove leading/trailing whitespace
37
+ let sanitized = name.trim();
38
+
39
+ // Replace invalid characters with underscores
40
+ sanitized = sanitized.replace(INVALID_CHARS, '_');
41
+
42
+ // Remove consecutive underscores
43
+ sanitized = sanitized.replace(/_+/g, '_');
44
+
45
+ // Remove leading/trailing underscores
46
+ sanitized = sanitized.replace(/^_+|_+$/g, '');
47
+
48
+ // If name is empty after sanitization, use default
49
+ if (!sanitized) {
50
+ return 'Asset';
51
+ }
52
+
53
+ // If name is a reserved keyword, append underscore
54
+ if (RESERVED_KEYWORDS.includes(sanitized)) {
55
+ sanitized = `${sanitized}_Asset`;
56
+ }
57
+
58
+ // Ensure name starts with a letter
59
+ if (!/^[A-Za-z]/.test(sanitized)) {
60
+ sanitized = `Asset_${sanitized}`;
61
+ }
62
+
63
+ // Truncate overly long names to reduce risk of hitting path length limits
64
+ if (sanitized.length > 64) {
65
+ sanitized = sanitized.slice(0, 64);
66
+ }
67
+
68
+ return sanitized;
69
+ }
70
+
71
+ /**
72
+ * Sanitize a path for Unreal Engine
73
+ * @param path The path to sanitize
74
+ * @returns Sanitized path
75
+ */
76
+ export function sanitizePath(path: string): string {
77
+ if (!path || typeof path !== 'string') {
78
+ return '/Game';
79
+ }
80
+
81
+ // Ensure path starts with /
82
+ if (!path.startsWith('/')) {
83
+ path = `/${path}`;
84
+ }
85
+
86
+ // Split path into segments and sanitize each
87
+ const segments = path.split('/').filter(s => s.length > 0);
88
+ const sanitizedSegments = segments.map(segment => {
89
+ // Don't sanitize Game, Engine, or other root folders
90
+ if (['Game', 'Engine', 'Script', 'Temp'].includes(segment)) {
91
+ return segment;
92
+ }
93
+ return sanitizeAssetName(segment);
94
+ });
95
+
96
+ // Reconstruct path
97
+ const sanitizedPath = '/' + sanitizedSegments.join('/');
98
+
99
+ return sanitizedPath;
100
+ }
101
+
102
+ /**
103
+ * Validate path length
104
+ * @param path The full path to validate
105
+ * @returns Object with validation result
106
+ */
107
+ export function validatePathLength(path: string): { valid: boolean; error?: string } {
108
+ if (path.length > MAX_PATH_LENGTH) {
109
+ return {
110
+ valid: false,
111
+ error: `Path too long (${path.length} characters). Maximum allowed is ${MAX_PATH_LENGTH} characters.`
112
+ };
113
+ }
114
+ return { valid: true };
115
+ }
116
+
117
+ /**
118
+ * Validate and sanitize asset parameters
119
+ * @param params Object containing name and optionally savePath
120
+ * @returns Sanitized parameters with validation result
121
+ */
122
+ export function validateAssetParams(params: {
123
+ name: string;
124
+ savePath?: string;
125
+ [key: string]: any;
126
+ }): {
127
+ valid: boolean;
128
+ sanitized: typeof params;
129
+ error?: string;
130
+ } {
131
+ // Sanitize name
132
+ const sanitizedName = sanitizeAssetName(params.name);
133
+
134
+ // Sanitize path if provided
135
+ const sanitizedPath = params.savePath
136
+ ? sanitizePath(params.savePath)
137
+ : params.savePath;
138
+
139
+ // Construct full path for validation
140
+ const fullPath = sanitizedPath
141
+ ? `${sanitizedPath}/${sanitizedName}`
142
+ : `/Game/${sanitizedName}`;
143
+
144
+ // Validate path length
145
+ const pathValidation = validatePathLength(fullPath);
146
+
147
+ if (!pathValidation.valid) {
148
+ return {
149
+ valid: false,
150
+ sanitized: params,
151
+ error: pathValidation.error
152
+ };
153
+ }
154
+
155
+ return {
156
+ valid: true,
157
+ sanitized: {
158
+ ...params,
159
+ name: sanitizedName,
160
+ ...(sanitizedPath && { savePath: sanitizedPath })
161
+ }
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Extract valid skeletal mesh path from various inputs
167
+ * @param input The input path which might be a skeleton or mesh
168
+ * @returns Corrected skeletal mesh path or null
169
+ */
170
+ export function resolveSkeletalMeshPath(input: string): string | null {
171
+ if (!input || typeof input !== 'string') {
172
+ return null;
173
+ }
174
+
175
+ // Common skeleton to mesh mappings
176
+ const skeletonToMeshMap: { [key: string]: string } = {
177
+ '/Game/Mannequin/Character/Mesh/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Manny',
178
+ '/Game/Characters/Mannequins/Meshes/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny',
179
+ '/Game/Mannequin/Character/Mesh/SK_Mannequin': '/Game/Characters/Mannequins/Meshes/SKM_Manny',
180
+ '/Game/Characters/Mannequin_UE4/Meshes/UE4_Mannequin_Skeleton': '/Game/Characters/Mannequins/Meshes/SKM_Quinn',
181
+ };
182
+
183
+ // Check if this is a known skeleton path
184
+ if (skeletonToMeshMap[input]) {
185
+ return skeletonToMeshMap[input];
186
+ }
187
+
188
+ // If it contains _Skeleton, try to convert to mesh name
189
+ if (input.includes('_Skeleton')) {
190
+ // Try common replacements
191
+ let meshPath = input.replace('_Skeleton', '');
192
+ meshPath = meshPath.replace('/SK_', '/SKM_');
193
+ meshPath = meshPath.replace('UE4_Mannequin', 'SKM_Manny');
194
+ return meshPath;
195
+ }
196
+
197
+ // If it starts with SK_ (skeleton prefix), try SKM_ (skeletal mesh prefix)
198
+ if (input.includes('/SK_')) {
199
+ return input.replace('/SK_', '/SKM_');
200
+ }
201
+
202
+ // Return as-is if no conversion needed
203
+ return input;
204
+ }
205
+
206
+ /**
207
+ * Concurrency delay to prevent race conditions
208
+ * @param ms Milliseconds to delay
209
+ */
210
+ export async function concurrencyDelay(ms: number = 100): Promise<void> {
211
+ return new Promise(resolve => setTimeout(resolve, ms));
212
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "Bundler",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "skipLibCheck": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true,
16
+ "incremental": true,
17
+ "tsBuildInfoFile": ".tsbuildinfo",
18
+ "noUnusedLocals": false,
19
+ "noUnusedParameters": false,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "allowUnreachableCode": false,
23
+ "types": ["node"]
24
+ },
25
+ "include": ["src/**/*"],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist",
29
+ "**/*.test.ts",
30
+ "**/*.spec.ts",
31
+ "src/tests/**/*"
32
+ ]
33
+ }