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,1050 @@
|
|
|
1
|
+
// Consolidated tool handlers - maps 10 tools to all 36 operations
|
|
2
|
+
import { handleToolCall } from './tool-handlers.js';
|
|
3
|
+
import { cleanObject } from '../utils/safe-json.js';
|
|
4
|
+
export async function handleConsolidatedToolCall(name, args, tools) {
|
|
5
|
+
const startTime = Date.now();
|
|
6
|
+
console.log(`[ConsolidatedToolHandler] Starting execution of ${name} at ${new Date().toISOString()}`);
|
|
7
|
+
try {
|
|
8
|
+
// Validate args is not null/undefined
|
|
9
|
+
if (args === null || args === undefined) {
|
|
10
|
+
throw new Error('Invalid arguments: null or undefined');
|
|
11
|
+
}
|
|
12
|
+
let mappedName;
|
|
13
|
+
let mappedArgs = { ...args };
|
|
14
|
+
switch (name) {
|
|
15
|
+
// 1. ASSET MANAGER
|
|
16
|
+
case 'manage_asset':
|
|
17
|
+
// Validate args is not null/undefined
|
|
18
|
+
if (args === null || args === undefined) {
|
|
19
|
+
throw new Error('Invalid arguments: null or undefined');
|
|
20
|
+
}
|
|
21
|
+
// Validate action exists
|
|
22
|
+
if (!args.action) {
|
|
23
|
+
throw new Error('Missing required parameter: action');
|
|
24
|
+
}
|
|
25
|
+
switch (args.action) {
|
|
26
|
+
case 'list':
|
|
27
|
+
// Directory is optional
|
|
28
|
+
if (args.directory !== undefined && args.directory !== null) {
|
|
29
|
+
if (typeof args.directory !== 'string') {
|
|
30
|
+
throw new Error('Invalid directory: must be a string');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
mappedName = 'list_assets';
|
|
34
|
+
mappedArgs = {
|
|
35
|
+
directory: args.directory
|
|
36
|
+
// recursive removed - always false internally
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
case 'import':
|
|
40
|
+
// Validate required parameters
|
|
41
|
+
if (args.sourcePath === undefined || args.sourcePath === null) {
|
|
42
|
+
throw new Error('Missing required parameter: sourcePath');
|
|
43
|
+
}
|
|
44
|
+
if (typeof args.sourcePath !== 'string') {
|
|
45
|
+
throw new Error('Invalid sourcePath: must be a string');
|
|
46
|
+
}
|
|
47
|
+
if (args.sourcePath.trim() === '') {
|
|
48
|
+
throw new Error('Invalid sourcePath: cannot be empty');
|
|
49
|
+
}
|
|
50
|
+
if (args.destinationPath === undefined || args.destinationPath === null) {
|
|
51
|
+
throw new Error('Missing required parameter: destinationPath');
|
|
52
|
+
}
|
|
53
|
+
if (typeof args.destinationPath !== 'string') {
|
|
54
|
+
throw new Error('Invalid destinationPath: must be a string');
|
|
55
|
+
}
|
|
56
|
+
if (args.destinationPath.trim() === '') {
|
|
57
|
+
throw new Error('Invalid destinationPath: cannot be empty');
|
|
58
|
+
}
|
|
59
|
+
mappedName = 'import_asset';
|
|
60
|
+
mappedArgs = {
|
|
61
|
+
sourcePath: args.sourcePath,
|
|
62
|
+
destinationPath: args.destinationPath
|
|
63
|
+
};
|
|
64
|
+
break;
|
|
65
|
+
case 'create_material':
|
|
66
|
+
// Validate required parameters
|
|
67
|
+
if (args.name === undefined || args.name === null) {
|
|
68
|
+
throw new Error('Missing required parameter: name');
|
|
69
|
+
}
|
|
70
|
+
if (typeof args.name !== 'string') {
|
|
71
|
+
throw new Error('Invalid name: must be a string');
|
|
72
|
+
}
|
|
73
|
+
if (args.name.trim() === '') {
|
|
74
|
+
throw new Error('Invalid name: cannot be empty');
|
|
75
|
+
}
|
|
76
|
+
// Path is optional
|
|
77
|
+
if (args.path !== undefined && args.path !== null) {
|
|
78
|
+
if (typeof args.path !== 'string') {
|
|
79
|
+
throw new Error('Invalid path: must be a string');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
mappedName = 'create_material';
|
|
83
|
+
mappedArgs = {
|
|
84
|
+
name: args.name,
|
|
85
|
+
path: args.path
|
|
86
|
+
};
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
throw new Error(`Unknown asset action: ${args.action}`);
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
// 2. ACTOR CONTROL
|
|
93
|
+
case 'control_actor':
|
|
94
|
+
// Validate action exists
|
|
95
|
+
if (!args.action) {
|
|
96
|
+
throw new Error('Missing required parameter: action');
|
|
97
|
+
}
|
|
98
|
+
switch (args.action) {
|
|
99
|
+
case 'spawn':
|
|
100
|
+
// Validate spawn parameters
|
|
101
|
+
if (!args.classPath) {
|
|
102
|
+
throw new Error('Missing required parameter: classPath');
|
|
103
|
+
}
|
|
104
|
+
if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
|
|
105
|
+
throw new Error('Invalid classPath: must be a non-empty string');
|
|
106
|
+
}
|
|
107
|
+
mappedName = 'spawn_actor';
|
|
108
|
+
mappedArgs = {
|
|
109
|
+
classPath: args.classPath,
|
|
110
|
+
location: args.location,
|
|
111
|
+
rotation: args.rotation
|
|
112
|
+
};
|
|
113
|
+
break;
|
|
114
|
+
case 'delete':
|
|
115
|
+
// Validate delete parameters
|
|
116
|
+
if (!args.actorName) {
|
|
117
|
+
throw new Error('Missing required parameter: actorName');
|
|
118
|
+
}
|
|
119
|
+
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
120
|
+
throw new Error('Invalid actorName: must be a non-empty string');
|
|
121
|
+
}
|
|
122
|
+
mappedName = 'delete_actor';
|
|
123
|
+
mappedArgs = {
|
|
124
|
+
actorName: args.actorName
|
|
125
|
+
};
|
|
126
|
+
break;
|
|
127
|
+
case 'apply_force':
|
|
128
|
+
// Validate apply_force parameters
|
|
129
|
+
if (!args.actorName) {
|
|
130
|
+
throw new Error('Missing required parameter: actorName');
|
|
131
|
+
}
|
|
132
|
+
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
133
|
+
throw new Error('Invalid actorName: must be a non-empty string');
|
|
134
|
+
}
|
|
135
|
+
if (!args.force) {
|
|
136
|
+
throw new Error('Missing required parameter: force');
|
|
137
|
+
}
|
|
138
|
+
if (typeof args.force !== 'object' || args.force === null) {
|
|
139
|
+
throw new Error('Invalid force: must be an object with x, y, z properties');
|
|
140
|
+
}
|
|
141
|
+
if (typeof args.force.x !== 'number' ||
|
|
142
|
+
typeof args.force.y !== 'number' ||
|
|
143
|
+
typeof args.force.z !== 'number') {
|
|
144
|
+
throw new Error('Invalid force: x, y, z must all be numbers');
|
|
145
|
+
}
|
|
146
|
+
mappedName = 'apply_force';
|
|
147
|
+
mappedArgs = {
|
|
148
|
+
actorName: args.actorName,
|
|
149
|
+
force: args.force
|
|
150
|
+
};
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
throw new Error(`Unknown actor action: ${args.action}`);
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
// 3. EDITOR CONTROL
|
|
157
|
+
case 'control_editor':
|
|
158
|
+
// Validate action exists
|
|
159
|
+
if (!args.action) {
|
|
160
|
+
throw new Error('Missing required parameter: action');
|
|
161
|
+
}
|
|
162
|
+
switch (args.action) {
|
|
163
|
+
case 'play':
|
|
164
|
+
mappedName = 'play_in_editor';
|
|
165
|
+
mappedArgs = {};
|
|
166
|
+
break;
|
|
167
|
+
case 'stop':
|
|
168
|
+
mappedName = 'stop_play_in_editor';
|
|
169
|
+
mappedArgs = {};
|
|
170
|
+
break;
|
|
171
|
+
case 'pause':
|
|
172
|
+
mappedName = 'pause_play_in_editor';
|
|
173
|
+
mappedArgs = {};
|
|
174
|
+
break;
|
|
175
|
+
case 'set_game_speed':
|
|
176
|
+
// Validate game speed parameter
|
|
177
|
+
if (args.speed === undefined || args.speed === null) {
|
|
178
|
+
throw new Error('Missing required parameter: speed');
|
|
179
|
+
}
|
|
180
|
+
if (typeof args.speed !== 'number') {
|
|
181
|
+
throw new Error('Invalid speed: must be a number');
|
|
182
|
+
}
|
|
183
|
+
if (isNaN(args.speed)) {
|
|
184
|
+
throw new Error('Invalid speed: cannot be NaN');
|
|
185
|
+
}
|
|
186
|
+
if (!isFinite(args.speed)) {
|
|
187
|
+
throw new Error('Invalid speed: must be finite');
|
|
188
|
+
}
|
|
189
|
+
if (args.speed <= 0) {
|
|
190
|
+
throw new Error('Invalid speed: must be positive');
|
|
191
|
+
}
|
|
192
|
+
mappedName = 'set_game_speed';
|
|
193
|
+
mappedArgs = {
|
|
194
|
+
speed: args.speed
|
|
195
|
+
};
|
|
196
|
+
break;
|
|
197
|
+
case 'eject':
|
|
198
|
+
mappedName = 'eject_from_pawn';
|
|
199
|
+
mappedArgs = {};
|
|
200
|
+
break;
|
|
201
|
+
case 'possess':
|
|
202
|
+
mappedName = 'possess_pawn';
|
|
203
|
+
mappedArgs = {};
|
|
204
|
+
break;
|
|
205
|
+
case 'set_camera':
|
|
206
|
+
// Allow either location or rotation or both
|
|
207
|
+
// Don't require both to be present
|
|
208
|
+
mappedName = 'set_camera';
|
|
209
|
+
mappedArgs = {
|
|
210
|
+
location: args.location,
|
|
211
|
+
rotation: args.rotation
|
|
212
|
+
};
|
|
213
|
+
break;
|
|
214
|
+
case 'set_view_mode':
|
|
215
|
+
// Validate view mode parameter
|
|
216
|
+
if (!args.viewMode) {
|
|
217
|
+
throw new Error('Missing required parameter: viewMode');
|
|
218
|
+
}
|
|
219
|
+
if (typeof args.viewMode !== 'string' || args.viewMode.trim() === '') {
|
|
220
|
+
throw new Error('Invalid viewMode: must be a non-empty string');
|
|
221
|
+
}
|
|
222
|
+
// Normalize view mode to match what debug.ts expects
|
|
223
|
+
const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
|
|
224
|
+
'light_complexity', 'shader_complexity', 'lightmap_density',
|
|
225
|
+
'stationary_light_overlap', 'reflections', 'visualize_buffer',
|
|
226
|
+
'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
|
|
227
|
+
const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
|
|
228
|
+
// Map to proper case for debug.ts
|
|
229
|
+
let mappedMode = '';
|
|
230
|
+
switch (normalizedMode) {
|
|
231
|
+
case 'lit':
|
|
232
|
+
mappedMode = 'Lit';
|
|
233
|
+
break;
|
|
234
|
+
case 'unlit':
|
|
235
|
+
mappedMode = 'Unlit';
|
|
236
|
+
break;
|
|
237
|
+
case 'wireframe':
|
|
238
|
+
mappedMode = 'Wireframe';
|
|
239
|
+
break;
|
|
240
|
+
case 'detaillighting':
|
|
241
|
+
mappedMode = 'DetailLighting';
|
|
242
|
+
break;
|
|
243
|
+
case 'lightingonly':
|
|
244
|
+
mappedMode = 'LightingOnly';
|
|
245
|
+
break;
|
|
246
|
+
case 'lightcomplexity':
|
|
247
|
+
mappedMode = 'LightComplexity';
|
|
248
|
+
break;
|
|
249
|
+
case 'shadercomplexity':
|
|
250
|
+
mappedMode = 'ShaderComplexity';
|
|
251
|
+
break;
|
|
252
|
+
case 'lightmapdensity':
|
|
253
|
+
mappedMode = 'LightmapDensity';
|
|
254
|
+
break;
|
|
255
|
+
case 'stationarylightoverlap':
|
|
256
|
+
mappedMode = 'StationaryLightOverlap';
|
|
257
|
+
break;
|
|
258
|
+
case 'reflections':
|
|
259
|
+
mappedMode = 'ReflectionOverride';
|
|
260
|
+
break;
|
|
261
|
+
case 'visualizebuffer':
|
|
262
|
+
mappedMode = 'VisualizeBuffer';
|
|
263
|
+
break;
|
|
264
|
+
case 'collisionpawn':
|
|
265
|
+
mappedMode = 'CollisionPawn';
|
|
266
|
+
break;
|
|
267
|
+
case 'collisionvisibility':
|
|
268
|
+
mappedMode = 'CollisionVisibility';
|
|
269
|
+
break;
|
|
270
|
+
case 'lodcoloration':
|
|
271
|
+
mappedMode = 'LODColoration';
|
|
272
|
+
break;
|
|
273
|
+
case 'quadoverdraw':
|
|
274
|
+
mappedMode = 'QuadOverdraw';
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
|
|
278
|
+
}
|
|
279
|
+
mappedName = 'set_view_mode';
|
|
280
|
+
mappedArgs = {
|
|
281
|
+
mode: mappedMode
|
|
282
|
+
};
|
|
283
|
+
break;
|
|
284
|
+
default:
|
|
285
|
+
throw new Error(`Unknown editor action: ${args.action}`);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
// 4. LEVEL MANAGER
|
|
289
|
+
case 'manage_level':
|
|
290
|
+
switch (args.action) {
|
|
291
|
+
case 'load':
|
|
292
|
+
mappedName = 'load_level';
|
|
293
|
+
mappedArgs = {
|
|
294
|
+
levelPath: args.levelPath
|
|
295
|
+
};
|
|
296
|
+
if (args.streaming !== undefined) {
|
|
297
|
+
mappedArgs.streaming = args.streaming;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
case 'save':
|
|
301
|
+
mappedName = 'save_level';
|
|
302
|
+
mappedArgs = {
|
|
303
|
+
levelName: args.levelName
|
|
304
|
+
};
|
|
305
|
+
if (args.savePath !== undefined) {
|
|
306
|
+
mappedArgs.savePath = args.savePath;
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
case 'stream':
|
|
310
|
+
mappedName = 'stream_level';
|
|
311
|
+
mappedArgs = {
|
|
312
|
+
levelName: args.levelName,
|
|
313
|
+
shouldBeLoaded: args.shouldBeLoaded,
|
|
314
|
+
shouldBeVisible: args.shouldBeVisible
|
|
315
|
+
};
|
|
316
|
+
break;
|
|
317
|
+
case 'create_light':
|
|
318
|
+
// Validate light type
|
|
319
|
+
if (!args.lightType) {
|
|
320
|
+
throw new Error('Missing required parameter: lightType');
|
|
321
|
+
}
|
|
322
|
+
const validLightTypes = ['directional', 'point', 'spot', 'rect', 'sky'];
|
|
323
|
+
const normalizedLightType = String(args.lightType).toLowerCase();
|
|
324
|
+
if (!validLightTypes.includes(normalizedLightType)) {
|
|
325
|
+
throw new Error(`Invalid lightType: '${args.lightType}'. Valid types are: ${validLightTypes.join(', ')}`);
|
|
326
|
+
}
|
|
327
|
+
// Validate name
|
|
328
|
+
if (!args.name) {
|
|
329
|
+
throw new Error('Missing required parameter: name');
|
|
330
|
+
}
|
|
331
|
+
if (typeof args.name !== 'string' || args.name.trim() === '') {
|
|
332
|
+
throw new Error('Invalid name: must be a non-empty string');
|
|
333
|
+
}
|
|
334
|
+
// Validate intensity if provided
|
|
335
|
+
if (args.intensity !== undefined) {
|
|
336
|
+
if (typeof args.intensity !== 'number' || !isFinite(args.intensity)) {
|
|
337
|
+
throw new Error(`Invalid intensity: must be a finite number, got ${typeof args.intensity}`);
|
|
338
|
+
}
|
|
339
|
+
if (args.intensity < 0) {
|
|
340
|
+
throw new Error('Invalid intensity: must be non-negative');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Validate location if provided
|
|
344
|
+
if (args.location !== undefined && args.location !== null) {
|
|
345
|
+
if (!Array.isArray(args.location) && typeof args.location !== 'object') {
|
|
346
|
+
throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
mappedName = 'create_light';
|
|
350
|
+
mappedArgs = {
|
|
351
|
+
lightType: args.lightType,
|
|
352
|
+
name: args.name,
|
|
353
|
+
location: args.location,
|
|
354
|
+
intensity: args.intensity
|
|
355
|
+
};
|
|
356
|
+
break;
|
|
357
|
+
case 'build_lighting':
|
|
358
|
+
mappedName = 'build_lighting';
|
|
359
|
+
mappedArgs = {
|
|
360
|
+
quality: args.quality
|
|
361
|
+
};
|
|
362
|
+
break;
|
|
363
|
+
default:
|
|
364
|
+
throw new Error(`Unknown level action: ${args.action}`);
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
// 5. ANIMATION & PHYSICS
|
|
368
|
+
case 'animation_physics':
|
|
369
|
+
// Validate action exists
|
|
370
|
+
if (!args.action) {
|
|
371
|
+
throw new Error('Missing required parameter: action');
|
|
372
|
+
}
|
|
373
|
+
switch (args.action) {
|
|
374
|
+
case 'create_animation_bp':
|
|
375
|
+
// Validate required parameters
|
|
376
|
+
if (args.name === undefined || args.name === null) {
|
|
377
|
+
throw new Error('Missing required parameter: name');
|
|
378
|
+
}
|
|
379
|
+
if (typeof args.name !== 'string') {
|
|
380
|
+
throw new Error('Invalid name: must be a string');
|
|
381
|
+
}
|
|
382
|
+
if (args.name.trim() === '') {
|
|
383
|
+
throw new Error('Invalid name: cannot be empty');
|
|
384
|
+
}
|
|
385
|
+
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
386
|
+
throw new Error('Missing required parameter: skeletonPath');
|
|
387
|
+
}
|
|
388
|
+
if (typeof args.skeletonPath !== 'string') {
|
|
389
|
+
throw new Error('Invalid skeletonPath: must be a string');
|
|
390
|
+
}
|
|
391
|
+
if (args.skeletonPath.trim() === '') {
|
|
392
|
+
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
393
|
+
}
|
|
394
|
+
// Optional savePath validation
|
|
395
|
+
if (args.savePath !== undefined && args.savePath !== null) {
|
|
396
|
+
if (typeof args.savePath !== 'string') {
|
|
397
|
+
throw new Error('Invalid savePath: must be a string');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
mappedName = 'create_animation_blueprint';
|
|
401
|
+
mappedArgs = {
|
|
402
|
+
name: args.name,
|
|
403
|
+
skeletonPath: args.skeletonPath,
|
|
404
|
+
savePath: args.savePath
|
|
405
|
+
};
|
|
406
|
+
break;
|
|
407
|
+
case 'play_montage':
|
|
408
|
+
// Validate required parameters
|
|
409
|
+
if (args.actorName === undefined || args.actorName === null) {
|
|
410
|
+
throw new Error('Missing required parameter: actorName');
|
|
411
|
+
}
|
|
412
|
+
if (typeof args.actorName !== 'string') {
|
|
413
|
+
throw new Error('Invalid actorName: must be a string');
|
|
414
|
+
}
|
|
415
|
+
if (args.actorName.trim() === '') {
|
|
416
|
+
throw new Error('Invalid actorName: cannot be empty');
|
|
417
|
+
}
|
|
418
|
+
// Check for montagePath or animationPath
|
|
419
|
+
const montagePath = args.montagePath || args.animationPath;
|
|
420
|
+
if (montagePath === undefined || montagePath === null) {
|
|
421
|
+
throw new Error('Missing required parameter: montagePath or animationPath');
|
|
422
|
+
}
|
|
423
|
+
if (typeof montagePath !== 'string') {
|
|
424
|
+
throw new Error('Invalid montagePath: must be a string');
|
|
425
|
+
}
|
|
426
|
+
if (montagePath.trim() === '') {
|
|
427
|
+
throw new Error('Invalid montagePath: cannot be empty');
|
|
428
|
+
}
|
|
429
|
+
// Optional playRate validation
|
|
430
|
+
if (args.playRate !== undefined && args.playRate !== null) {
|
|
431
|
+
if (typeof args.playRate !== 'number') {
|
|
432
|
+
throw new Error('Invalid playRate: must be a number');
|
|
433
|
+
}
|
|
434
|
+
if (isNaN(args.playRate)) {
|
|
435
|
+
throw new Error('Invalid playRate: cannot be NaN');
|
|
436
|
+
}
|
|
437
|
+
if (!isFinite(args.playRate)) {
|
|
438
|
+
throw new Error('Invalid playRate: must be finite');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
mappedName = 'play_animation_montage';
|
|
442
|
+
mappedArgs = {
|
|
443
|
+
actorName: args.actorName,
|
|
444
|
+
montagePath: montagePath,
|
|
445
|
+
playRate: args.playRate
|
|
446
|
+
};
|
|
447
|
+
break;
|
|
448
|
+
case 'setup_ragdoll':
|
|
449
|
+
// Validate required parameters
|
|
450
|
+
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
451
|
+
throw new Error('Missing required parameter: skeletonPath');
|
|
452
|
+
}
|
|
453
|
+
if (typeof args.skeletonPath !== 'string') {
|
|
454
|
+
throw new Error('Invalid skeletonPath: must be a string');
|
|
455
|
+
}
|
|
456
|
+
if (args.skeletonPath.trim() === '') {
|
|
457
|
+
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
458
|
+
}
|
|
459
|
+
if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
|
|
460
|
+
throw new Error('Missing required parameter: physicsAssetName');
|
|
461
|
+
}
|
|
462
|
+
if (typeof args.physicsAssetName !== 'string') {
|
|
463
|
+
throw new Error('Invalid physicsAssetName: must be a string');
|
|
464
|
+
}
|
|
465
|
+
if (args.physicsAssetName.trim() === '') {
|
|
466
|
+
throw new Error('Invalid physicsAssetName: cannot be empty');
|
|
467
|
+
}
|
|
468
|
+
// Optional blendWeight validation
|
|
469
|
+
if (args.blendWeight !== undefined && args.blendWeight !== null) {
|
|
470
|
+
if (typeof args.blendWeight !== 'number') {
|
|
471
|
+
throw new Error('Invalid blendWeight: must be a number');
|
|
472
|
+
}
|
|
473
|
+
if (isNaN(args.blendWeight)) {
|
|
474
|
+
throw new Error('Invalid blendWeight: cannot be NaN');
|
|
475
|
+
}
|
|
476
|
+
if (!isFinite(args.blendWeight)) {
|
|
477
|
+
throw new Error('Invalid blendWeight: must be finite');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Optional savePath validation
|
|
481
|
+
if (args.savePath !== undefined && args.savePath !== null) {
|
|
482
|
+
if (typeof args.savePath !== 'string') {
|
|
483
|
+
throw new Error('Invalid savePath: must be a string');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
mappedName = 'setup_ragdoll';
|
|
487
|
+
mappedArgs = {
|
|
488
|
+
skeletonPath: args.skeletonPath,
|
|
489
|
+
physicsAssetName: args.physicsAssetName,
|
|
490
|
+
blendWeight: args.blendWeight,
|
|
491
|
+
savePath: args.savePath
|
|
492
|
+
};
|
|
493
|
+
break;
|
|
494
|
+
default:
|
|
495
|
+
throw new Error(`Unknown animation/physics action: ${args.action}`);
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
// 6. EFFECTS SYSTEM
|
|
499
|
+
case 'create_effect':
|
|
500
|
+
switch (args.action) {
|
|
501
|
+
case 'particle':
|
|
502
|
+
mappedName = 'create_particle_effect';
|
|
503
|
+
mappedArgs = {
|
|
504
|
+
effectType: args.effectType,
|
|
505
|
+
name: args.name,
|
|
506
|
+
location: args.location
|
|
507
|
+
};
|
|
508
|
+
break;
|
|
509
|
+
case 'niagara':
|
|
510
|
+
mappedName = 'spawn_niagara_system';
|
|
511
|
+
mappedArgs = {
|
|
512
|
+
systemPath: args.systemPath,
|
|
513
|
+
location: args.location,
|
|
514
|
+
scale: args.scale
|
|
515
|
+
};
|
|
516
|
+
break;
|
|
517
|
+
case 'debug_shape':
|
|
518
|
+
mappedName = 'draw_debug_shape';
|
|
519
|
+
// Convert location object to array for position
|
|
520
|
+
const pos = args.location || args.position;
|
|
521
|
+
mappedArgs = {
|
|
522
|
+
shape: args.shape,
|
|
523
|
+
position: pos ? [pos.x || 0, pos.y || 0, pos.z || 0] : [0, 0, 0],
|
|
524
|
+
size: args.size,
|
|
525
|
+
color: args.color,
|
|
526
|
+
duration: args.duration
|
|
527
|
+
};
|
|
528
|
+
break;
|
|
529
|
+
default:
|
|
530
|
+
throw new Error(`Unknown effect action: ${args.action}`);
|
|
531
|
+
}
|
|
532
|
+
break;
|
|
533
|
+
// 7. BLUEPRINT MANAGER
|
|
534
|
+
case 'manage_blueprint':
|
|
535
|
+
switch (args.action) {
|
|
536
|
+
case 'create':
|
|
537
|
+
mappedName = 'create_blueprint';
|
|
538
|
+
mappedArgs = {
|
|
539
|
+
name: args.name,
|
|
540
|
+
blueprintType: args.blueprintType,
|
|
541
|
+
savePath: args.savePath
|
|
542
|
+
};
|
|
543
|
+
break;
|
|
544
|
+
case 'add_component':
|
|
545
|
+
mappedName = 'add_blueprint_component';
|
|
546
|
+
mappedArgs = {
|
|
547
|
+
blueprintName: args.name,
|
|
548
|
+
componentType: args.componentType,
|
|
549
|
+
componentName: args.componentName
|
|
550
|
+
};
|
|
551
|
+
break;
|
|
552
|
+
default:
|
|
553
|
+
throw new Error(`Unknown blueprint action: ${args.action}`);
|
|
554
|
+
}
|
|
555
|
+
break;
|
|
556
|
+
// 8. ENVIRONMENT BUILDER
|
|
557
|
+
case 'build_environment':
|
|
558
|
+
switch (args.action) {
|
|
559
|
+
case 'create_landscape':
|
|
560
|
+
mappedName = 'create_landscape';
|
|
561
|
+
mappedArgs = {
|
|
562
|
+
name: args.name,
|
|
563
|
+
sizeX: args.sizeX,
|
|
564
|
+
sizeY: args.sizeY,
|
|
565
|
+
materialPath: args.materialPath
|
|
566
|
+
};
|
|
567
|
+
break;
|
|
568
|
+
case 'sculpt':
|
|
569
|
+
mappedName = 'sculpt_landscape';
|
|
570
|
+
mappedArgs = {
|
|
571
|
+
landscapeName: args.name,
|
|
572
|
+
tool: args.tool,
|
|
573
|
+
brushSize: args.brushSize,
|
|
574
|
+
strength: args.strength
|
|
575
|
+
};
|
|
576
|
+
break;
|
|
577
|
+
case 'add_foliage':
|
|
578
|
+
// Validate foliage creation parameters to avoid bad console commands / engine warnings
|
|
579
|
+
if (args.name === undefined || args.name === null || typeof args.name !== 'string' || args.name.trim() === '' || String(args.name).toLowerCase() === 'undefined' || String(args.name).toLowerCase() === 'any') {
|
|
580
|
+
throw new Error(`Invalid foliage name: '${args.name}'`);
|
|
581
|
+
}
|
|
582
|
+
if (args.meshPath === undefined || args.meshPath === null || typeof args.meshPath !== 'string' || args.meshPath.trim() === '' || String(args.meshPath).toLowerCase() === 'undefined') {
|
|
583
|
+
throw new Error(`Invalid meshPath: '${args.meshPath}'`);
|
|
584
|
+
}
|
|
585
|
+
if (args.density !== undefined) {
|
|
586
|
+
if (typeof args.density !== 'number' || !isFinite(args.density) || args.density < 0) {
|
|
587
|
+
throw new Error(`Invalid density: '${args.density}' (must be non-negative finite number)`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
mappedName = 'add_foliage_type';
|
|
591
|
+
mappedArgs = {
|
|
592
|
+
name: args.name,
|
|
593
|
+
meshPath: args.meshPath,
|
|
594
|
+
density: args.density
|
|
595
|
+
};
|
|
596
|
+
break;
|
|
597
|
+
case 'paint_foliage':
|
|
598
|
+
// Validate paint parameters
|
|
599
|
+
if (args.foliageType === undefined || args.foliageType === null || typeof args.foliageType !== 'string' || args.foliageType.trim() === '' || String(args.foliageType).toLowerCase() === 'undefined' || String(args.foliageType).toLowerCase() === 'any') {
|
|
600
|
+
throw new Error(`Invalid foliageType: '${args.foliageType}'`);
|
|
601
|
+
}
|
|
602
|
+
// Convert position object to array if needed
|
|
603
|
+
let positionArray;
|
|
604
|
+
if (args.position) {
|
|
605
|
+
if (Array.isArray(args.position)) {
|
|
606
|
+
positionArray = args.position;
|
|
607
|
+
}
|
|
608
|
+
else if (typeof args.position === 'object') {
|
|
609
|
+
positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
positionArray = [0, 0, 0];
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
positionArray = [0, 0, 0];
|
|
617
|
+
}
|
|
618
|
+
// Validate numbers in position
|
|
619
|
+
if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
|
|
620
|
+
throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
|
|
621
|
+
}
|
|
622
|
+
if (args.brushSize !== undefined) {
|
|
623
|
+
if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
|
|
624
|
+
throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
mappedName = 'paint_foliage';
|
|
628
|
+
mappedArgs = {
|
|
629
|
+
foliageType: args.foliageType,
|
|
630
|
+
position: positionArray,
|
|
631
|
+
brushSize: args.brushSize
|
|
632
|
+
};
|
|
633
|
+
break;
|
|
634
|
+
default:
|
|
635
|
+
throw new Error(`Unknown environment action: ${args.action}`);
|
|
636
|
+
}
|
|
637
|
+
break;
|
|
638
|
+
// 9. SYSTEM CONTROL
|
|
639
|
+
case 'system_control':
|
|
640
|
+
// Validate args is not null/undefined
|
|
641
|
+
if (args === null || args === undefined) {
|
|
642
|
+
throw new Error('Invalid arguments: null or undefined');
|
|
643
|
+
}
|
|
644
|
+
if (typeof args !== 'object') {
|
|
645
|
+
throw new Error('Invalid arguments: must be an object');
|
|
646
|
+
}
|
|
647
|
+
// Validate action exists
|
|
648
|
+
if (!args.action) {
|
|
649
|
+
throw new Error('Missing required parameter: action');
|
|
650
|
+
}
|
|
651
|
+
switch (args.action) {
|
|
652
|
+
case 'profile':
|
|
653
|
+
// Validate profile type
|
|
654
|
+
const validProfileTypes = ['CPU', 'GPU', 'Memory', 'RenderThread', 'GameThread', 'All'];
|
|
655
|
+
if (!args.profileType || !validProfileTypes.includes(args.profileType)) {
|
|
656
|
+
throw new Error(`Invalid profileType: '${args.profileType}'. Valid types: ${validProfileTypes.join(', ')}`);
|
|
657
|
+
}
|
|
658
|
+
mappedName = 'start_profiling';
|
|
659
|
+
mappedArgs = {
|
|
660
|
+
type: args.profileType,
|
|
661
|
+
duration: args.duration
|
|
662
|
+
};
|
|
663
|
+
break;
|
|
664
|
+
case 'show_fps':
|
|
665
|
+
// Validate enabled is boolean
|
|
666
|
+
if (args.enabled !== undefined && typeof args.enabled !== 'boolean') {
|
|
667
|
+
throw new Error(`Invalid enabled: must be boolean, got ${typeof args.enabled}`);
|
|
668
|
+
}
|
|
669
|
+
mappedName = 'show_fps';
|
|
670
|
+
mappedArgs = {
|
|
671
|
+
enabled: args.enabled,
|
|
672
|
+
verbose: args.verbose
|
|
673
|
+
};
|
|
674
|
+
break;
|
|
675
|
+
case 'set_quality':
|
|
676
|
+
// Validate category - normalize aliases and singular forms used by sg.*Quality
|
|
677
|
+
const validCategories = ['ViewDistance', 'AntiAliasing', 'PostProcessing', 'PostProcess',
|
|
678
|
+
'Shadows', 'Shadow', 'GlobalIllumination', 'Reflections', 'Reflection', 'Textures', 'Texture',
|
|
679
|
+
'Effects', 'Foliage', 'Shading'];
|
|
680
|
+
if (!args.category || !validCategories.includes(args.category)) {
|
|
681
|
+
throw new Error(`Invalid category: '${args.category}'. Valid categories: ${validCategories.join(', ')}`);
|
|
682
|
+
}
|
|
683
|
+
// Validate level
|
|
684
|
+
if (args.level === undefined || args.level === null) {
|
|
685
|
+
throw new Error('Missing required parameter: level');
|
|
686
|
+
}
|
|
687
|
+
if (typeof args.level !== 'number' || !Number.isInteger(args.level) || args.level < 0 || args.level > 4) {
|
|
688
|
+
throw new Error(`Invalid level: must be integer 0-4, got ${args.level}`);
|
|
689
|
+
}
|
|
690
|
+
// Normalize category to sg.<Base>Quality base (singular where needed)
|
|
691
|
+
const map = {
|
|
692
|
+
ViewDistance: 'ViewDistance',
|
|
693
|
+
AntiAliasing: 'AntiAliasing',
|
|
694
|
+
PostProcessing: 'PostProcess',
|
|
695
|
+
PostProcess: 'PostProcess',
|
|
696
|
+
Shadows: 'Shadow',
|
|
697
|
+
Shadow: 'Shadow',
|
|
698
|
+
GlobalIllumination: 'GlobalIllumination',
|
|
699
|
+
Reflections: 'Reflection',
|
|
700
|
+
Reflection: 'Reflection',
|
|
701
|
+
Textures: 'Texture',
|
|
702
|
+
Texture: 'Texture',
|
|
703
|
+
Effects: 'Effects',
|
|
704
|
+
Foliage: 'Foliage',
|
|
705
|
+
Shading: 'Shading',
|
|
706
|
+
};
|
|
707
|
+
const categoryName = map[String(args.category)] || args.category;
|
|
708
|
+
mappedName = 'set_scalability';
|
|
709
|
+
mappedArgs = {
|
|
710
|
+
category: categoryName,
|
|
711
|
+
level: args.level
|
|
712
|
+
};
|
|
713
|
+
break;
|
|
714
|
+
case 'play_sound':
|
|
715
|
+
// Validate sound path
|
|
716
|
+
if (!args.soundPath || typeof args.soundPath !== 'string') {
|
|
717
|
+
throw new Error('Invalid soundPath: must be a non-empty string');
|
|
718
|
+
}
|
|
719
|
+
// Validate volume if provided
|
|
720
|
+
if (args.volume !== undefined) {
|
|
721
|
+
if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
|
|
722
|
+
throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
mappedName = 'play_sound';
|
|
726
|
+
mappedArgs = {
|
|
727
|
+
soundPath: args.soundPath,
|
|
728
|
+
location: args.location,
|
|
729
|
+
volume: args.volume,
|
|
730
|
+
is3D: args.is3D
|
|
731
|
+
};
|
|
732
|
+
break;
|
|
733
|
+
case 'create_widget':
|
|
734
|
+
// Validate widget name
|
|
735
|
+
if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
|
|
736
|
+
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
737
|
+
}
|
|
738
|
+
mappedName = 'create_widget';
|
|
739
|
+
mappedArgs = {
|
|
740
|
+
name: args.widgetName,
|
|
741
|
+
type: args.widgetType,
|
|
742
|
+
savePath: args.savePath
|
|
743
|
+
};
|
|
744
|
+
break;
|
|
745
|
+
case 'show_widget':
|
|
746
|
+
// Validate widget name
|
|
747
|
+
if (!args.widgetName || typeof args.widgetName !== 'string') {
|
|
748
|
+
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
749
|
+
}
|
|
750
|
+
// Validate visible is boolean (default to true if not provided)
|
|
751
|
+
const isVisible = args.visible !== undefined ? args.visible : true;
|
|
752
|
+
if (typeof isVisible !== 'boolean') {
|
|
753
|
+
throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
|
|
754
|
+
}
|
|
755
|
+
mappedName = 'show_widget';
|
|
756
|
+
mappedArgs = {
|
|
757
|
+
widgetName: args.widgetName,
|
|
758
|
+
visible: isVisible
|
|
759
|
+
};
|
|
760
|
+
break;
|
|
761
|
+
case 'screenshot':
|
|
762
|
+
mappedName = 'take_screenshot';
|
|
763
|
+
mappedArgs = { resolution: args.resolution };
|
|
764
|
+
break;
|
|
765
|
+
case 'engine_start':
|
|
766
|
+
mappedName = 'launch_editor';
|
|
767
|
+
mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
|
|
768
|
+
break;
|
|
769
|
+
case 'engine_quit':
|
|
770
|
+
mappedName = 'quit_editor';
|
|
771
|
+
mappedArgs = {};
|
|
772
|
+
break;
|
|
773
|
+
default:
|
|
774
|
+
throw new Error(`Unknown system action: ${args.action}`);
|
|
775
|
+
}
|
|
776
|
+
break;
|
|
777
|
+
// 10. CONSOLE COMMAND - handle validation here
|
|
778
|
+
case 'console_command':
|
|
779
|
+
// Handle empty/invalid commands gracefully
|
|
780
|
+
if (args.command === undefined || args.command === null || args.command === '' || typeof args.command !== 'string') {
|
|
781
|
+
return {
|
|
782
|
+
content: [{
|
|
783
|
+
type: 'text',
|
|
784
|
+
text: 'Empty command ignored'
|
|
785
|
+
}],
|
|
786
|
+
isError: false,
|
|
787
|
+
success: true,
|
|
788
|
+
message: 'Empty command'
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const cmdTrimmed = args.command.trim();
|
|
792
|
+
if (cmdTrimmed.length === 0) {
|
|
793
|
+
return {
|
|
794
|
+
content: [{
|
|
795
|
+
type: 'text',
|
|
796
|
+
text: 'Empty command ignored'
|
|
797
|
+
}],
|
|
798
|
+
isError: false,
|
|
799
|
+
success: true,
|
|
800
|
+
message: 'Empty command'
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
mappedName = 'console_command';
|
|
804
|
+
mappedArgs = args;
|
|
805
|
+
break;
|
|
806
|
+
// 11. REMOTE CONTROL PRESETS - Direct implementation
|
|
807
|
+
case 'manage_rc':
|
|
808
|
+
if (!args.action)
|
|
809
|
+
throw new Error('Missing required parameter: action');
|
|
810
|
+
// Handle RC operations directly through RcTools
|
|
811
|
+
let rcResult;
|
|
812
|
+
switch (args.action) {
|
|
813
|
+
// Support both 'create_preset' and 'create' for compatibility
|
|
814
|
+
case 'create_preset':
|
|
815
|
+
case 'create':
|
|
816
|
+
// Support both 'name' and 'presetName' parameter names
|
|
817
|
+
const presetName = args.name || args.presetName;
|
|
818
|
+
if (!presetName)
|
|
819
|
+
throw new Error('Missing required parameter: name or presetName');
|
|
820
|
+
rcResult = await tools.rcTools.createPreset({
|
|
821
|
+
name: presetName,
|
|
822
|
+
path: args.path
|
|
823
|
+
});
|
|
824
|
+
// Return consistent output with presetId for tests
|
|
825
|
+
if (rcResult.success) {
|
|
826
|
+
rcResult.message = `Remote Control preset created: ${presetName}`;
|
|
827
|
+
// Ensure presetId is set (for test compatibility)
|
|
828
|
+
if (rcResult.presetPath && !rcResult.presetId) {
|
|
829
|
+
rcResult.presetId = rcResult.presetPath;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
break;
|
|
833
|
+
case 'list':
|
|
834
|
+
// List all presets - implement via RcTools
|
|
835
|
+
rcResult = await tools.rcTools.listPresets();
|
|
836
|
+
break;
|
|
837
|
+
case 'delete':
|
|
838
|
+
if (!args.presetId)
|
|
839
|
+
throw new Error('Missing required parameter: presetId');
|
|
840
|
+
rcResult = await tools.rcTools.deletePreset(args.presetId);
|
|
841
|
+
if (rcResult.success) {
|
|
842
|
+
rcResult.message = 'Preset deleted successfully';
|
|
843
|
+
}
|
|
844
|
+
break;
|
|
845
|
+
case 'expose_actor':
|
|
846
|
+
if (!args.presetPath)
|
|
847
|
+
throw new Error('Missing required parameter: presetPath');
|
|
848
|
+
if (!args.actorName)
|
|
849
|
+
throw new Error('Missing required parameter: actorName');
|
|
850
|
+
rcResult = await tools.rcTools.exposeActor({
|
|
851
|
+
presetPath: args.presetPath,
|
|
852
|
+
actorName: args.actorName
|
|
853
|
+
});
|
|
854
|
+
if (rcResult.success) {
|
|
855
|
+
rcResult.message = `Actor '${args.actorName}' exposed to preset`;
|
|
856
|
+
}
|
|
857
|
+
break;
|
|
858
|
+
case 'expose_property':
|
|
859
|
+
case 'expose': // Support simplified name from tests
|
|
860
|
+
// Support both presetPath and presetId
|
|
861
|
+
const presetPathExp = args.presetPath || args.presetId;
|
|
862
|
+
if (!presetPathExp)
|
|
863
|
+
throw new Error('Missing required parameter: presetPath or presetId');
|
|
864
|
+
if (!args.objectPath)
|
|
865
|
+
throw new Error('Missing required parameter: objectPath');
|
|
866
|
+
if (!args.propertyName)
|
|
867
|
+
throw new Error('Missing required parameter: propertyName');
|
|
868
|
+
rcResult = await tools.rcTools.exposeProperty({
|
|
869
|
+
presetPath: presetPathExp,
|
|
870
|
+
objectPath: args.objectPath,
|
|
871
|
+
propertyName: args.propertyName
|
|
872
|
+
});
|
|
873
|
+
if (rcResult.success) {
|
|
874
|
+
rcResult.message = `Property '${args.propertyName}' exposed to preset`;
|
|
875
|
+
}
|
|
876
|
+
break;
|
|
877
|
+
case 'list_fields':
|
|
878
|
+
case 'get_exposed': // Support test naming
|
|
879
|
+
const presetPathList = args.presetPath || args.presetId;
|
|
880
|
+
if (!presetPathList)
|
|
881
|
+
throw new Error('Missing required parameter: presetPath or presetId');
|
|
882
|
+
rcResult = await tools.rcTools.listFields({
|
|
883
|
+
presetPath: presetPathList
|
|
884
|
+
});
|
|
885
|
+
// Map 'fields' to 'exposedProperties' for test compatibility
|
|
886
|
+
if (rcResult.success && rcResult.fields) {
|
|
887
|
+
rcResult.exposedProperties = rcResult.fields;
|
|
888
|
+
}
|
|
889
|
+
break;
|
|
890
|
+
case 'set_property':
|
|
891
|
+
case 'set_value': // Support test naming
|
|
892
|
+
// Support both patterns
|
|
893
|
+
const objPathSet = args.objectPath || args.presetId;
|
|
894
|
+
const propNameSet = args.propertyName || args.propertyLabel;
|
|
895
|
+
if (!objPathSet)
|
|
896
|
+
throw new Error('Missing required parameter: objectPath or presetId');
|
|
897
|
+
if (!propNameSet)
|
|
898
|
+
throw new Error('Missing required parameter: propertyName or propertyLabel');
|
|
899
|
+
if (args.value === undefined)
|
|
900
|
+
throw new Error('Missing required parameter: value');
|
|
901
|
+
rcResult = await tools.rcTools.setProperty({
|
|
902
|
+
objectPath: objPathSet,
|
|
903
|
+
propertyName: propNameSet,
|
|
904
|
+
value: args.value
|
|
905
|
+
});
|
|
906
|
+
if (rcResult.success) {
|
|
907
|
+
rcResult.message = `Property '${propNameSet}' value updated`;
|
|
908
|
+
}
|
|
909
|
+
break;
|
|
910
|
+
case 'get_property':
|
|
911
|
+
case 'get_value': // Support test naming
|
|
912
|
+
const objPathGet = args.objectPath || args.presetId;
|
|
913
|
+
const propNameGet = args.propertyName || args.propertyLabel;
|
|
914
|
+
if (!objPathGet)
|
|
915
|
+
throw new Error('Missing required parameter: objectPath or presetId');
|
|
916
|
+
if (!propNameGet)
|
|
917
|
+
throw new Error('Missing required parameter: propertyName or propertyLabel');
|
|
918
|
+
rcResult = await tools.rcTools.getProperty({
|
|
919
|
+
objectPath: objPathGet,
|
|
920
|
+
propertyName: propNameGet
|
|
921
|
+
});
|
|
922
|
+
break;
|
|
923
|
+
case 'call_function':
|
|
924
|
+
if (!args.presetId)
|
|
925
|
+
throw new Error('Missing required parameter: presetId');
|
|
926
|
+
if (!args.functionLabel)
|
|
927
|
+
throw new Error('Missing required parameter: functionLabel');
|
|
928
|
+
// For now, return not implemented
|
|
929
|
+
rcResult = {
|
|
930
|
+
success: false,
|
|
931
|
+
error: 'Function calls not yet implemented'
|
|
932
|
+
};
|
|
933
|
+
break;
|
|
934
|
+
default:
|
|
935
|
+
throw new Error(`Unknown RC action: ${args.action}. Valid actions are: create_preset, expose_actor, expose_property, list_fields, set_property, get_property, or their simplified versions: create, list, delete, expose, get_exposed, set_value, get_value, call_function`);
|
|
936
|
+
}
|
|
937
|
+
// Return result directly - MCP formatting will be handled by response validator
|
|
938
|
+
// Clean to prevent circular references
|
|
939
|
+
return cleanObject(rcResult);
|
|
940
|
+
// 12. SEQUENCER / CINEMATICS
|
|
941
|
+
case 'manage_sequence':
|
|
942
|
+
if (!args.action)
|
|
943
|
+
throw new Error('Missing required parameter: action');
|
|
944
|
+
// Direct handling for sequence operations
|
|
945
|
+
const seqResult = await (async () => {
|
|
946
|
+
const sequenceTools = tools.sequenceTools;
|
|
947
|
+
if (!sequenceTools)
|
|
948
|
+
throw new Error('Sequence tools not available');
|
|
949
|
+
switch (args.action) {
|
|
950
|
+
case 'create':
|
|
951
|
+
return await sequenceTools.create({ name: args.name, path: args.path });
|
|
952
|
+
case 'open':
|
|
953
|
+
return await sequenceTools.open({ path: args.path });
|
|
954
|
+
case 'add_camera':
|
|
955
|
+
return await sequenceTools.addCamera({ spawnable: args.spawnable !== false });
|
|
956
|
+
case 'add_actor':
|
|
957
|
+
return await sequenceTools.addActor({ actorName: args.actorName });
|
|
958
|
+
case 'add_actors':
|
|
959
|
+
if (!args.actorNames)
|
|
960
|
+
throw new Error('Missing required parameter: actorNames');
|
|
961
|
+
return await sequenceTools.addActors({ actorNames: args.actorNames });
|
|
962
|
+
case 'remove_actors':
|
|
963
|
+
if (!args.actorNames)
|
|
964
|
+
throw new Error('Missing required parameter: actorNames');
|
|
965
|
+
return await sequenceTools.removeActors({ actorNames: args.actorNames });
|
|
966
|
+
case 'get_bindings':
|
|
967
|
+
return await sequenceTools.getBindings({ path: args.path });
|
|
968
|
+
case 'add_spawnable_from_class':
|
|
969
|
+
if (!args.className)
|
|
970
|
+
throw new Error('Missing required parameter: className');
|
|
971
|
+
return await sequenceTools.addSpawnableFromClass({ className: args.className, path: args.path });
|
|
972
|
+
case 'play':
|
|
973
|
+
return await sequenceTools.play({ loopMode: args.loopMode });
|
|
974
|
+
case 'pause':
|
|
975
|
+
return await sequenceTools.pause();
|
|
976
|
+
case 'stop':
|
|
977
|
+
return await sequenceTools.stop();
|
|
978
|
+
case 'set_properties':
|
|
979
|
+
return await sequenceTools.setSequenceProperties({
|
|
980
|
+
path: args.path,
|
|
981
|
+
frameRate: args.frameRate,
|
|
982
|
+
lengthInFrames: args.lengthInFrames,
|
|
983
|
+
playbackStart: args.playbackStart,
|
|
984
|
+
playbackEnd: args.playbackEnd
|
|
985
|
+
});
|
|
986
|
+
case 'get_properties':
|
|
987
|
+
return await sequenceTools.getSequenceProperties({ path: args.path });
|
|
988
|
+
case 'set_playback_speed':
|
|
989
|
+
if (args.speed === undefined)
|
|
990
|
+
throw new Error('Missing required parameter: speed');
|
|
991
|
+
return await sequenceTools.setPlaybackSpeed({ speed: args.speed });
|
|
992
|
+
default:
|
|
993
|
+
throw new Error(`Unknown sequence action: ${args.action}`);
|
|
994
|
+
}
|
|
995
|
+
})();
|
|
996
|
+
// Return result directly - MCP formatting will be handled by response validator
|
|
997
|
+
// Clean to prevent circular references
|
|
998
|
+
return cleanObject(seqResult);
|
|
999
|
+
// 13. INTROSPECTION
|
|
1000
|
+
case 'inspect':
|
|
1001
|
+
if (!args.action)
|
|
1002
|
+
throw new Error('Missing required parameter: action');
|
|
1003
|
+
switch (args.action) {
|
|
1004
|
+
case 'inspect_object':
|
|
1005
|
+
mappedName = 'inspect_object';
|
|
1006
|
+
mappedArgs = { objectPath: args.objectPath };
|
|
1007
|
+
break;
|
|
1008
|
+
case 'set_property':
|
|
1009
|
+
mappedName = 'inspect_set_property';
|
|
1010
|
+
mappedArgs = { objectPath: args.objectPath, propertyName: args.propertyName, value: args.value };
|
|
1011
|
+
break;
|
|
1012
|
+
default:
|
|
1013
|
+
throw new Error(`Unknown inspect action: ${args.action}`);
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
default:
|
|
1017
|
+
throw new Error(`Unknown consolidated tool: ${name}`);
|
|
1018
|
+
}
|
|
1019
|
+
// Call the original handler with mapped name and args with timeout
|
|
1020
|
+
const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
|
|
1021
|
+
const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
|
|
1022
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1023
|
+
setTimeout(() => {
|
|
1024
|
+
reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
|
|
1025
|
+
}, TOOL_TIMEOUT);
|
|
1026
|
+
});
|
|
1027
|
+
const result = await Promise.race([toolPromise, timeoutPromise]);
|
|
1028
|
+
const duration = Date.now() - startTime;
|
|
1029
|
+
console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
|
|
1030
|
+
// Clean the result to prevent circular reference errors
|
|
1031
|
+
return cleanObject(result);
|
|
1032
|
+
}
|
|
1033
|
+
catch (err) {
|
|
1034
|
+
const duration = Date.now() - startTime;
|
|
1035
|
+
console.log(`[ConsolidatedToolHandler] Failed execution of ${name} after ${duration}ms: ${err?.message || String(err)}`);
|
|
1036
|
+
// Return consistent error structure matching regular tool handlers
|
|
1037
|
+
const errorMessage = err?.message || String(err);
|
|
1038
|
+
const isTimeout = errorMessage.includes('timeout');
|
|
1039
|
+
return {
|
|
1040
|
+
content: [{
|
|
1041
|
+
type: 'text',
|
|
1042
|
+
text: isTimeout
|
|
1043
|
+
? `Tool ${name} timed out. Please check Unreal Engine connection.`
|
|
1044
|
+
: `Failed to execute ${name}: ${errorMessage}`
|
|
1045
|
+
}],
|
|
1046
|
+
isError: true
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
//# sourceMappingURL=consolidated-tool-handlers.js.map
|