unreal-engine-mcp-server 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-mcp.yml +1 -4
- package/.github/workflows/release-drafter.yml +2 -1
- package/CHANGELOG.md +38 -0
- package/dist/automation/bridge.d.ts +1 -2
- package/dist/automation/bridge.js +24 -23
- package/dist/automation/connection-manager.d.ts +1 -0
- package/dist/automation/connection-manager.js +10 -0
- package/dist/automation/message-handler.js +5 -4
- package/dist/automation/request-tracker.d.ts +4 -0
- package/dist/automation/request-tracker.js +11 -3
- package/dist/tools/actors.d.ts +19 -1
- package/dist/tools/actors.js +15 -5
- package/dist/tools/assets.js +1 -1
- package/dist/tools/blueprint.d.ts +12 -0
- package/dist/tools/blueprint.js +43 -14
- package/dist/tools/consolidated-tool-definitions.js +2 -1
- package/dist/tools/editor.js +3 -2
- package/dist/tools/handlers/actor-handlers.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.js +14 -8
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/sequence-handlers.js +24 -13
- package/dist/tools/introspection.d.ts +1 -1
- package/dist/tools/introspection.js +1 -1
- package/dist/tools/level.js +3 -3
- package/dist/tools/lighting.d.ts +54 -7
- package/dist/tools/lighting.js +4 -4
- package/dist/tools/materials.d.ts +1 -1
- package/dist/types/tool-types.d.ts +2 -0
- package/dist/unreal-bridge.js +4 -4
- package/dist/utils/command-validator.js +6 -5
- package/dist/utils/error-handler.d.ts +24 -2
- package/dist/utils/error-handler.js +58 -23
- package/dist/utils/normalize.d.ts +7 -4
- package/dist/utils/normalize.js +12 -10
- package/dist/utils/response-validator.js +88 -73
- package/dist/utils/unreal-command-queue.d.ts +2 -0
- package/dist/utils/unreal-command-queue.js +8 -1
- package/docs/handler-mapping.md +4 -2
- package/package.json +1 -1
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
- package/server.json +3 -3
- package/src/automation/bridge.ts +27 -25
- package/src/automation/connection-manager.ts +18 -0
- package/src/automation/message-handler.ts +33 -8
- package/src/automation/request-tracker.ts +39 -7
- package/src/server/tool-registry.ts +3 -3
- package/src/tools/actors.ts +44 -19
- package/src/tools/assets.ts +3 -3
- package/src/tools/blueprint.ts +115 -49
- package/src/tools/consolidated-tool-definitions.ts +2 -1
- package/src/tools/editor.ts +4 -3
- package/src/tools/handlers/actor-handlers.ts +14 -9
- package/src/tools/handlers/sequence-handlers.ts +86 -63
- package/src/tools/introspection.ts +7 -7
- package/src/tools/level.ts +6 -6
- package/src/tools/lighting.ts +19 -19
- package/src/tools/materials.ts +1 -1
- package/src/tools/sequence.ts +1 -1
- package/src/tools/ui.ts +1 -1
- package/src/types/tool-types.ts +4 -0
- package/src/unreal-bridge.ts +71 -26
- package/src/utils/command-validator.ts +46 -5
- package/src/utils/error-handler.ts +128 -45
- package/src/utils/normalize.ts +38 -16
- package/src/utils/response-validator.ts +103 -87
- package/src/utils/unreal-command-queue.ts +13 -1
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { cleanObject } from '../../utils/safe-json.js';
|
|
2
|
-
import { ITools } from '../../types/tool-interfaces.js';
|
|
2
|
+
import { ITools, StandardActionResponse } from '../../types/tool-interfaces.js';
|
|
3
3
|
import { executeAutomationRequest, requireNonEmptyString } from './common-handlers.js';
|
|
4
4
|
|
|
5
|
+
/** Extended response with common sequence fields */
|
|
6
|
+
interface SequenceActionResponse extends StandardActionResponse {
|
|
7
|
+
result?: {
|
|
8
|
+
sequencePath?: string;
|
|
9
|
+
results?: Array<{ success?: boolean; error?: string }>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
bindings?: Array<{ name?: string;[key: string]: unknown }>;
|
|
13
|
+
message?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
const managedSequences = new Set<string>();
|
|
6
17
|
const deletedSequences = new Set<string>();
|
|
7
18
|
|
|
@@ -22,37 +33,48 @@ function markSequenceDeleted(path: unknown) {
|
|
|
22
33
|
const norm = normalizeSequencePath(path);
|
|
23
34
|
if (!norm) return;
|
|
24
35
|
managedSequences.delete(norm);
|
|
25
|
-
deletedSequences.
|
|
36
|
+
deletedSequences.delete(norm);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Helper to safely get string from error/message */
|
|
40
|
+
function getErrorString(res: SequenceActionResponse | null | undefined): string {
|
|
41
|
+
if (!res) return '';
|
|
42
|
+
return typeof res.error === 'string' ? res.error : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getMessageString(res: SequenceActionResponse | null | undefined): string {
|
|
46
|
+
if (!res) return '';
|
|
47
|
+
return typeof res.message === 'string' ? res.message : '';
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
|
|
29
51
|
|
|
30
|
-
export async function handleSequenceTools(action: string, args:
|
|
52
|
+
export async function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools) {
|
|
31
53
|
const seqAction = String(action || '').trim();
|
|
32
54
|
switch (seqAction) {
|
|
33
55
|
case 'create': {
|
|
34
56
|
const name = requireNonEmptyString(args.name, 'name', 'Missing required parameter: name');
|
|
35
|
-
const res = await tools.sequenceTools.create({ name, path: args.path });
|
|
57
|
+
const res = await tools.sequenceTools.create({ name, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
36
58
|
|
|
37
59
|
let sequencePath: string | undefined;
|
|
38
|
-
if (res &&
|
|
39
|
-
sequencePath =
|
|
60
|
+
if (res && res.result && typeof res.result.sequencePath === 'string') {
|
|
61
|
+
sequencePath = res.result.sequencePath;
|
|
40
62
|
} else if (typeof args.path === 'string' && args.path.trim().length > 0) {
|
|
41
63
|
const basePath = args.path.trim().replace(/\/$/, '');
|
|
42
64
|
sequencePath = `${basePath}/${name}`;
|
|
43
65
|
}
|
|
44
|
-
if (sequencePath && res &&
|
|
66
|
+
if (sequencePath && res && res.success !== false) {
|
|
45
67
|
markSequenceCreated(sequencePath);
|
|
46
68
|
}
|
|
47
69
|
|
|
48
|
-
const errorCode =
|
|
49
|
-
const msgLower =
|
|
50
|
-
if (res &&
|
|
70
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
71
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
72
|
+
if (res && res.success === false && (errorCode === 'FACTORY_NOT_AVAILABLE' || msgLower.includes('ulevelsequencefactorynew not available'))) {
|
|
51
73
|
const path = sequencePath || (typeof args.path === 'string' ? args.path : undefined);
|
|
52
74
|
return cleanObject({
|
|
53
75
|
success: false,
|
|
54
76
|
error: 'FACTORY_NOT_AVAILABLE',
|
|
55
|
-
message:
|
|
77
|
+
message: res.message || 'Sequence creation failed: factory not available',
|
|
56
78
|
action: 'create',
|
|
57
79
|
name,
|
|
58
80
|
path,
|
|
@@ -69,7 +91,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
69
91
|
return cleanObject(res);
|
|
70
92
|
}
|
|
71
93
|
case 'add_camera': {
|
|
72
|
-
const res = await tools.sequenceTools.addCamera({ spawnable: args.spawnable, path: args.path });
|
|
94
|
+
const res = await tools.sequenceTools.addCamera({ spawnable: args.spawnable as boolean | undefined, path: args.path as string | undefined });
|
|
73
95
|
return cleanObject(res);
|
|
74
96
|
}
|
|
75
97
|
case 'add_actor': {
|
|
@@ -82,18 +104,18 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
82
104
|
subAction: 'add_actor'
|
|
83
105
|
};
|
|
84
106
|
|
|
85
|
-
const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
|
|
107
|
+
const res = await executeAutomationRequest(tools, 'manage_sequence', payload) as SequenceActionResponse;
|
|
86
108
|
|
|
87
|
-
const errorCode =
|
|
88
|
-
const msgLower =
|
|
109
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
110
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
89
111
|
|
|
90
|
-
if (res &&
|
|
112
|
+
if (res && res.success === false && path) {
|
|
91
113
|
const isInvalidSequence = errorCode === 'INVALID_SEQUENCE' || msgLower.includes('sequence_add_actor requires a sequence path') || msgLower.includes('sequence not found');
|
|
92
114
|
if (isInvalidSequence) {
|
|
93
115
|
return cleanObject({
|
|
94
116
|
success: false,
|
|
95
117
|
error: 'NOT_FOUND',
|
|
96
|
-
message:
|
|
118
|
+
message: res.message || 'Sequence not found',
|
|
97
119
|
action: 'add_actor',
|
|
98
120
|
path,
|
|
99
121
|
actorName
|
|
@@ -101,8 +123,8 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
|
|
104
|
-
const results = res &&
|
|
105
|
-
?
|
|
126
|
+
const results = res && res.result && Array.isArray(res.result.results)
|
|
127
|
+
? res.result.results
|
|
106
128
|
: undefined;
|
|
107
129
|
if (results && results.length) {
|
|
108
130
|
const failed = results.find((item) => item && item.success === false && typeof item.error === 'string');
|
|
@@ -124,24 +146,24 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
124
146
|
return cleanObject(res);
|
|
125
147
|
}
|
|
126
148
|
case 'add_actors': {
|
|
127
|
-
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames : [];
|
|
128
|
-
const res = await tools.sequenceTools.addActors({ actorNames, path: args.path });
|
|
129
|
-
const errorCode =
|
|
130
|
-
const msgLower =
|
|
131
|
-
if (actorNames.length === 0 && res &&
|
|
149
|
+
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames as string[] : [];
|
|
150
|
+
const res = await tools.sequenceTools.addActors({ actorNames, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
151
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
152
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
153
|
+
if (actorNames.length === 0 && res && res.success === false && errorCode === 'INVALID_ARGUMENT') {
|
|
132
154
|
return cleanObject({
|
|
133
155
|
success: false,
|
|
134
156
|
error: 'INVALID_ARGUMENT',
|
|
135
|
-
message:
|
|
157
|
+
message: res.message || 'Invalid argument: actorNames required',
|
|
136
158
|
action: 'add_actors',
|
|
137
159
|
actorNames
|
|
138
160
|
});
|
|
139
161
|
}
|
|
140
|
-
if (res &&
|
|
162
|
+
if (res && res.success === false && msgLower.includes('actor not found')) {
|
|
141
163
|
return cleanObject({
|
|
142
164
|
success: false,
|
|
143
165
|
error: 'NOT_FOUND',
|
|
144
|
-
message:
|
|
166
|
+
message: res.message || 'Actor not found',
|
|
145
167
|
action: 'add_actors',
|
|
146
168
|
actorNames
|
|
147
169
|
});
|
|
@@ -149,8 +171,8 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
149
171
|
return cleanObject(res);
|
|
150
172
|
}
|
|
151
173
|
case 'remove_actors': {
|
|
152
|
-
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames : [];
|
|
153
|
-
const res = await tools.sequenceTools.removeActors({ actorNames, path: args.path });
|
|
174
|
+
const actorNames: string[] = Array.isArray(args.actorNames) ? args.actorNames as string[] : [];
|
|
175
|
+
const res = await tools.sequenceTools.removeActors({ actorNames, path: args.path as string | undefined });
|
|
154
176
|
return cleanObject(res);
|
|
155
177
|
}
|
|
156
178
|
case 'get_bindings': {
|
|
@@ -164,7 +186,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
164
186
|
const property = typeof args.property === 'string' ? args.property : undefined;
|
|
165
187
|
const frame = typeof args.frame === 'number' ? args.frame : Number(args.frame);
|
|
166
188
|
|
|
167
|
-
const payload = {
|
|
189
|
+
const payload: Record<string, unknown> = {
|
|
168
190
|
...args,
|
|
169
191
|
path: path || args.path,
|
|
170
192
|
actorName,
|
|
@@ -185,16 +207,16 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
185
207
|
payload.value = { scale: args.value };
|
|
186
208
|
}
|
|
187
209
|
|
|
188
|
-
const res = await executeAutomationRequest(tools, 'manage_sequence', payload);
|
|
189
|
-
const errorCode =
|
|
190
|
-
const msgLower =
|
|
210
|
+
const res = await executeAutomationRequest(tools, 'manage_sequence', payload) as SequenceActionResponse;
|
|
211
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
212
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
191
213
|
|
|
192
214
|
// Keep explicit INVALID_ARGUMENT for missing frame as a real error
|
|
193
215
|
if (errorCode === 'INVALID_ARGUMENT' || msgLower.includes('frame number is required')) {
|
|
194
216
|
return cleanObject(res);
|
|
195
217
|
}
|
|
196
218
|
|
|
197
|
-
if (res &&
|
|
219
|
+
if (res && res.success === false) {
|
|
198
220
|
const isBindingIssue = errorCode === 'BINDING_NOT_FOUND' || msgLower.includes('binding not found');
|
|
199
221
|
const isUnsupported = errorCode === 'UNSUPPORTED_PROPERTY' || msgLower.includes('unsupported property') || msgLower.includes('invalid_sequence_type');
|
|
200
222
|
const isInvalidSeq = errorCode === 'INVALID_SEQUENCE' || msgLower.includes('sequence not found') || msgLower.includes('requires a sequence path');
|
|
@@ -203,7 +225,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
203
225
|
return cleanObject({
|
|
204
226
|
success: false,
|
|
205
227
|
error: 'NOT_FOUND',
|
|
206
|
-
message:
|
|
228
|
+
message: res.message || 'Sequence not found',
|
|
207
229
|
action: 'add_keyframe',
|
|
208
230
|
path,
|
|
209
231
|
actorName,
|
|
@@ -222,28 +244,28 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
222
244
|
}
|
|
223
245
|
case 'add_spawnable_from_class': {
|
|
224
246
|
const className = requireNonEmptyString(args.className, 'className', 'Missing required parameter: className');
|
|
225
|
-
const res = await tools.sequenceTools.addSpawnableFromClass({ className, path: args.path });
|
|
247
|
+
const res = await tools.sequenceTools.addSpawnableFromClass({ className, path: args.path as string | undefined });
|
|
226
248
|
return cleanObject(res);
|
|
227
249
|
}
|
|
228
250
|
case 'play': {
|
|
229
|
-
const res = await tools.sequenceTools.play({ path: args.path, startTime: args.startTime, loopMode: args.loopMode });
|
|
251
|
+
const res = await tools.sequenceTools.play({ path: args.path as string | undefined, startTime: args.startTime as number | undefined, loopMode: args.loopMode as 'once' | 'loop' | 'pingpong' | undefined });
|
|
230
252
|
return cleanObject(res);
|
|
231
253
|
}
|
|
232
254
|
case 'pause': {
|
|
233
|
-
const res = await tools.sequenceTools.pause({ path: args.path });
|
|
255
|
+
const res = await tools.sequenceTools.pause({ path: args.path as string | undefined });
|
|
234
256
|
return cleanObject(res);
|
|
235
257
|
}
|
|
236
258
|
case 'stop': {
|
|
237
|
-
const res = await tools.sequenceTools.stop({ path: args.path });
|
|
259
|
+
const res = await tools.sequenceTools.stop({ path: args.path as string | undefined });
|
|
238
260
|
return cleanObject(res);
|
|
239
261
|
}
|
|
240
262
|
case 'set_properties': {
|
|
241
263
|
const res = await tools.sequenceTools.setSequenceProperties({
|
|
242
|
-
path: args.path,
|
|
243
|
-
frameRate: args.frameRate,
|
|
244
|
-
lengthInFrames: args.lengthInFrames,
|
|
245
|
-
playbackStart: args.playbackStart,
|
|
246
|
-
playbackEnd: args.playbackEnd
|
|
264
|
+
path: args.path as string | undefined,
|
|
265
|
+
frameRate: args.frameRate as number | undefined,
|
|
266
|
+
lengthInFrames: args.lengthInFrames as number | undefined,
|
|
267
|
+
playbackStart: args.playbackStart as number | undefined,
|
|
268
|
+
playbackEnd: args.playbackEnd as number | undefined
|
|
247
269
|
});
|
|
248
270
|
return cleanObject(res);
|
|
249
271
|
}
|
|
@@ -258,31 +280,32 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
258
280
|
throw new Error('Invalid speed: must be a positive number');
|
|
259
281
|
}
|
|
260
282
|
// Try setting speed
|
|
261
|
-
let res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path });
|
|
283
|
+
let res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
262
284
|
|
|
263
285
|
// Fix: Auto-open if editor not open
|
|
264
|
-
const errorCode =
|
|
286
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
265
287
|
if ((!res || res.success === false) && errorCode === 'EDITOR_NOT_OPEN' && args.path) {
|
|
266
288
|
// Attempt to open the sequence
|
|
267
|
-
await tools.sequenceTools.open({ path: args.path });
|
|
289
|
+
await tools.sequenceTools.open({ path: args.path as string });
|
|
268
290
|
|
|
269
291
|
// Wait a short moment for editor to initialize on game thread
|
|
270
292
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
271
293
|
|
|
272
294
|
// Retry
|
|
273
|
-
res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path });
|
|
295
|
+
res = await tools.sequenceTools.setPlaybackSpeed({ speed, path: args.path as string | undefined }) as SequenceActionResponse;
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
return cleanObject(res);
|
|
277
299
|
}
|
|
278
300
|
case 'list': {
|
|
279
|
-
const res = await tools.sequenceTools.list({ path: args.path });
|
|
301
|
+
const res = await tools.sequenceTools.list({ path: args.path as string | undefined });
|
|
280
302
|
return cleanObject(res);
|
|
281
303
|
}
|
|
282
304
|
case 'duplicate': {
|
|
283
305
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
284
306
|
const destDir = requireNonEmptyString(args.destinationPath, 'destinationPath', 'Missing required parameter: destinationPath');
|
|
285
|
-
const
|
|
307
|
+
const defaultNewName = path.split('/').pop() || '';
|
|
308
|
+
const newName = requireNonEmptyString(args.newName || defaultNewName, 'newName', 'Missing required parameter: newName');
|
|
286
309
|
const baseDir = destDir.replace(/\/$/, '');
|
|
287
310
|
const destPath = `${baseDir}/${newName}`;
|
|
288
311
|
const res = await tools.sequenceTools.duplicate({ path, destinationPath: destPath });
|
|
@@ -291,15 +314,15 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
291
314
|
case 'rename': {
|
|
292
315
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
293
316
|
const newName = requireNonEmptyString(args.newName, 'newName', 'Missing required parameter: newName');
|
|
294
|
-
const res = await tools.sequenceTools.rename({ path, newName });
|
|
295
|
-
const errorCode =
|
|
296
|
-
const msgLower =
|
|
297
|
-
if (res &&
|
|
317
|
+
const res = await tools.sequenceTools.rename({ path, newName }) as SequenceActionResponse;
|
|
318
|
+
const errorCode = getErrorString(res).toUpperCase();
|
|
319
|
+
const msgLower = getMessageString(res).toLowerCase();
|
|
320
|
+
if (res && res.success === false && (errorCode === 'OPERATION_FAILED' || msgLower.includes('failed to rename sequence'))) {
|
|
298
321
|
// Return actual failure, not best-effort success - rename is a destructive operation
|
|
299
322
|
return cleanObject({
|
|
300
323
|
success: false,
|
|
301
324
|
error: 'OPERATION_FAILED',
|
|
302
|
-
message:
|
|
325
|
+
message: res.message || 'Failed to rename sequence',
|
|
303
326
|
action: 'rename',
|
|
304
327
|
path,
|
|
305
328
|
newName
|
|
@@ -309,20 +332,20 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
309
332
|
}
|
|
310
333
|
case 'delete': {
|
|
311
334
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
312
|
-
const res = await tools.sequenceTools.deleteSequence({ path });
|
|
335
|
+
const res = await tools.sequenceTools.deleteSequence({ path }) as SequenceActionResponse;
|
|
313
336
|
|
|
314
|
-
if (res &&
|
|
337
|
+
if (res && res.success !== false) {
|
|
315
338
|
markSequenceDeleted(path);
|
|
316
339
|
}
|
|
317
340
|
return cleanObject(res);
|
|
318
341
|
}
|
|
319
342
|
case 'get_metadata': {
|
|
320
|
-
const res = await tools.sequenceTools.getMetadata({ path: args.path });
|
|
343
|
+
const res = await tools.sequenceTools.getMetadata({ path: args.path as string });
|
|
321
344
|
return cleanObject(res);
|
|
322
345
|
}
|
|
323
346
|
case 'set_metadata': {
|
|
324
347
|
const path = requireNonEmptyString(args.path, 'path', 'Missing required parameter: path');
|
|
325
|
-
const metadata = (args.metadata && typeof args.metadata === 'object') ? args.metadata : {};
|
|
348
|
+
const metadata = (args.metadata && typeof args.metadata === 'object') ? args.metadata as Record<string, unknown> : {};
|
|
326
349
|
const res = await executeAutomationRequest(tools, 'set_metadata', { assetPath: path, metadata });
|
|
327
350
|
return cleanObject(res);
|
|
328
351
|
}
|
|
@@ -335,10 +358,10 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
335
358
|
|
|
336
359
|
// Fix: Check if actor is bound before adding track
|
|
337
360
|
if (actorName) {
|
|
338
|
-
const bindingsRes = await tools.sequenceTools.getBindings({ path });
|
|
361
|
+
const bindingsRes = await tools.sequenceTools.getBindings({ path }) as SequenceActionResponse;
|
|
339
362
|
if (bindingsRes && bindingsRes.success) {
|
|
340
|
-
const bindings =
|
|
341
|
-
const isBound = bindings.some((b
|
|
363
|
+
const bindings = bindingsRes.bindings || [];
|
|
364
|
+
const isBound = bindings.some((b) => b.name === actorName);
|
|
342
365
|
if (!isBound) {
|
|
343
366
|
return cleanObject({
|
|
344
367
|
success: false,
|
|
@@ -399,7 +422,7 @@ export async function handleSequenceTools(action: string, args: any, tools: IToo
|
|
|
399
422
|
if (!Number.isFinite(end)) throw new Error('Invalid end: must be a number');
|
|
400
423
|
|
|
401
424
|
const res = await tools.sequenceTools.setWorkRange({
|
|
402
|
-
path: args.path,
|
|
425
|
+
path: args.path as string | undefined,
|
|
403
426
|
start,
|
|
404
427
|
end
|
|
405
428
|
});
|
|
@@ -35,7 +35,7 @@ export interface PropertyInfo {
|
|
|
35
35
|
type: string;
|
|
36
36
|
value?: any;
|
|
37
37
|
flags?: string[];
|
|
38
|
-
metadata?: Record<string,
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
39
|
category?: string;
|
|
40
40
|
tooltip?: string;
|
|
41
41
|
description?: string;
|
|
@@ -141,7 +141,7 @@ export class IntrospectionTools {
|
|
|
141
141
|
return value;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
private isPlainObject(value: any): value is Record<string,
|
|
144
|
+
private isPlainObject(value: any): value is Record<string, unknown> {
|
|
145
145
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -200,7 +200,7 @@ export class IntrospectionTools {
|
|
|
200
200
|
const rawValue = entry.value ?? entry.currentValue ?? entry.defaultValue ?? entry.data ?? entry;
|
|
201
201
|
const value = this.convertPropertyValue(rawValue, type);
|
|
202
202
|
const flags: string[] | undefined = entry.flags ?? entry.attributes;
|
|
203
|
-
const metadata: Record<string,
|
|
203
|
+
const metadata: Record<string, unknown> | undefined = entry.metadata ?? entry.annotations;
|
|
204
204
|
const filtered = this.shouldFilterProperty(name, value, flags, detailed);
|
|
205
205
|
const dictionaryEntry = lookupPropertyMetadata(name);
|
|
206
206
|
const propertyInfo: PropertyInfo = {
|
|
@@ -220,12 +220,12 @@ export class IntrospectionTools {
|
|
|
220
220
|
return propertyInfo;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
private flattenPropertyMap(source: Record<string,
|
|
223
|
+
private flattenPropertyMap(source: Record<string, unknown>, prefix = '', detailed = false): PropertyInfo[] {
|
|
224
224
|
const properties: PropertyInfo[] = [];
|
|
225
225
|
for (const [rawKey, rawValue] of Object.entries(source)) {
|
|
226
226
|
const name = prefix ? `${prefix}.${rawKey}` : rawKey;
|
|
227
227
|
if (this.isLikelyPropertyDescriptor(rawValue)) {
|
|
228
|
-
const normalized = this.normalizePropertyEntry({ ...rawValue, name }, detailed);
|
|
228
|
+
const normalized = this.normalizePropertyEntry({ ...(rawValue as Record<string, unknown>), name }, detailed);
|
|
229
229
|
if (normalized) properties.push(normalized);
|
|
230
230
|
continue;
|
|
231
231
|
}
|
|
@@ -441,8 +441,8 @@ export class IntrospectionTools {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
return res;
|
|
444
|
-
} catch (err:
|
|
445
|
-
const errorMsg = err
|
|
444
|
+
} catch (err: unknown) {
|
|
445
|
+
const errorMsg = (err instanceof Error ? err.message : undefined) || String(err);
|
|
446
446
|
if (errorMsg.includes('404')) {
|
|
447
447
|
return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
|
|
448
448
|
}
|
package/src/tools/level.ts
CHANGED
|
@@ -50,12 +50,12 @@ export class LevelTools extends BaseTool implements ILevelTools {
|
|
|
50
50
|
// Security validation
|
|
51
51
|
try {
|
|
52
52
|
formatted = sanitizePath(formatted);
|
|
53
|
-
} catch (e:
|
|
53
|
+
} catch (e: unknown) {
|
|
54
54
|
// If sanitizePath fails, we should probably propagate that error,
|
|
55
55
|
// but normalizeLevelPath signature expects to return an object.
|
|
56
56
|
// For now, let's log and rethrow or fallback?
|
|
57
57
|
// Throwing is safer as it prevents operation on invalid path.
|
|
58
|
-
throw new Error(`Security validation failed for level path: ${e.message}`);
|
|
58
|
+
throw new Error(`Security validation failed for level path: ${e instanceof Error ? e.message : String(e)}`);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
formatted = formatted.replace(/\.umap$/i, '');
|
|
@@ -322,8 +322,8 @@ export class LevelTools extends BaseTool implements ILevelTools {
|
|
|
322
322
|
exportPath: params.exportPath,
|
|
323
323
|
details: res
|
|
324
324
|
} as StandardActionResponse;
|
|
325
|
-
} catch (e:
|
|
326
|
-
return { success: false, error: `Export failed: ${e.message}` };
|
|
325
|
+
} catch (e: unknown) {
|
|
326
|
+
return { success: false, error: `Export failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
@@ -356,8 +356,8 @@ export class LevelTools extends BaseTool implements ILevelTools {
|
|
|
356
356
|
streaming: Boolean(params.streaming),
|
|
357
357
|
details: res
|
|
358
358
|
} as StandardActionResponse;
|
|
359
|
-
} catch (e:
|
|
360
|
-
return { success: false, error: `Import failed: ${e.message}` };
|
|
359
|
+
} catch (e: unknown) {
|
|
360
|
+
return { success: false, error: `Import failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
363
|
|
package/src/tools/lighting.ts
CHANGED
|
@@ -40,7 +40,7 @@ export class LightingTools {
|
|
|
40
40
|
name: string;
|
|
41
41
|
location?: [number, number, number];
|
|
42
42
|
rotation?: [number, number, number] | { pitch: number, yaw: number, roll: number };
|
|
43
|
-
properties?: Record<string,
|
|
43
|
+
properties?: Record<string, unknown>;
|
|
44
44
|
}
|
|
45
45
|
) {
|
|
46
46
|
if (!this.automationBridge) {
|
|
@@ -48,7 +48,7 @@ export class LightingTools {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
|
-
const payload: Record<string,
|
|
51
|
+
const payload: Record<string, unknown> = {
|
|
52
52
|
lightClass,
|
|
53
53
|
name: params.name,
|
|
54
54
|
};
|
|
@@ -98,7 +98,7 @@ export class LightingTools {
|
|
|
98
98
|
castShadows?: boolean;
|
|
99
99
|
temperature?: number;
|
|
100
100
|
useAsAtmosphereSunLight?: boolean;
|
|
101
|
-
properties?: Record<string,
|
|
101
|
+
properties?: Record<string, unknown>;
|
|
102
102
|
}) {
|
|
103
103
|
const name = this.normalizeName(params.name);
|
|
104
104
|
if (!this.automationBridge) {
|
|
@@ -149,7 +149,7 @@ export class LightingTools {
|
|
|
149
149
|
const rot = params.rotation || [0, 0, 0];
|
|
150
150
|
|
|
151
151
|
// Build properties for the light
|
|
152
|
-
const properties: Record<string,
|
|
152
|
+
const properties: Record<string, unknown> = params.properties || {};
|
|
153
153
|
if (params.intensity !== undefined) {
|
|
154
154
|
properties.intensity = params.intensity;
|
|
155
155
|
}
|
|
@@ -175,9 +175,9 @@ export class LightingTools {
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
return { success: true, message: `Directional light '${name}' spawned` };
|
|
178
|
-
} catch (e:
|
|
178
|
+
} catch (e: unknown) {
|
|
179
179
|
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
180
|
-
return { success: false, error: `Failed to create directional light: ${e
|
|
180
|
+
return { success: false, error: `Failed to create directional light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -247,7 +247,7 @@ export class LightingTools {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
// Build properties for the light
|
|
250
|
-
const properties: Record<string,
|
|
250
|
+
const properties: Record<string, unknown> = {};
|
|
251
251
|
if (params.intensity !== undefined) {
|
|
252
252
|
properties.intensity = params.intensity;
|
|
253
253
|
}
|
|
@@ -273,9 +273,9 @@ export class LightingTools {
|
|
|
273
273
|
});
|
|
274
274
|
|
|
275
275
|
return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
|
|
276
|
-
} catch (e:
|
|
276
|
+
} catch (e: unknown) {
|
|
277
277
|
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
278
|
-
return { success: false, error: `Failed to create point light: ${e
|
|
278
|
+
return { success: false, error: `Failed to create point light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -369,7 +369,7 @@ export class LightingTools {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
// Build properties for the light
|
|
372
|
-
const properties: Record<string,
|
|
372
|
+
const properties: Record<string, unknown> = {};
|
|
373
373
|
if (params.intensity !== undefined) {
|
|
374
374
|
properties.intensity = params.intensity;
|
|
375
375
|
}
|
|
@@ -398,9 +398,9 @@ export class LightingTools {
|
|
|
398
398
|
});
|
|
399
399
|
|
|
400
400
|
return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
|
|
401
|
-
} catch (e:
|
|
401
|
+
} catch (e: unknown) {
|
|
402
402
|
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
403
|
-
return { success: false, error: `Failed to create spot light: ${e
|
|
403
|
+
return { success: false, error: `Failed to create spot light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
|
|
@@ -485,7 +485,7 @@ export class LightingTools {
|
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
// Build properties for the light
|
|
488
|
-
const properties: Record<string,
|
|
488
|
+
const properties: Record<string, unknown> = {};
|
|
489
489
|
if (params.intensity !== undefined) {
|
|
490
490
|
properties.intensity = params.intensity;
|
|
491
491
|
}
|
|
@@ -508,9 +508,9 @@ export class LightingTools {
|
|
|
508
508
|
});
|
|
509
509
|
|
|
510
510
|
return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
|
|
511
|
-
} catch (e:
|
|
511
|
+
} catch (e: unknown) {
|
|
512
512
|
// Don't mask errors as "not implemented" - report the actual error from the bridge
|
|
513
|
-
return { success: false, error: `Failed to create rect light: ${e
|
|
513
|
+
return { success: false, error: `Failed to create rect light: ${(e instanceof Error ? e.message : String(e)) ?? e}` };
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
516
|
|
|
@@ -581,13 +581,13 @@ export class LightingTools {
|
|
|
581
581
|
}
|
|
582
582
|
|
|
583
583
|
try {
|
|
584
|
-
const properties: Record<string,
|
|
584
|
+
const properties: Record<string, unknown> = {};
|
|
585
585
|
if (params.intensity !== undefined) properties.Intensity = params.intensity;
|
|
586
586
|
if (params.castShadows !== undefined) properties.CastShadows = params.castShadows;
|
|
587
587
|
if (params.realTimeCapture !== undefined) properties.RealTimeCapture = params.realTimeCapture;
|
|
588
588
|
if (params.color) properties.LightColor = { r: params.color[0], g: params.color[1], b: params.color[2], a: 1.0 };
|
|
589
589
|
|
|
590
|
-
const payload: Record<string,
|
|
590
|
+
const payload: Record<string, unknown> = {
|
|
591
591
|
name,
|
|
592
592
|
sourceType: params.sourceType || 'CapturedScene',
|
|
593
593
|
location: params.location,
|
|
@@ -811,12 +811,12 @@ export class LightingTools {
|
|
|
811
811
|
success: true,
|
|
812
812
|
message: response.message || 'Lighting build started',
|
|
813
813
|
...(response.result || {})
|
|
814
|
-
}
|
|
814
|
+
};
|
|
815
815
|
} catch (error) {
|
|
816
816
|
return {
|
|
817
817
|
success: false,
|
|
818
818
|
error: `Failed to build lighting: ${error instanceof Error ? error.message : String(error)}`
|
|
819
|
-
}
|
|
819
|
+
};
|
|
820
820
|
}
|
|
821
821
|
}
|
|
822
822
|
|
package/src/tools/materials.ts
CHANGED
|
@@ -120,7 +120,7 @@ export class MaterialTools {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
async createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string,
|
|
123
|
+
async createMaterialInstance(name: string, path: string, parentMaterial: string, parameters?: Record<string, unknown>) {
|
|
124
124
|
try {
|
|
125
125
|
if (!name || name.trim() === '') {
|
|
126
126
|
return { success: false, error: 'Material instance name cannot be empty' };
|
package/src/tools/sequence.ts
CHANGED
|
@@ -51,7 +51,7 @@ export class SequenceTools extends BaseTool implements ISequenceTools {
|
|
|
51
51
|
const result = response.result ?? response;
|
|
52
52
|
|
|
53
53
|
return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId } as any;
|
|
54
|
-
} catch (err:
|
|
54
|
+
} catch (err: unknown) {
|
|
55
55
|
return { success: false, error: String(err), message: String(err) } as const;
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/tools/ui.ts
CHANGED
|
@@ -59,7 +59,7 @@ export class UITools {
|
|
|
59
59
|
package_path: path,
|
|
60
60
|
factory_class: 'WidgetBlueprintFactory',
|
|
61
61
|
asset_class: 'unreal.WidgetBlueprint'
|
|
62
|
-
} as Record<string,
|
|
62
|
+
} as Record<string, unknown>;
|
|
63
63
|
|
|
64
64
|
const resp = await this.bridge.executeEditorFunction('CREATE_ASSET', payload as any);
|
|
65
65
|
const result = resp && typeof resp === 'object' ? (resp.result ?? resp) : resp;
|
package/src/types/tool-types.ts
CHANGED
|
@@ -9,6 +9,10 @@ export interface BaseToolResponse {
|
|
|
9
9
|
message?: string;
|
|
10
10
|
error?: string;
|
|
11
11
|
warning?: string;
|
|
12
|
+
/** Whether this error is retriable (e.g., connection failures) */
|
|
13
|
+
retriable?: boolean;
|
|
14
|
+
/** Scope/context for the error (e.g., 'tool-call/manage_asset') */
|
|
15
|
+
scope?: string;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
// Asset Management Types
|