unreal-engine-mcp-server 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +21 -5
- package/dist/index.js +124 -31
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.js +46 -62
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +29 -54
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +52 -44
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +190 -45
- package/dist/tools/consolidated-tool-definitions.js +78 -252
- package/dist/tools/consolidated-tool-handlers.js +506 -133
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +167 -31
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +97 -23
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +125 -22
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.js +28 -2
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +10 -9
- package/server.json +37 -14
- package/src/index.ts +130 -33
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +48 -51
- package/src/resources/levels.ts +35 -45
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +53 -43
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +266 -64
- package/src/tools/consolidated-tool-definitions.ts +90 -264
- package/src/tools/consolidated-tool-handlers.ts +630 -121
- package/src/tools/debug.ts +176 -33
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +102 -24
- package/src/tools/sequence.ts +136 -28
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +25 -2
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
|
@@ -4,6 +4,129 @@ import { Logger } from '../utils/logger.js';
|
|
|
4
4
|
|
|
5
5
|
const log = new Logger('ConsolidatedToolHandler');
|
|
6
6
|
|
|
7
|
+
const ACTION_REQUIRED_ERROR = 'Missing required parameter: action';
|
|
8
|
+
|
|
9
|
+
function ensureArgsPresent(args: any) {
|
|
10
|
+
if (args === null || args === undefined) {
|
|
11
|
+
throw new Error('Invalid arguments: null or undefined');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function requireAction(args: any): string {
|
|
16
|
+
ensureArgsPresent(args);
|
|
17
|
+
const action = args.action;
|
|
18
|
+
if (typeof action !== 'string' || action.trim() === '') {
|
|
19
|
+
throw new Error(ACTION_REQUIRED_ERROR);
|
|
20
|
+
}
|
|
21
|
+
return action;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function requireNonEmptyString(value: any, field: string, message?: string): string {
|
|
25
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
26
|
+
throw new Error(message ?? `Invalid ${field}: must be a non-empty string`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function requirePositiveNumber(value: any, field: string, message?: string): number {
|
|
32
|
+
if (typeof value !== 'number' || !isFinite(value) || value <= 0) {
|
|
33
|
+
throw new Error(message ?? `Invalid ${field}: must be a positive number`);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function requireVector3Components(
|
|
39
|
+
vector: any,
|
|
40
|
+
message: string
|
|
41
|
+
): [number, number, number] {
|
|
42
|
+
if (
|
|
43
|
+
!vector ||
|
|
44
|
+
typeof vector.x !== 'number' ||
|
|
45
|
+
typeof vector.y !== 'number' ||
|
|
46
|
+
typeof vector.z !== 'number'
|
|
47
|
+
) {
|
|
48
|
+
throw new Error(message);
|
|
49
|
+
}
|
|
50
|
+
return [vector.x, vector.y, vector.z];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getElicitationTimeoutMs(tools: any): number | undefined {
|
|
54
|
+
if (!tools) return undefined;
|
|
55
|
+
const direct = tools.elicitationTimeoutMs;
|
|
56
|
+
if (typeof direct === 'number' && Number.isFinite(direct)) {
|
|
57
|
+
return direct;
|
|
58
|
+
}
|
|
59
|
+
if (typeof tools.getElicitationTimeoutMs === 'function') {
|
|
60
|
+
const value = tools.getElicitationTimeoutMs();
|
|
61
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function elicitMissingPrimitiveArgs(
|
|
69
|
+
tools: any,
|
|
70
|
+
args: any,
|
|
71
|
+
prompt: string,
|
|
72
|
+
fieldSchemas: Record<string, { type: 'string' | 'number' | 'integer' | 'boolean'; title?: string; description?: string; enum?: string[]; enumNames?: string[]; minimum?: number; maximum?: number; minLength?: number; maxLength?: number; pattern?: string; format?: string; default?: unknown }>
|
|
73
|
+
) {
|
|
74
|
+
if (
|
|
75
|
+
!tools ||
|
|
76
|
+
typeof tools.supportsElicitation !== 'function' ||
|
|
77
|
+
!tools.supportsElicitation() ||
|
|
78
|
+
typeof tools.elicit !== 'function'
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const properties: Record<string, any> = {};
|
|
84
|
+
const required: string[] = [];
|
|
85
|
+
|
|
86
|
+
for (const [key, schema] of Object.entries(fieldSchemas)) {
|
|
87
|
+
const value = args?.[key];
|
|
88
|
+
const missing =
|
|
89
|
+
value === undefined ||
|
|
90
|
+
value === null ||
|
|
91
|
+
(typeof value === 'string' && value.trim() === '');
|
|
92
|
+
if (missing) {
|
|
93
|
+
properties[key] = schema;
|
|
94
|
+
required.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (required.length === 0) return;
|
|
99
|
+
|
|
100
|
+
const timeoutMs = getElicitationTimeoutMs(tools);
|
|
101
|
+
const options: any = {
|
|
102
|
+
fallback: async () => ({ ok: false, error: 'missing-params' })
|
|
103
|
+
};
|
|
104
|
+
if (typeof timeoutMs === 'number') {
|
|
105
|
+
options.timeoutMs = timeoutMs;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const elicited = await tools.elicit(
|
|
110
|
+
prompt,
|
|
111
|
+
{ type: 'object', properties, required },
|
|
112
|
+
options
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (elicited?.ok && elicited.value) {
|
|
116
|
+
for (const key of required) {
|
|
117
|
+
const value = elicited.value[key];
|
|
118
|
+
if (value === undefined || value === null) continue;
|
|
119
|
+
args[key] = typeof value === 'string' ? value.trim() : value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log.debug('Special elicitation fallback skipped', {
|
|
124
|
+
prompt,
|
|
125
|
+
err: (err as any)?.message || String(err)
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
7
130
|
export async function handleConsolidatedToolCall(
|
|
8
131
|
name: string,
|
|
9
132
|
args: any,
|
|
@@ -14,26 +137,12 @@ export async function handleConsolidatedToolCall(
|
|
|
14
137
|
log.debug(`Starting execution of ${name} at ${new Date().toISOString()}`);
|
|
15
138
|
|
|
16
139
|
try {
|
|
17
|
-
|
|
18
|
-
if (args === null || args === undefined) {
|
|
19
|
-
throw new Error('Invalid arguments: null or undefined');
|
|
20
|
-
}
|
|
21
|
-
|
|
140
|
+
ensureArgsPresent(args);
|
|
22
141
|
|
|
23
142
|
switch (name) {
|
|
24
143
|
// 1. ASSET MANAGER
|
|
25
144
|
case 'manage_asset':
|
|
26
|
-
|
|
27
|
-
if (args === null || args === undefined) {
|
|
28
|
-
throw new Error('Invalid arguments: null or undefined');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Validate action exists
|
|
32
|
-
if (!args.action) {
|
|
33
|
-
throw new Error('Missing required parameter: action');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
switch (args.action) {
|
|
145
|
+
switch (requireAction(args)) {
|
|
37
146
|
case 'list': {
|
|
38
147
|
if (args.directory !== undefined && args.directory !== null && typeof args.directory !== 'string') {
|
|
39
148
|
throw new Error('Invalid directory: must be a string');
|
|
@@ -42,20 +151,81 @@ switch (args.action) {
|
|
|
42
151
|
return cleanObject({ success: true, ...res });
|
|
43
152
|
}
|
|
44
153
|
case 'import': {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (typeof
|
|
49
|
-
|
|
154
|
+
let sourcePath = typeof args.sourcePath === 'string' ? args.sourcePath.trim() : '';
|
|
155
|
+
let destinationPath = typeof args.destinationPath === 'string' ? args.destinationPath.trim() : '';
|
|
156
|
+
|
|
157
|
+
if ((!sourcePath || !destinationPath) && typeof tools.supportsElicitation === 'function' && tools.supportsElicitation() && typeof tools.elicit === 'function') {
|
|
158
|
+
const schemaProps: Record<string, any> = {};
|
|
159
|
+
const required: string[] = [];
|
|
160
|
+
|
|
161
|
+
if (!sourcePath) {
|
|
162
|
+
schemaProps.sourcePath = {
|
|
163
|
+
type: 'string',
|
|
164
|
+
title: 'Source File Path',
|
|
165
|
+
description: 'Full path to the asset file on disk to import'
|
|
166
|
+
};
|
|
167
|
+
required.push('sourcePath');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!destinationPath) {
|
|
171
|
+
schemaProps.destinationPath = {
|
|
172
|
+
type: 'string',
|
|
173
|
+
title: 'Destination Path',
|
|
174
|
+
description: 'Unreal content path where the asset should be imported (e.g., /Game/MCP/Assets)'
|
|
175
|
+
};
|
|
176
|
+
required.push('destinationPath');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (required.length > 0) {
|
|
180
|
+
const timeoutMs = getElicitationTimeoutMs(tools);
|
|
181
|
+
const options: any = { fallback: async () => ({ ok: false, error: 'missing-import-params' }) };
|
|
182
|
+
if (typeof timeoutMs === 'number') {
|
|
183
|
+
options.timeoutMs = timeoutMs;
|
|
184
|
+
}
|
|
185
|
+
const elicited = await tools.elicit(
|
|
186
|
+
'Provide the missing import parameters for manage_asset.import',
|
|
187
|
+
{ type: 'object', properties: schemaProps, required },
|
|
188
|
+
options
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (elicited?.ok && elicited.value) {
|
|
192
|
+
if (typeof elicited.value.sourcePath === 'string') {
|
|
193
|
+
sourcePath = elicited.value.sourcePath.trim();
|
|
194
|
+
}
|
|
195
|
+
if (typeof elicited.value.destinationPath === 'string') {
|
|
196
|
+
destinationPath = elicited.value.destinationPath.trim();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
50
200
|
}
|
|
51
|
-
|
|
201
|
+
|
|
202
|
+
const sourcePathValidated = requireNonEmptyString(sourcePath || args.sourcePath, 'sourcePath', 'Invalid sourcePath');
|
|
203
|
+
const destinationPathValidated = requireNonEmptyString(destinationPath || args.destinationPath, 'destinationPath', 'Invalid destinationPath');
|
|
204
|
+
const res = await tools.assetTools.importAsset(sourcePathValidated, destinationPathValidated);
|
|
52
205
|
return cleanObject(res);
|
|
53
206
|
}
|
|
54
207
|
case 'create_material': {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
208
|
+
await elicitMissingPrimitiveArgs(
|
|
209
|
+
tools,
|
|
210
|
+
args,
|
|
211
|
+
'Provide the material details for manage_asset.create_material',
|
|
212
|
+
{
|
|
213
|
+
name: {
|
|
214
|
+
type: 'string',
|
|
215
|
+
title: 'Material Name',
|
|
216
|
+
description: 'Name for the new material asset'
|
|
217
|
+
},
|
|
218
|
+
path: {
|
|
219
|
+
type: 'string',
|
|
220
|
+
title: 'Save Path',
|
|
221
|
+
description: 'Optional Unreal content path where the material should be saved'
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
const sanitizedName = typeof args.name === 'string' ? args.name.trim() : args.name;
|
|
226
|
+
const sanitizedPath = typeof args.path === 'string' ? args.path.trim() : args.path;
|
|
227
|
+
const name = requireNonEmptyString(sanitizedName, 'name', 'Invalid name: must be a non-empty string');
|
|
228
|
+
const res = await tools.materialTools.createMaterial(name, sanitizedPath || '/Game/Materials');
|
|
59
229
|
return cleanObject(res);
|
|
60
230
|
}
|
|
61
231
|
default:
|
|
@@ -64,41 +234,72 @@ switch (args.action) {
|
|
|
64
234
|
|
|
65
235
|
// 2. ACTOR CONTROL
|
|
66
236
|
case 'control_actor':
|
|
67
|
-
|
|
68
|
-
if (!args.action) {
|
|
69
|
-
throw new Error('Missing required parameter: action');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
switch (args.action) {
|
|
237
|
+
switch (requireAction(args)) {
|
|
73
238
|
case 'spawn': {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
239
|
+
await elicitMissingPrimitiveArgs(
|
|
240
|
+
tools,
|
|
241
|
+
args,
|
|
242
|
+
'Provide the spawn parameters for control_actor.spawn',
|
|
243
|
+
{
|
|
244
|
+
classPath: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
title: 'Actor Class or Asset Path',
|
|
247
|
+
description: 'Class name (e.g., StaticMeshActor) or asset path (e.g., /Engine/BasicShapes/Cube) to spawn'
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
const classPathInput = typeof args.classPath === 'string' ? args.classPath.trim() : args.classPath;
|
|
252
|
+
const classPath = requireNonEmptyString(classPathInput, 'classPath', 'Invalid classPath: must be a non-empty string');
|
|
253
|
+
const actorNameInput = typeof args.actorName === 'string' && args.actorName.trim() !== ''
|
|
254
|
+
? args.actorName
|
|
255
|
+
: (typeof args.name === 'string' ? args.name : undefined);
|
|
77
256
|
const res = await tools.actorTools.spawn({
|
|
78
|
-
classPath
|
|
257
|
+
classPath,
|
|
79
258
|
location: args.location,
|
|
80
|
-
rotation: args.rotation
|
|
259
|
+
rotation: args.rotation,
|
|
260
|
+
actorName: actorNameInput
|
|
81
261
|
});
|
|
82
262
|
return cleanObject(res);
|
|
83
263
|
}
|
|
84
264
|
case 'delete': {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
265
|
+
await elicitMissingPrimitiveArgs(
|
|
266
|
+
tools,
|
|
267
|
+
args,
|
|
268
|
+
'Which actor should control_actor.delete remove?',
|
|
269
|
+
{
|
|
270
|
+
actorName: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
title: 'Actor Name',
|
|
273
|
+
description: 'Exact label of the actor to delete'
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
const actorNameArg = typeof args.actorName === 'string' && args.actorName.trim() !== ''
|
|
278
|
+
? args.actorName
|
|
279
|
+
: (typeof args.name === 'string' ? args.name : undefined);
|
|
280
|
+
const actorName = requireNonEmptyString(actorNameArg, 'actorName', 'Invalid actorName');
|
|
281
|
+
const res = await tools.bridge.executeEditorFunction('DELETE_ACTOR', { actor_name: actorName });
|
|
89
282
|
return cleanObject(res);
|
|
90
283
|
}
|
|
91
284
|
case 'apply_force': {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
285
|
+
await elicitMissingPrimitiveArgs(
|
|
286
|
+
tools,
|
|
287
|
+
args,
|
|
288
|
+
'Provide the target actor for control_actor.apply_force',
|
|
289
|
+
{
|
|
290
|
+
actorName: {
|
|
291
|
+
type: 'string',
|
|
292
|
+
title: 'Actor Name',
|
|
293
|
+
description: 'Physics-enabled actor that should receive the force'
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
const actorName = requireNonEmptyString(args.actorName, 'actorName', 'Invalid actorName');
|
|
298
|
+
const vector = requireVector3Components(args.force, 'Invalid force: must have numeric x,y,z');
|
|
98
299
|
const res = await tools.physicsTools.applyForce({
|
|
99
|
-
actorName
|
|
300
|
+
actorName,
|
|
100
301
|
forceType: 'Force',
|
|
101
|
-
vector
|
|
302
|
+
vector
|
|
102
303
|
});
|
|
103
304
|
return cleanObject(res);
|
|
104
305
|
}
|
|
@@ -108,12 +309,7 @@ switch (args.action) {
|
|
|
108
309
|
|
|
109
310
|
// 3. EDITOR CONTROL
|
|
110
311
|
case 'control_editor':
|
|
111
|
-
|
|
112
|
-
if (!args.action) {
|
|
113
|
-
throw new Error('Missing required parameter: action');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
switch (args.action) {
|
|
312
|
+
switch (requireAction(args)) {
|
|
117
313
|
case 'play': {
|
|
118
314
|
const res = await tools.editorTools.playInEditor();
|
|
119
315
|
return cleanObject(res);
|
|
@@ -127,11 +323,9 @@ switch (args.action) {
|
|
|
127
323
|
return cleanObject(res);
|
|
128
324
|
}
|
|
129
325
|
case 'set_game_speed': {
|
|
130
|
-
|
|
131
|
-
throw new Error('Invalid speed: must be a positive number');
|
|
132
|
-
}
|
|
326
|
+
const speed = requirePositiveNumber(args.speed, 'speed', 'Invalid speed: must be a positive number');
|
|
133
327
|
// Use console command via bridge
|
|
134
|
-
const res = await tools.bridge.executeConsoleCommand(`slomo ${
|
|
328
|
+
const res = await tools.bridge.executeConsoleCommand(`slomo ${speed}`);
|
|
135
329
|
return cleanObject(res);
|
|
136
330
|
}
|
|
137
331
|
case 'eject': {
|
|
@@ -147,8 +341,20 @@ switch (args.action) {
|
|
|
147
341
|
return cleanObject(res);
|
|
148
342
|
}
|
|
149
343
|
case 'set_view_mode': {
|
|
150
|
-
|
|
151
|
-
|
|
344
|
+
await elicitMissingPrimitiveArgs(
|
|
345
|
+
tools,
|
|
346
|
+
args,
|
|
347
|
+
'Provide the view mode for control_editor.set_view_mode',
|
|
348
|
+
{
|
|
349
|
+
viewMode: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
title: 'View Mode',
|
|
352
|
+
description: 'Viewport view mode (e.g., Lit, Unlit, Wireframe)'
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
const viewMode = requireNonEmptyString(args.viewMode, 'viewMode', 'Missing required parameter: viewMode');
|
|
357
|
+
const res = await tools.bridge.setSafeViewMode(viewMode);
|
|
152
358
|
return cleanObject(res);
|
|
153
359
|
}
|
|
154
360
|
default:
|
|
@@ -157,11 +363,22 @@ switch (args.action) {
|
|
|
157
363
|
|
|
158
364
|
// 4. LEVEL MANAGER
|
|
159
365
|
case 'manage_level':
|
|
160
|
-
|
|
161
|
-
switch (args.action) {
|
|
366
|
+
switch (requireAction(args)) {
|
|
162
367
|
case 'load': {
|
|
163
|
-
|
|
164
|
-
|
|
368
|
+
await elicitMissingPrimitiveArgs(
|
|
369
|
+
tools,
|
|
370
|
+
args,
|
|
371
|
+
'Select the level to load for manage_level.load',
|
|
372
|
+
{
|
|
373
|
+
levelPath: {
|
|
374
|
+
type: 'string',
|
|
375
|
+
title: 'Level Path',
|
|
376
|
+
description: 'Content path of the level asset to load (e.g., /Game/Maps/MyLevel)'
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
const levelPath = requireNonEmptyString(args.levelPath, 'levelPath', 'Missing required parameter: levelPath');
|
|
381
|
+
const res = await tools.levelTools.loadLevel({ levelPath, streaming: !!args.streaming });
|
|
165
382
|
return cleanObject(res);
|
|
166
383
|
}
|
|
167
384
|
case 'save': {
|
|
@@ -169,19 +386,136 @@ case 'manage_level':
|
|
|
169
386
|
return cleanObject(res);
|
|
170
387
|
}
|
|
171
388
|
case 'stream': {
|
|
172
|
-
|
|
173
|
-
|
|
389
|
+
await elicitMissingPrimitiveArgs(
|
|
390
|
+
tools,
|
|
391
|
+
args,
|
|
392
|
+
'Provide the streaming level name for manage_level.stream',
|
|
393
|
+
{
|
|
394
|
+
levelName: {
|
|
395
|
+
type: 'string',
|
|
396
|
+
title: 'Level Name',
|
|
397
|
+
description: 'Streaming level name to toggle'
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
const levelName = requireNonEmptyString(args.levelName, 'levelName', 'Missing required parameter: levelName');
|
|
402
|
+
const res = await tools.levelTools.streamLevel({ levelName, shouldBeLoaded: !!args.shouldBeLoaded, shouldBeVisible: !!args.shouldBeVisible });
|
|
174
403
|
return cleanObject(res);
|
|
175
404
|
}
|
|
176
405
|
case 'create_light': {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
406
|
+
await elicitMissingPrimitiveArgs(
|
|
407
|
+
tools,
|
|
408
|
+
args,
|
|
409
|
+
'Provide the light details for manage_level.create_light',
|
|
410
|
+
{
|
|
411
|
+
lightType: {
|
|
412
|
+
type: 'string',
|
|
413
|
+
title: 'Light Type',
|
|
414
|
+
description: 'Directional, Point, Spot, Rect, or Sky'
|
|
415
|
+
},
|
|
416
|
+
name: {
|
|
417
|
+
type: 'string',
|
|
418
|
+
title: 'Light Name',
|
|
419
|
+
description: 'Name for the new light actor'
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
const lightType = requireNonEmptyString(args.lightType, 'lightType', 'Missing required parameter: lightType');
|
|
424
|
+
const name = requireNonEmptyString(args.name, 'name', 'Invalid name');
|
|
425
|
+
const typeKey = lightType.toLowerCase();
|
|
426
|
+
const toVector = (value: any, fallback: [number, number, number]): [number, number, number] => {
|
|
427
|
+
if (Array.isArray(value) && value.length === 3) {
|
|
428
|
+
return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
|
|
429
|
+
}
|
|
430
|
+
if (value && typeof value === 'object') {
|
|
431
|
+
return [Number(value.x) || 0, Number(value.y) || 0, Number(value.z) || 0];
|
|
432
|
+
}
|
|
433
|
+
return fallback;
|
|
434
|
+
};
|
|
435
|
+
const toRotator = (value: any, fallback: [number, number, number]): [number, number, number] => {
|
|
436
|
+
if (Array.isArray(value) && value.length === 3) {
|
|
437
|
+
return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
|
|
438
|
+
}
|
|
439
|
+
if (value && typeof value === 'object') {
|
|
440
|
+
return [Number(value.pitch) || 0, Number(value.yaw) || 0, Number(value.roll) || 0];
|
|
441
|
+
}
|
|
442
|
+
return fallback;
|
|
443
|
+
};
|
|
444
|
+
const toColor = (value: any): [number, number, number] | undefined => {
|
|
445
|
+
if (Array.isArray(value) && value.length === 3) {
|
|
446
|
+
return [Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0];
|
|
447
|
+
}
|
|
448
|
+
if (value && typeof value === 'object') {
|
|
449
|
+
return [Number(value.r) || 0, Number(value.g) || 0, Number(value.b) || 0];
|
|
450
|
+
}
|
|
451
|
+
return undefined;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const location = toVector(args.location, [0, 0, typeKey === 'directional' ? 500 : 0]);
|
|
455
|
+
const rotation = toRotator(args.rotation, [0, 0, 0]);
|
|
456
|
+
const color = toColor(args.color);
|
|
457
|
+
const castShadows = typeof args.castShadows === 'boolean' ? args.castShadows : undefined;
|
|
458
|
+
|
|
459
|
+
if (typeKey === 'directional') {
|
|
460
|
+
return cleanObject(await tools.lightingTools.createDirectionalLight({
|
|
461
|
+
name,
|
|
462
|
+
intensity: args.intensity,
|
|
463
|
+
color,
|
|
464
|
+
rotation,
|
|
465
|
+
castShadows,
|
|
466
|
+
temperature: args.temperature
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
if (typeKey === 'point') {
|
|
470
|
+
return cleanObject(await tools.lightingTools.createPointLight({
|
|
471
|
+
name,
|
|
472
|
+
location,
|
|
473
|
+
intensity: args.intensity,
|
|
474
|
+
radius: args.radius,
|
|
475
|
+
color,
|
|
476
|
+
falloffExponent: args.falloffExponent,
|
|
477
|
+
castShadows
|
|
478
|
+
}));
|
|
479
|
+
}
|
|
480
|
+
if (typeKey === 'spot') {
|
|
481
|
+
const innerCone = typeof args.innerCone === 'number' ? args.innerCone : undefined;
|
|
482
|
+
const outerCone = typeof args.outerCone === 'number' ? args.outerCone : undefined;
|
|
483
|
+
if (innerCone !== undefined && outerCone !== undefined && innerCone >= outerCone) {
|
|
484
|
+
throw new Error('innerCone must be less than outerCone');
|
|
485
|
+
}
|
|
486
|
+
return cleanObject(await tools.lightingTools.createSpotLight({
|
|
487
|
+
name,
|
|
488
|
+
location,
|
|
489
|
+
rotation,
|
|
490
|
+
intensity: args.intensity,
|
|
491
|
+
innerCone: args.innerCone,
|
|
492
|
+
outerCone: args.outerCone,
|
|
493
|
+
radius: args.radius,
|
|
494
|
+
color,
|
|
495
|
+
castShadows
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
if (typeKey === 'rect') {
|
|
499
|
+
return cleanObject(await tools.lightingTools.createRectLight({
|
|
500
|
+
name,
|
|
501
|
+
location,
|
|
502
|
+
rotation,
|
|
503
|
+
intensity: args.intensity,
|
|
504
|
+
width: args.width,
|
|
505
|
+
height: args.height,
|
|
506
|
+
color
|
|
507
|
+
}));
|
|
508
|
+
}
|
|
509
|
+
if (typeKey === 'sky' || typeKey === 'skylight') {
|
|
510
|
+
return cleanObject(await tools.lightingTools.createSkyLight({
|
|
511
|
+
name,
|
|
512
|
+
sourceType: args.sourceType,
|
|
513
|
+
cubemapPath: args.cubemapPath,
|
|
514
|
+
intensity: args.intensity,
|
|
515
|
+
recapture: args.recapture
|
|
516
|
+
}));
|
|
517
|
+
}
|
|
518
|
+
throw new Error(`Unknown light type: ${lightType}`);
|
|
185
519
|
}
|
|
186
520
|
case 'build_lighting': {
|
|
187
521
|
const res = await tools.lightingTools.buildLighting({ quality: args.quality || 'High', buildReflectionCaptures: true });
|
|
@@ -193,29 +527,75 @@ case 'manage_level':
|
|
|
193
527
|
|
|
194
528
|
// 5. ANIMATION & PHYSICS
|
|
195
529
|
case 'animation_physics':
|
|
196
|
-
|
|
197
|
-
if (!args.action) {
|
|
198
|
-
throw new Error('Missing required parameter: action');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
switch (args.action) {
|
|
530
|
+
switch (requireAction(args)) {
|
|
202
531
|
case 'create_animation_bp': {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
532
|
+
await elicitMissingPrimitiveArgs(
|
|
533
|
+
tools,
|
|
534
|
+
args,
|
|
535
|
+
'Provide details for animation_physics.create_animation_bp',
|
|
536
|
+
{
|
|
537
|
+
name: {
|
|
538
|
+
type: 'string',
|
|
539
|
+
title: 'Blueprint Name',
|
|
540
|
+
description: 'Name of the Animation Blueprint to create'
|
|
541
|
+
},
|
|
542
|
+
skeletonPath: {
|
|
543
|
+
type: 'string',
|
|
544
|
+
title: 'Skeleton Path',
|
|
545
|
+
description: 'Content path of the skeleton asset to bind'
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
const name = requireNonEmptyString(args.name, 'name', 'Invalid name');
|
|
550
|
+
const skeletonPath = requireNonEmptyString(args.skeletonPath, 'skeletonPath', 'Invalid skeletonPath');
|
|
551
|
+
const res = await tools.animationTools.createAnimationBlueprint({ name, skeletonPath, savePath: args.savePath });
|
|
206
552
|
return cleanObject(res);
|
|
207
553
|
}
|
|
208
554
|
case 'play_montage': {
|
|
209
|
-
|
|
555
|
+
await elicitMissingPrimitiveArgs(
|
|
556
|
+
tools,
|
|
557
|
+
args,
|
|
558
|
+
'Provide playback details for animation_physics.play_montage',
|
|
559
|
+
{
|
|
560
|
+
actorName: {
|
|
561
|
+
type: 'string',
|
|
562
|
+
title: 'Actor Name',
|
|
563
|
+
description: 'Actor that should play the montage'
|
|
564
|
+
},
|
|
565
|
+
montagePath: {
|
|
566
|
+
type: 'string',
|
|
567
|
+
title: 'Montage Path',
|
|
568
|
+
description: 'Montage or animation asset path to play'
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
const actorName = requireNonEmptyString(args.actorName, 'actorName', 'Invalid actorName');
|
|
210
573
|
const montagePath = args.montagePath || args.animationPath;
|
|
211
|
-
|
|
212
|
-
const res = await tools.animationTools.playAnimation({ actorName
|
|
574
|
+
const validatedMontage = requireNonEmptyString(montagePath, 'montagePath', 'Invalid montagePath');
|
|
575
|
+
const res = await tools.animationTools.playAnimation({ actorName, animationType: 'Montage', animationPath: validatedMontage, playRate: args.playRate });
|
|
213
576
|
return cleanObject(res);
|
|
214
577
|
}
|
|
215
578
|
case 'setup_ragdoll': {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
579
|
+
await elicitMissingPrimitiveArgs(
|
|
580
|
+
tools,
|
|
581
|
+
args,
|
|
582
|
+
'Provide setup details for animation_physics.setup_ragdoll',
|
|
583
|
+
{
|
|
584
|
+
skeletonPath: {
|
|
585
|
+
type: 'string',
|
|
586
|
+
title: 'Skeleton Path',
|
|
587
|
+
description: 'Content path for the skeleton asset'
|
|
588
|
+
},
|
|
589
|
+
physicsAssetName: {
|
|
590
|
+
type: 'string',
|
|
591
|
+
title: 'Physics Asset Name',
|
|
592
|
+
description: 'Name of the physics asset to apply'
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
);
|
|
596
|
+
const skeletonPath = requireNonEmptyString(args.skeletonPath, 'skeletonPath', 'Invalid skeletonPath');
|
|
597
|
+
const physicsAssetName = requireNonEmptyString(args.physicsAssetName, 'physicsAssetName', 'Invalid physicsAssetName');
|
|
598
|
+
const res = await tools.physicsTools.setupRagdoll({ skeletonPath, physicsAssetName, blendWeight: args.blendWeight, savePath: args.savePath });
|
|
219
599
|
return cleanObject(res);
|
|
220
600
|
}
|
|
221
601
|
default:
|
|
@@ -224,20 +604,61 @@ case 'animation_physics':
|
|
|
224
604
|
|
|
225
605
|
// 6. EFFECTS SYSTEM
|
|
226
606
|
case 'create_effect':
|
|
227
|
-
switch (args
|
|
607
|
+
switch (requireAction(args)) {
|
|
228
608
|
case 'particle': {
|
|
609
|
+
await elicitMissingPrimitiveArgs(
|
|
610
|
+
tools,
|
|
611
|
+
args,
|
|
612
|
+
'Provide the particle effect details for create_effect.particle',
|
|
613
|
+
{
|
|
614
|
+
effectType: {
|
|
615
|
+
type: 'string',
|
|
616
|
+
title: 'Effect Type',
|
|
617
|
+
description: 'Preset effect type to spawn (e.g., Fire, Smoke)'
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
);
|
|
229
621
|
const res = await tools.niagaraTools.createEffect({ effectType: args.effectType, name: args.name, location: args.location, scale: args.scale, customParameters: args.customParameters });
|
|
230
622
|
return cleanObject(res);
|
|
231
623
|
}
|
|
232
624
|
case 'niagara': {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
625
|
+
await elicitMissingPrimitiveArgs(
|
|
626
|
+
tools,
|
|
627
|
+
args,
|
|
628
|
+
'Provide the Niagara system path for create_effect.niagara',
|
|
629
|
+
{
|
|
630
|
+
systemPath: {
|
|
631
|
+
type: 'string',
|
|
632
|
+
title: 'Niagara System Path',
|
|
633
|
+
description: 'Asset path of the Niagara system to spawn'
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
const systemPath = requireNonEmptyString(args.systemPath, 'systemPath', 'Invalid systemPath');
|
|
638
|
+
const verifyResult = await tools.bridge.executePythonWithResult(`
|
|
639
|
+
import unreal, json
|
|
640
|
+
path = r"${systemPath}"
|
|
641
|
+
exists = unreal.EditorAssetLibrary.does_asset_exist(path)
|
|
642
|
+
print('RESULT:' + json.dumps({'success': exists, 'exists': exists, 'path': path}))
|
|
643
|
+
`.trim());
|
|
644
|
+
if (!verifyResult?.exists) {
|
|
645
|
+
return cleanObject({ success: false, error: `Niagara system not found at ${systemPath}` });
|
|
646
|
+
}
|
|
647
|
+
const loc = Array.isArray(args.location)
|
|
648
|
+
? { x: args.location[0], y: args.location[1], z: args.location[2] }
|
|
649
|
+
: args.location || { x: 0, y: 0, z: 0 };
|
|
650
|
+
const res = await tools.niagaraTools.spawnEffect({
|
|
651
|
+
systemPath,
|
|
652
|
+
location: [loc.x ?? 0, loc.y ?? 0, loc.z ?? 0],
|
|
653
|
+
rotation: Array.isArray(args.rotation) ? args.rotation : undefined,
|
|
654
|
+
scale: args.scale
|
|
655
|
+
});
|
|
237
656
|
return cleanObject(res);
|
|
238
657
|
}
|
|
239
658
|
case 'debug_shape': {
|
|
240
|
-
const
|
|
659
|
+
const shapeInput = args.shape ?? 'Sphere';
|
|
660
|
+
const shape = String(shapeInput).trim().toLowerCase();
|
|
661
|
+
const originalShapeLabel = String(shapeInput).trim() || 'shape';
|
|
241
662
|
const loc = args.location || { x: 0, y: 0, z: 0 };
|
|
242
663
|
const size = args.size || 100;
|
|
243
664
|
const color = args.color || [255, 0, 0, 255];
|
|
@@ -264,7 +685,7 @@ case 'animation_physics':
|
|
|
264
685
|
return cleanObject(await tools.debugTools.drawDebugString({ location: [loc.x, loc.y, loc.z], text, color, duration }));
|
|
265
686
|
}
|
|
266
687
|
// Default fallback
|
|
267
|
-
return cleanObject(
|
|
688
|
+
return cleanObject({ success: false, error: `Unsupported debug shape: ${originalShapeLabel}` });
|
|
268
689
|
}
|
|
269
690
|
default:
|
|
270
691
|
throw new Error(`Unknown effect action: ${args.action}`);
|
|
@@ -272,12 +693,56 @@ case 'animation_physics':
|
|
|
272
693
|
|
|
273
694
|
// 7. BLUEPRINT MANAGER
|
|
274
695
|
case 'manage_blueprint':
|
|
275
|
-
switch (args
|
|
696
|
+
switch (requireAction(args)) {
|
|
276
697
|
case 'create': {
|
|
277
|
-
|
|
698
|
+
await elicitMissingPrimitiveArgs(
|
|
699
|
+
tools,
|
|
700
|
+
args,
|
|
701
|
+
'Provide details for manage_blueprint.create',
|
|
702
|
+
{
|
|
703
|
+
name: {
|
|
704
|
+
type: 'string',
|
|
705
|
+
title: 'Blueprint Name',
|
|
706
|
+
description: 'Name for the new Blueprint asset'
|
|
707
|
+
},
|
|
708
|
+
blueprintType: {
|
|
709
|
+
type: 'string',
|
|
710
|
+
title: 'Blueprint Type',
|
|
711
|
+
description: 'Base type such as Actor, Pawn, Character, etc.'
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
const res = await tools.blueprintTools.createBlueprint({
|
|
716
|
+
name: args.name,
|
|
717
|
+
blueprintType: args.blueprintType || 'Actor',
|
|
718
|
+
savePath: args.savePath,
|
|
719
|
+
parentClass: args.parentClass
|
|
720
|
+
});
|
|
278
721
|
return cleanObject(res);
|
|
279
722
|
}
|
|
280
723
|
case 'add_component': {
|
|
724
|
+
await elicitMissingPrimitiveArgs(
|
|
725
|
+
tools,
|
|
726
|
+
args,
|
|
727
|
+
'Provide details for manage_blueprint.add_component',
|
|
728
|
+
{
|
|
729
|
+
name: {
|
|
730
|
+
type: 'string',
|
|
731
|
+
title: 'Blueprint Name',
|
|
732
|
+
description: 'Blueprint asset to modify'
|
|
733
|
+
},
|
|
734
|
+
componentType: {
|
|
735
|
+
type: 'string',
|
|
736
|
+
title: 'Component Type',
|
|
737
|
+
description: 'Component class to add (e.g., StaticMeshComponent)'
|
|
738
|
+
},
|
|
739
|
+
componentName: {
|
|
740
|
+
type: 'string',
|
|
741
|
+
title: 'Component Name',
|
|
742
|
+
description: 'Name for the new component'
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
);
|
|
281
746
|
const res = await tools.blueprintTools.addComponent({ blueprintName: args.name, componentType: args.componentType, componentName: args.componentName });
|
|
282
747
|
return cleanObject(res);
|
|
283
748
|
}
|
|
@@ -287,7 +752,7 @@ case 'animation_physics':
|
|
|
287
752
|
|
|
288
753
|
// 8. ENVIRONMENT BUILDER
|
|
289
754
|
case 'build_environment':
|
|
290
|
-
switch (args
|
|
755
|
+
switch (requireAction(args)) {
|
|
291
756
|
case 'create_landscape': {
|
|
292
757
|
const res = await tools.landscapeTools.createLandscape({ name: args.name, sizeX: args.sizeX, sizeY: args.sizeY, materialPath: args.materialPath });
|
|
293
758
|
return cleanObject(res);
|
|
@@ -359,8 +824,7 @@ case 'animation_physics':
|
|
|
359
824
|
|
|
360
825
|
// 9. SYSTEM CONTROL
|
|
361
826
|
case 'system_control':
|
|
362
|
-
|
|
363
|
-
switch (args.action) {
|
|
827
|
+
switch (requireAction(args)) {
|
|
364
828
|
case 'profile': {
|
|
365
829
|
const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
|
|
366
830
|
return cleanObject(res);
|
|
@@ -374,16 +838,48 @@ case 'system_control':
|
|
|
374
838
|
return cleanObject(res);
|
|
375
839
|
}
|
|
376
840
|
case 'play_sound': {
|
|
841
|
+
await elicitMissingPrimitiveArgs(
|
|
842
|
+
tools,
|
|
843
|
+
args,
|
|
844
|
+
'Provide the audio asset for system_control.play_sound',
|
|
845
|
+
{
|
|
846
|
+
soundPath: {
|
|
847
|
+
type: 'string',
|
|
848
|
+
title: 'Sound Asset Path',
|
|
849
|
+
description: 'Asset path of the sound to play'
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
const soundPath = requireNonEmptyString(args.soundPath, 'soundPath', 'Missing required parameter: soundPath');
|
|
377
854
|
if (args.location && typeof args.location === 'object') {
|
|
378
855
|
const loc = [args.location.x || 0, args.location.y || 0, args.location.z || 0];
|
|
379
|
-
const res = await tools.audioTools.playSoundAtLocation({ soundPath
|
|
856
|
+
const res = await tools.audioTools.playSoundAtLocation({ soundPath, location: loc as [number, number, number], volume: args.volume, pitch: args.pitch, startTime: args.startTime });
|
|
380
857
|
return cleanObject(res);
|
|
381
858
|
}
|
|
382
|
-
const res = await tools.audioTools.playSound2D({ soundPath
|
|
859
|
+
const res = await tools.audioTools.playSound2D({ soundPath, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
|
|
383
860
|
return cleanObject(res);
|
|
384
861
|
}
|
|
385
862
|
case 'create_widget': {
|
|
386
|
-
|
|
863
|
+
await elicitMissingPrimitiveArgs(
|
|
864
|
+
tools,
|
|
865
|
+
args,
|
|
866
|
+
'Provide details for system_control.create_widget',
|
|
867
|
+
{
|
|
868
|
+
widgetName: {
|
|
869
|
+
type: 'string',
|
|
870
|
+
title: 'Widget Name',
|
|
871
|
+
description: 'Name for the new UI widget asset'
|
|
872
|
+
},
|
|
873
|
+
widgetType: {
|
|
874
|
+
type: 'string',
|
|
875
|
+
title: 'Widget Type',
|
|
876
|
+
description: 'Widget type such as HUD, Menu, Overlay, etc.'
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
);
|
|
880
|
+
const widgetName = requireNonEmptyString(args.widgetName ?? args.name, 'widgetName', 'Missing required parameter: widgetName');
|
|
881
|
+
const widgetType = requireNonEmptyString(args.widgetType, 'widgetType', 'Missing required parameter: widgetType');
|
|
882
|
+
const res = await tools.uiTools.createWidget({ name: widgetName, type: widgetType as any, savePath: args.savePath });
|
|
387
883
|
return cleanObject(res);
|
|
388
884
|
}
|
|
389
885
|
case 'show_widget': {
|
|
@@ -418,8 +914,22 @@ case 'console_command':
|
|
|
418
914
|
return { success: false, error: 'Command blocked for safety' } as any;
|
|
419
915
|
}
|
|
420
916
|
try {
|
|
421
|
-
const
|
|
422
|
-
|
|
917
|
+
const raw = await tools.bridge.executeConsoleCommand(cmd);
|
|
918
|
+
const summary = tools.bridge.summarizeConsoleCommand(cmd, raw);
|
|
919
|
+
const output = summary.output || '';
|
|
920
|
+
const looksInvalid = /unknown|invalid/i.test(output);
|
|
921
|
+
return cleanObject({
|
|
922
|
+
success: summary.returnValue !== false && !looksInvalid,
|
|
923
|
+
command: summary.command,
|
|
924
|
+
output: output || undefined,
|
|
925
|
+
logLines: summary.logLines?.length ? summary.logLines : undefined,
|
|
926
|
+
returnValue: summary.returnValue,
|
|
927
|
+
message: !looksInvalid
|
|
928
|
+
? (output || 'Command executed')
|
|
929
|
+
: undefined,
|
|
930
|
+
error: looksInvalid ? output : undefined,
|
|
931
|
+
raw: summary.raw
|
|
932
|
+
});
|
|
423
933
|
} catch (e: any) {
|
|
424
934
|
return cleanObject({ success: false, command: cmd, error: e?.message || String(e) });
|
|
425
935
|
}
|
|
@@ -427,12 +937,10 @@ case 'console_command':
|
|
|
427
937
|
|
|
428
938
|
// 11. REMOTE CONTROL PRESETS - Direct implementation
|
|
429
939
|
case 'manage_rc':
|
|
430
|
-
if (!args.action) throw new Error('Missing required parameter: action');
|
|
431
|
-
|
|
432
940
|
// Handle RC operations directly through RcTools
|
|
433
941
|
let rcResult: any;
|
|
434
|
-
|
|
435
|
-
switch (
|
|
942
|
+
const rcAction = requireAction(args);
|
|
943
|
+
switch (rcAction) {
|
|
436
944
|
// Support both 'create_preset' and 'create' for compatibility
|
|
437
945
|
case 'create_preset':
|
|
438
946
|
case 'create':
|
|
@@ -459,8 +967,10 @@ case 'console_command':
|
|
|
459
967
|
break;
|
|
460
968
|
|
|
461
969
|
case 'delete':
|
|
462
|
-
|
|
463
|
-
|
|
970
|
+
case 'delete_preset':
|
|
971
|
+
const presetIdentifier = args.presetId || args.presetPath;
|
|
972
|
+
if (!presetIdentifier) throw new Error('Missing required parameter: presetId');
|
|
973
|
+
rcResult = await tools.rcTools.deletePreset(presetIdentifier);
|
|
464
974
|
if (rcResult.success) {
|
|
465
975
|
rcResult.message = 'Preset deleted successfully';
|
|
466
976
|
}
|
|
@@ -557,7 +1067,7 @@ case 'console_command':
|
|
|
557
1067
|
break;
|
|
558
1068
|
|
|
559
1069
|
default:
|
|
560
|
-
throw new Error(`Unknown RC action: ${
|
|
1070
|
+
throw new Error(`Unknown RC action: ${rcAction}. 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`);
|
|
561
1071
|
}
|
|
562
1072
|
|
|
563
1073
|
// Return result directly - MCP formatting will be handled by response validator
|
|
@@ -566,14 +1076,13 @@ case 'console_command':
|
|
|
566
1076
|
|
|
567
1077
|
// 12. SEQUENCER / CINEMATICS
|
|
568
1078
|
case 'manage_sequence':
|
|
569
|
-
if (!args.action) throw new Error('Missing required parameter: action');
|
|
570
|
-
|
|
571
1079
|
// Direct handling for sequence operations
|
|
572
1080
|
const seqResult = await (async () => {
|
|
573
1081
|
const sequenceTools = tools.sequenceTools;
|
|
574
1082
|
if (!sequenceTools) throw new Error('Sequence tools not available');
|
|
1083
|
+
const action = requireAction(args);
|
|
575
1084
|
|
|
576
|
-
switch (
|
|
1085
|
+
switch (action) {
|
|
577
1086
|
case 'create':
|
|
578
1087
|
return await sequenceTools.create({ name: args.name, path: args.path });
|
|
579
1088
|
case 'open':
|
|
@@ -613,7 +1122,7 @@ case 'console_command':
|
|
|
613
1122
|
if (args.speed === undefined) throw new Error('Missing required parameter: speed');
|
|
614
1123
|
return await sequenceTools.setPlaybackSpeed({ speed: args.speed });
|
|
615
1124
|
default:
|
|
616
|
-
throw new Error(`Unknown sequence action: ${
|
|
1125
|
+
throw new Error(`Unknown sequence action: ${action}`);
|
|
617
1126
|
}
|
|
618
1127
|
})();
|
|
619
1128
|
|
|
@@ -622,8 +1131,8 @@ case 'console_command':
|
|
|
622
1131
|
return cleanObject(seqResult);
|
|
623
1132
|
// 13. INTROSPECTION
|
|
624
1133
|
case 'inspect':
|
|
625
|
-
|
|
626
|
-
|
|
1134
|
+
const inspectAction = requireAction(args);
|
|
1135
|
+
switch (inspectAction) {
|
|
627
1136
|
case 'inspect_object': {
|
|
628
1137
|
const res = await tools.introspectionTools.inspectObject({ objectPath: args.objectPath, detailed: args.detailed });
|
|
629
1138
|
return cleanObject(res);
|
|
@@ -633,7 +1142,7 @@ case 'inspect':
|
|
|
633
1142
|
return cleanObject(res);
|
|
634
1143
|
}
|
|
635
1144
|
default:
|
|
636
|
-
throw new Error(`Unknown inspect action: ${
|
|
1145
|
+
throw new Error(`Unknown inspect action: ${inspectAction}`);
|
|
637
1146
|
}
|
|
638
1147
|
|
|
639
1148
|
|