unreal-engine-mcp-server 0.5.0 → 0.5.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/.env.example +1 -1
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/greetings.yml +5 -1
- package/.github/workflows/labeler.yml +2 -1
- package/.github/workflows/publish-mcp.yml +1 -0
- package/.github/workflows/release-drafter.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +71 -0
- package/CONTRIBUTING.md +1 -1
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +166 -200
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +3 -3
- package/dist/graphql/resolvers.js +33 -30
- package/dist/graphql/server.js +3 -1
- package/dist/graphql/types.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/dist/server-setup.d.ts +0 -1
- package/dist/server-setup.js +0 -40
- package/dist/tools/actors.d.ts +40 -24
- package/dist/tools/actors.js +8 -2
- package/dist/tools/assets.d.ts +19 -71
- package/dist/tools/assets.js +28 -22
- package/dist/tools/base-tool.d.ts +4 -4
- package/dist/tools/base-tool.js +1 -1
- package/dist/tools/blueprint.d.ts +33 -61
- package/dist/tools/consolidated-tool-handlers.js +96 -110
- package/dist/tools/dynamic-handler-registry.d.ts +11 -9
- package/dist/tools/dynamic-handler-registry.js +17 -95
- package/dist/tools/editor.d.ts +19 -193
- package/dist/tools/editor.js +8 -0
- package/dist/tools/environment.d.ts +8 -14
- package/dist/tools/foliage.d.ts +18 -143
- package/dist/tools/foliage.js +4 -2
- package/dist/tools/handlers/actor-handlers.js +0 -5
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/landscape.d.ts +16 -116
- package/dist/tools/landscape.js +7 -3
- package/dist/tools/level.d.ts +22 -103
- package/dist/tools/level.js +24 -16
- package/dist/tools/lighting.js +5 -1
- package/dist/tools/materials.js +5 -1
- package/dist/tools/niagara.js +37 -2
- package/dist/tools/performance.d.ts +0 -1
- package/dist/tools/performance.js +0 -1
- package/dist/tools/physics.js +5 -1
- package/dist/tools/sequence.d.ts +24 -24
- package/dist/tools/sequence.js +13 -0
- package/dist/tools/ui.d.ts +0 -2
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +135 -135
- package/dist/utils/command-validator.js +3 -2
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +4 -4
- package/dist/utils/response-factory.js +15 -21
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/config.ts +1 -1
- package/src/constants.ts +7 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +47 -49
- package/src/graphql/server.ts +3 -1
- package/src/graphql/types.ts +3 -0
- package/src/index.ts +15 -2
- package/src/resources/assets.ts +5 -4
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +36 -28
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +74 -63
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +59 -59
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +39 -26
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +2 -8
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +1 -1
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +96 -76
- package/src/tools/lighting.ts +6 -1
- package/src/tools/materials.ts +8 -2
- package/src/tools/niagara.ts +44 -2
- package/src/tools/performance.ts +1 -2
- package/src/tools/physics.ts +7 -1
- package/src/tools/sequence.ts +41 -25
- package/src/tools/ui.ts +0 -2
- package/src/types/automation-responses.ts +119 -0
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +135 -135
- package/src/utils/command-validator.ts +3 -2
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/validation.test.ts +184 -0
- package/tests/test-animation.mjs +358 -33
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +314 -116
- package/tests/test-behavior-tree.mjs +327 -144
- package/tests/test-blueprint-graph.mjs +343 -12
- package/tests/test-control-editor.mjs +85 -53
- package/tests/test-graphql.mjs +58 -8
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +291 -61
- package/tests/test-landscape.mjs +304 -48
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-level.mjs +70 -51
- package/tests/test-performance.mjs +539 -0
- package/tests/test-sequence.mjs +82 -46
- package/tests/test-system.mjs +72 -33
- package/tests/test-wasm.mjs +98 -8
- package/vitest.config.ts +35 -0
- package/.github/release-drafter.yml +0 -148
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/blueprint/helpers.d.ts +0 -29
- package/dist/tools/blueprint/helpers.js +0 -182
- package/src/prompts/index.ts +0 -249
- package/src/tools/blueprint/helpers.ts +0 -189
- package/tests/test-blueprint-events.mjs +0 -35
- package/tests/test-extra-tools.mjs +0 -38
- package/tests/test-render.mjs +0 -33
- package/tests/test-search-assets.mjs +0 -66
package/src/graphql/resolvers.ts
CHANGED
|
@@ -129,6 +129,35 @@ interface Blueprint {
|
|
|
129
129
|
scsHierarchy?: Record<string, any>;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
import { Logger } from '../utils/logger.js';
|
|
133
|
+
|
|
134
|
+
const log = new Logger('GraphQL:Resolvers');
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Creates a GraphQL-friendly error with proper extensions
|
|
138
|
+
*/
|
|
139
|
+
class GraphQLResolverError extends Error {
|
|
140
|
+
extensions: { code: string; originalError?: string };
|
|
141
|
+
|
|
142
|
+
constructor(message: string, code: string = 'UNREAL_ENGINE_ERROR', originalError?: Error) {
|
|
143
|
+
super(message);
|
|
144
|
+
this.name = 'GraphQLResolverError';
|
|
145
|
+
this.extensions = {
|
|
146
|
+
code,
|
|
147
|
+
originalError: originalError?.message
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Helper to create resolver errors with proper logging
|
|
154
|
+
*/
|
|
155
|
+
function createResolverError(operation: string, error: unknown): GraphQLResolverError {
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
log.error(`${operation} failed:`, message);
|
|
158
|
+
return new GraphQLResolverError(`${operation} failed: ${message}`, 'UNREAL_ENGINE_ERROR', error instanceof Error ? error : undefined);
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
function logAutomationFailure(source: string, response: any) {
|
|
133
162
|
try {
|
|
134
163
|
if (!response || response.success !== false) {
|
|
@@ -138,7 +167,7 @@ function logAutomationFailure(source: string, response: any) {
|
|
|
138
167
|
if (errorText.length === 0) {
|
|
139
168
|
return;
|
|
140
169
|
}
|
|
141
|
-
|
|
170
|
+
log.error(`${source} automation failure:`, errorText);
|
|
142
171
|
} catch {
|
|
143
172
|
}
|
|
144
173
|
}
|
|
@@ -158,7 +187,7 @@ async function getActorProperties(
|
|
|
158
187
|
});
|
|
159
188
|
return result.success ? result.value || {} : {};
|
|
160
189
|
} catch (error) {
|
|
161
|
-
|
|
190
|
+
log.error('Failed to get actor properties:', error);
|
|
162
191
|
return {};
|
|
163
192
|
}
|
|
164
193
|
}
|
|
@@ -190,11 +219,11 @@ async function listAssets(
|
|
|
190
219
|
}
|
|
191
220
|
|
|
192
221
|
logAutomationFailure('list_assets', response);
|
|
193
|
-
|
|
222
|
+
log.warn('Failed to list assets - returning empty set');
|
|
194
223
|
return { assets: [], totalCount: 0 };
|
|
195
224
|
} catch (error) {
|
|
196
|
-
|
|
197
|
-
|
|
225
|
+
log.error('Failed to list assets:', error);
|
|
226
|
+
throw createResolverError('listAssets', error);
|
|
198
227
|
}
|
|
199
228
|
}
|
|
200
229
|
|
|
@@ -229,33 +258,7 @@ async function listActors(
|
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
|
|
232
|
-
/**
|
|
233
|
-
* Helper to get blueprint details
|
|
234
|
-
*/
|
|
235
|
-
async function getBlueprint(
|
|
236
|
-
automationBridge: AutomationBridge,
|
|
237
|
-
blueprintPath: string
|
|
238
|
-
): Promise<Blueprint | null> {
|
|
239
|
-
try {
|
|
240
|
-
const response = await automationBridge.sendAutomationRequest(
|
|
241
|
-
'get_blueprint',
|
|
242
|
-
{
|
|
243
|
-
blueprintPath
|
|
244
|
-
},
|
|
245
|
-
{ timeoutMs: 30000 }
|
|
246
|
-
);
|
|
247
261
|
|
|
248
|
-
if (response.success && response.result) {
|
|
249
|
-
return response.result as Blueprint;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
logAutomationFailure('get_blueprint', response);
|
|
253
|
-
return null;
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error('Failed to get blueprint:', error);
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
262
|
|
|
260
263
|
/**
|
|
261
264
|
* GraphQL Resolvers Implementation
|
|
@@ -291,17 +294,10 @@ export const resolvers = {
|
|
|
291
294
|
|
|
292
295
|
asset: async (_: any, { path }: { path: string }, context: GraphQLContext) => {
|
|
293
296
|
try {
|
|
294
|
-
|
|
295
|
-
'
|
|
296
|
-
{ assetPath: path },
|
|
297
|
-
{ timeoutMs: 10000 }
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
if (response.success && response.result) {
|
|
301
|
-
return response.result;
|
|
297
|
+
if (!context.loaders) {
|
|
298
|
+
throw new Error('Loaders not initialized');
|
|
302
299
|
}
|
|
303
|
-
|
|
304
|
-
return null;
|
|
300
|
+
return await context.loaders.assetLoader.load(path);
|
|
305
301
|
} catch (error) {
|
|
306
302
|
console.error('Failed to get asset:', error);
|
|
307
303
|
return null;
|
|
@@ -336,11 +332,10 @@ export const resolvers = {
|
|
|
336
332
|
|
|
337
333
|
actor: async (_: any, { name }: { name: string }, context: GraphQLContext) => {
|
|
338
334
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
return actors.actors[0];
|
|
335
|
+
if (!context.loaders) {
|
|
336
|
+
throw new Error('Loaders not initialized');
|
|
342
337
|
}
|
|
343
|
-
return
|
|
338
|
+
return await context.loaders.actorLoader.load(name);
|
|
344
339
|
} catch (error) {
|
|
345
340
|
console.error('Failed to get actor:', error);
|
|
346
341
|
return null;
|
|
@@ -394,7 +389,10 @@ export const resolvers = {
|
|
|
394
389
|
},
|
|
395
390
|
|
|
396
391
|
blueprint: async (_: any, { path }: { path: string }, context: GraphQLContext) => {
|
|
397
|
-
|
|
392
|
+
if (!context.loaders) {
|
|
393
|
+
throw new Error('Loaders not initialized');
|
|
394
|
+
}
|
|
395
|
+
return await context.loaders.blueprintLoader.load(path);
|
|
398
396
|
},
|
|
399
397
|
|
|
400
398
|
levels: async (_: any, __: any, context: GraphQLContext) => {
|
|
@@ -524,9 +522,9 @@ export const resolvers = {
|
|
|
524
522
|
{ subAction: 'get_cells' },
|
|
525
523
|
{ timeoutMs: 10000 }
|
|
526
524
|
);
|
|
527
|
-
|
|
525
|
+
|
|
528
526
|
if (response.success && response.result) {
|
|
529
|
-
|
|
527
|
+
return (response.result as any).cells || [];
|
|
530
528
|
}
|
|
531
529
|
return [];
|
|
532
530
|
} catch (error) {
|
|
@@ -550,7 +548,7 @@ export const resolvers = {
|
|
|
550
548
|
const edges = assets.map((asset, index) => ({
|
|
551
549
|
node: {
|
|
552
550
|
...asset,
|
|
553
|
-
emitters: [],
|
|
551
|
+
emitters: [],
|
|
554
552
|
parameters: []
|
|
555
553
|
},
|
|
556
554
|
cursor: Buffer.from(`${asset.path}:${offset + index}`).toString('base64')
|
package/src/graphql/server.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createGraphQLSchema } from './schema.js';
|
|
|
5
5
|
import type { GraphQLContext } from './types.js';
|
|
6
6
|
import type { UnrealBridge } from '../unreal-bridge.js';
|
|
7
7
|
import { AutomationBridge } from '../automation/index.js';
|
|
8
|
+
import { createLoaders } from './loaders.js';
|
|
8
9
|
|
|
9
10
|
export interface GraphQLServerConfig {
|
|
10
11
|
enabled?: boolean;
|
|
@@ -65,7 +66,8 @@ export class GraphQLServer {
|
|
|
65
66
|
},
|
|
66
67
|
context: (): GraphQLContext => ({
|
|
67
68
|
bridge: this.bridge,
|
|
68
|
-
automationBridge: this.automationBridge
|
|
69
|
+
automationBridge: this.automationBridge,
|
|
70
|
+
loaders: createLoaders(this.automationBridge)
|
|
69
71
|
}),
|
|
70
72
|
logging: {
|
|
71
73
|
debug: (...args) => this.log.debug('[GraphQL]', ...args),
|
package/src/graphql/types.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { UnrealBridge } from '../unreal-bridge.js';
|
|
2
2
|
import { AutomationBridge } from '../automation/index.js';
|
|
3
|
+
import type { GraphQLLoaders } from './loaders.js';
|
|
3
4
|
|
|
4
5
|
export interface GraphQLContext {
|
|
5
6
|
bridge: UnrealBridge;
|
|
6
7
|
automationBridge: AutomationBridge;
|
|
8
|
+
/** DataLoaders for batching and caching - solves N+1 query problem */
|
|
9
|
+
loaders?: GraphQLLoaders;
|
|
7
10
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { HealthMonitor } from './services/health-monitor.js';
|
|
|
13
13
|
import { ServerSetup } from './server-setup.js';
|
|
14
14
|
import { startMetricsServer } from './services/metrics-server.js';
|
|
15
15
|
import { config } from './config.js';
|
|
16
|
+
import { GraphQLServer } from './graphql/server.js';
|
|
16
17
|
|
|
17
18
|
const require = createRequire(import.meta.url);
|
|
18
19
|
const packageInfo: { name?: string; version?: string } = (() => {
|
|
@@ -106,6 +107,12 @@ export function createServer() {
|
|
|
106
107
|
// Optionally expose Prometheus-style metrics via /metrics
|
|
107
108
|
startMetricsServer({ healthMonitor, automationBridge, logger: log });
|
|
108
109
|
|
|
110
|
+
// Initialize GraphQL server (controlled by GRAPHQL_ENABLED env var)
|
|
111
|
+
const graphqlServer = new GraphQLServer(bridge, automationBridge);
|
|
112
|
+
graphqlServer.start().catch((error) => {
|
|
113
|
+
log.warn('GraphQL server failed to start:', error);
|
|
114
|
+
});
|
|
115
|
+
|
|
109
116
|
// Initialize WebAssembly module for high-performance operations (5-8x faster)
|
|
110
117
|
log.debug('Initializing WebAssembly integration...');
|
|
111
118
|
initializeWASM().then(() => {
|
|
@@ -145,7 +152,7 @@ export function createServer() {
|
|
|
145
152
|
const serverSetup = new ServerSetup(server, bridge, automationBridge, log, healthMonitor);
|
|
146
153
|
serverSetup.setup(); // Register tools, resources, and prompts
|
|
147
154
|
|
|
148
|
-
return { server, bridge, automationBridge };
|
|
155
|
+
return { server, bridge, automationBridge, graphqlServer };
|
|
149
156
|
}
|
|
150
157
|
|
|
151
158
|
// Export configuration schema for session UI and runtime validation
|
|
@@ -170,7 +177,7 @@ export default function createServerDefault({ config }: { config?: any } = {}) {
|
|
|
170
177
|
}
|
|
171
178
|
|
|
172
179
|
export async function startStdioServer() {
|
|
173
|
-
const { server, automationBridge } = createServer();
|
|
180
|
+
const { server, automationBridge, graphqlServer } = createServer();
|
|
174
181
|
const transport = new StdioServerTransport();
|
|
175
182
|
let shuttingDown = false;
|
|
176
183
|
|
|
@@ -187,6 +194,12 @@ export async function startStdioServer() {
|
|
|
187
194
|
log.warn('Failed to stop automation bridge cleanly', error);
|
|
188
195
|
}
|
|
189
196
|
|
|
197
|
+
try {
|
|
198
|
+
await graphqlServer.stop();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
log.warn('Failed to stop GraphQL server cleanly', error);
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
try {
|
|
191
204
|
if (typeof (server as any).close === 'function') {
|
|
192
205
|
await (server as any).close();
|
package/src/resources/assets.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseTool } from '../tools/base-tool.js';
|
|
2
2
|
import { IAssetResources } from '../types/tool-interfaces.js';
|
|
3
3
|
import { coerceString } from '../utils/result-helpers.js';
|
|
4
|
+
import { AutomationResponse } from '../types/automation-responses.js';
|
|
4
5
|
|
|
5
6
|
export class AssetResources extends BaseTool implements IAssetResources {
|
|
6
7
|
// Simple in-memory cache for asset listing
|
|
@@ -175,14 +176,14 @@ export class AssetResources extends BaseTool implements IAssetResources {
|
|
|
175
176
|
// Use the native C++ plugin's list action instead of Python
|
|
176
177
|
try {
|
|
177
178
|
const normalizedDir = this.normalizeDir(dir);
|
|
178
|
-
const response = await this.sendAutomationRequest(
|
|
179
|
+
const response = await this.sendAutomationRequest<AutomationResponse>(
|
|
179
180
|
'list',
|
|
180
181
|
{ directory: normalizedDir, limit, recursive: false },
|
|
181
182
|
{ timeoutMs: 30000 }
|
|
182
183
|
);
|
|
183
184
|
|
|
184
185
|
if (response.success !== false && response.result) {
|
|
185
|
-
const payload = response.result;
|
|
186
|
+
const payload = response.result as any;
|
|
186
187
|
|
|
187
188
|
const foldersArr = Array.isArray(payload.folders_list)
|
|
188
189
|
? payload.folders_list.map((f: any) => ({
|
|
@@ -257,12 +258,12 @@ export class AssetResources extends BaseTool implements IAssetResources {
|
|
|
257
258
|
|
|
258
259
|
try {
|
|
259
260
|
const normalizedPath = this.normalizeDir(assetPath);
|
|
260
|
-
const response = await this.sendAutomationRequest(
|
|
261
|
+
const response = await this.sendAutomationRequest<AutomationResponse>(
|
|
261
262
|
'asset_exists',
|
|
262
263
|
{ asset_path: normalizedPath }
|
|
263
264
|
);
|
|
264
265
|
|
|
265
|
-
return response?.success !== false && response?.result?.exists === true;
|
|
266
|
+
return response?.success !== false && (response?.result as any)?.exists === true;
|
|
266
267
|
} catch {
|
|
267
268
|
return false;
|
|
268
269
|
}
|
package/src/server-setup.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
// import { ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import { UnrealBridge } from './unreal-bridge.js';
|
|
4
4
|
import { AutomationBridge } from './automation/index.js';
|
|
5
5
|
import { Logger } from './utils/logger.js';
|
|
6
6
|
import { HealthMonitor } from './services/health-monitor.js';
|
|
7
|
-
import { prompts } from './prompts/index.js';
|
|
7
|
+
// import { prompts } from './prompts/index.js';
|
|
8
8
|
import { AssetResources } from './resources/assets.js';
|
|
9
9
|
import { ActorResources } from './resources/actors.js';
|
|
10
10
|
import { LevelResources } from './resources/levels.js';
|
|
@@ -73,7 +73,7 @@ export class ServerSetup {
|
|
|
73
73
|
);
|
|
74
74
|
toolRegistry.register();
|
|
75
75
|
|
|
76
|
-
this.registerPrompts();
|
|
76
|
+
// this.registerPrompts();
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
private validateEnvironment() {
|
|
@@ -110,39 +110,5 @@ export class ServerSetup {
|
|
|
110
110
|
return ok;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
private registerPrompts() {
|
|
114
|
-
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
115
|
-
return {
|
|
116
|
-
prompts: prompts.map(p => ({
|
|
117
|
-
name: p.name,
|
|
118
|
-
description: p.description,
|
|
119
|
-
arguments: Object.entries(p.arguments || {}).map(([name, schema]) => {
|
|
120
|
-
const meta: Record<string, unknown> = {};
|
|
121
|
-
if (schema.type) meta.type = schema.type;
|
|
122
|
-
if (schema.enum) meta.enum = schema.enum;
|
|
123
|
-
if (schema.default !== undefined) meta.default = schema.default;
|
|
124
|
-
return {
|
|
125
|
-
name,
|
|
126
|
-
description: schema.description,
|
|
127
|
-
required: schema.required ?? false,
|
|
128
|
-
...(Object.keys(meta).length ? { _meta: meta } : {})
|
|
129
|
-
};
|
|
130
|
-
})
|
|
131
|
-
}))
|
|
132
|
-
};
|
|
133
|
-
});
|
|
134
113
|
|
|
135
|
-
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
136
|
-
const prompt = prompts.find(p => p.name === request.params.name);
|
|
137
|
-
if (!prompt) {
|
|
138
|
-
throw new Error(`Unknown prompt: ${request.params.name}`);
|
|
139
|
-
}
|
|
140
|
-
const args = (request.params.arguments || {}) as Record<string, unknown>;
|
|
141
|
-
const messages = prompt.build(args);
|
|
142
|
-
return {
|
|
143
|
-
description: prompt.description,
|
|
144
|
-
messages
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
114
|
}
|
package/src/tools/actors.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
import { ensureRotation, ensureVector3 } from '../utils/validation.js';
|
|
3
3
|
import { BaseTool } from './base-tool.js';
|
|
4
4
|
import { IActorTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
5
|
+
import { ActorResponse } from '../types/automation-responses.js';
|
|
6
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
5
7
|
|
|
6
8
|
export class ActorTools extends BaseTool implements IActorTools {
|
|
7
9
|
constructor(bridge: UnrealBridge) {
|
|
@@ -44,7 +46,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
44
46
|
try {
|
|
45
47
|
const bridge = this.getAutomationBridge();
|
|
46
48
|
const timeoutMs = typeof params.timeoutMs === 'number' && params.timeoutMs > 0 ? params.timeoutMs : undefined;
|
|
47
|
-
const response = await bridge.sendAutomationRequest(
|
|
49
|
+
const response = await bridge.sendAutomationRequest<ActorResponse>(
|
|
48
50
|
'control_actor',
|
|
49
51
|
{
|
|
50
52
|
action: 'spawn',
|
|
@@ -58,7 +60,9 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
58
60
|
);
|
|
59
61
|
|
|
60
62
|
if (!response || !response.success) {
|
|
61
|
-
|
|
63
|
+
const error = response?.error;
|
|
64
|
+
const errorMsg = typeof error === 'string' ? error : (error as any)?.message || response?.message || 'Failed to spawn actor';
|
|
65
|
+
throw new Error(errorMsg);
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
const data = (response as any).data || {};
|
|
@@ -117,7 +121,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
117
121
|
// DELETE_PARTIAL as a handled, partial-success cleanup instead
|
|
118
122
|
// of surfacing it as a hard error to the consolidated handler.
|
|
119
123
|
const bridge = this.getAutomationBridge();
|
|
120
|
-
const response: any = await bridge.sendAutomationRequest('control_actor', {
|
|
124
|
+
const response: any = await bridge.sendAutomationRequest<ActorResponse>('control_actor', {
|
|
121
125
|
action: 'delete',
|
|
122
126
|
actorNames: names
|
|
123
127
|
});
|
|
@@ -158,7 +162,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
158
162
|
throw new Error('Invalid actorName');
|
|
159
163
|
}
|
|
160
164
|
|
|
161
|
-
return this.sendRequest('delete', { actorName: params.actorName }, 'control_actor');
|
|
165
|
+
return this.sendRequest<StandardActionResponse>('delete', { actorName: params.actorName }, 'control_actor');
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
async applyForce(params: { actorName: string; force: { x: number; y: number; z: number } }) {
|
|
@@ -183,7 +187,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
183
187
|
};
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
return this.sendRequest('apply_force', {
|
|
190
|
+
return this.sendRequest<StandardActionResponse>('apply_force', {
|
|
187
191
|
actorName: params.actorName,
|
|
188
192
|
force: { x: forceX, y: forceY, z: forceZ }
|
|
189
193
|
}, 'control_actor');
|
|
@@ -259,7 +263,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
259
263
|
if (location) payload.location = { x: location[0], y: location[1], z: location[2] };
|
|
260
264
|
if (rotation) payload.rotation = { pitch: rotation[0], yaw: rotation[1], roll: rotation[2] };
|
|
261
265
|
|
|
262
|
-
return this.sendRequest('spawn_blueprint', payload, 'control_actor');
|
|
266
|
+
return this.sendRequest<StandardActionResponse>('spawn_blueprint', payload, 'control_actor');
|
|
263
267
|
}
|
|
264
268
|
|
|
265
269
|
async setTransform(params: { actorName: string; location?: { x: number; y: number; z: number }; rotation?: { pitch: number; yaw: number; roll: number }; scale?: { x: number; y: number; z: number } }) {
|
|
@@ -282,14 +286,14 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
282
286
|
payload.scale = { x: scl[0], y: scl[1], z: scl[2] };
|
|
283
287
|
}
|
|
284
288
|
|
|
285
|
-
return this.sendRequest('set_transform', payload, 'control_actor');
|
|
289
|
+
return this.sendRequest<StandardActionResponse>('set_transform', payload, 'control_actor');
|
|
286
290
|
}
|
|
287
291
|
|
|
288
292
|
async getTransform(actorName: string) {
|
|
289
293
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
290
294
|
throw new Error('Invalid actorName');
|
|
291
295
|
}
|
|
292
|
-
return this.sendRequest('get_transform', { actorName }, 'control_actor')
|
|
296
|
+
return this.sendRequest<StandardActionResponse>('get_transform', { actorName }, 'control_actor')
|
|
293
297
|
.then(response => {
|
|
294
298
|
// If response is standardized, extract data or return as is.
|
|
295
299
|
// For now, return the full response which includes data.
|
|
@@ -302,7 +306,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
302
306
|
if (!actorName) {
|
|
303
307
|
throw new Error('Invalid actorName');
|
|
304
308
|
}
|
|
305
|
-
return this.sendRequest('set_visibility', { actorName, visible: Boolean(params.visible) }, 'control_actor');
|
|
309
|
+
return this.sendRequest<StandardActionResponse>('set_visibility', { actorName, visible: Boolean(params.visible) }, 'control_actor');
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
async addComponent(params: { actorName: string; componentType: string; componentName?: string; properties?: Record<string, unknown> }) {
|
|
@@ -311,7 +315,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
311
315
|
if (!actorName) throw new Error('Invalid actorName');
|
|
312
316
|
if (!componentType) throw new Error('Invalid componentType');
|
|
313
317
|
|
|
314
|
-
return this.sendRequest('add_component', {
|
|
318
|
+
return this.sendRequest<StandardActionResponse>('add_component', {
|
|
315
319
|
actorName,
|
|
316
320
|
componentType,
|
|
317
321
|
componentName: typeof params.componentName === 'string' ? params.componentName : undefined,
|
|
@@ -325,7 +329,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
325
329
|
if (!actorName) throw new Error('Invalid actorName');
|
|
326
330
|
if (!componentName) throw new Error('Invalid componentName');
|
|
327
331
|
|
|
328
|
-
return this.sendRequest('set_component_properties', {
|
|
332
|
+
return this.sendRequest<StandardActionResponse>('set_component_properties', {
|
|
329
333
|
actorName,
|
|
330
334
|
componentName,
|
|
331
335
|
properties: params.properties ?? {}
|
|
@@ -336,7 +340,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
336
340
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
337
341
|
throw new Error('Invalid actorName');
|
|
338
342
|
}
|
|
339
|
-
const response = await this.sendRequest('get_components', { actorName }, 'control_actor');
|
|
343
|
+
const response = await this.sendRequest<StandardActionResponse>('get_components', { actorName }, 'control_actor');
|
|
340
344
|
if (!response.success) {
|
|
341
345
|
return { success: false, error: response.error || `Failed to get components for actor ${actorName}` };
|
|
342
346
|
}
|
|
@@ -365,10 +369,14 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
365
369
|
}
|
|
366
370
|
if (params.offset) {
|
|
367
371
|
const offs = ensureVector3(params.offset, 'duplicate offset');
|
|
368
|
-
|
|
372
|
+
// Use WASM vectorAdd for offset calculation (origin + offset)
|
|
373
|
+
const origin: [number, number, number] = [0, 0, 0];
|
|
374
|
+
const calculatedOffset = wasmIntegration.vectorAdd(origin, offs);
|
|
375
|
+
console.error('[WASM] Using vectorAdd for duplicate offset calculation');
|
|
376
|
+
payload.offset = { x: calculatedOffset[0], y: calculatedOffset[1], z: calculatedOffset[2] };
|
|
369
377
|
}
|
|
370
378
|
|
|
371
|
-
return this.sendRequest('duplicate', payload, 'control_actor');
|
|
379
|
+
return this.sendRequest<StandardActionResponse>('duplicate', payload, 'control_actor');
|
|
372
380
|
}
|
|
373
381
|
|
|
374
382
|
async addTag(params: { actorName: string; tag: string }) {
|
|
@@ -377,7 +385,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
377
385
|
if (!actorName) throw new Error('Invalid actorName');
|
|
378
386
|
if (!tag) throw new Error('Invalid tag');
|
|
379
387
|
|
|
380
|
-
return this.sendRequest('add_tag', { actorName, tag }, 'control_actor');
|
|
388
|
+
return this.sendRequest<StandardActionResponse>('add_tag', { actorName, tag }, 'control_actor');
|
|
381
389
|
}
|
|
382
390
|
|
|
383
391
|
async removeTag(params: { actorName: string; tag: string }) {
|
|
@@ -386,7 +394,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
386
394
|
if (!actorName) throw new Error('Invalid actorName');
|
|
387
395
|
if (!tag) throw new Error('Invalid tag');
|
|
388
396
|
|
|
389
|
-
return this.sendRequest('remove_tag', { actorName, tag }, 'control_actor');
|
|
397
|
+
return this.sendRequest<StandardActionResponse>('remove_tag', { actorName, tag }, 'control_actor');
|
|
390
398
|
}
|
|
391
399
|
|
|
392
400
|
async findByTag(params: { tag: string; matchType?: string }) {
|
|
@@ -404,7 +412,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
404
412
|
};
|
|
405
413
|
}
|
|
406
414
|
|
|
407
|
-
return this.sendRequest('find_by_tag', {
|
|
415
|
+
return this.sendRequest<StandardActionResponse>('find_by_tag', {
|
|
408
416
|
tag,
|
|
409
417
|
matchType: typeof params.matchType === 'string' ? params.matchType : undefined
|
|
410
418
|
}, 'control_actor');
|
|
@@ -414,7 +422,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
414
422
|
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
415
423
|
throw new Error('Invalid actor name query');
|
|
416
424
|
}
|
|
417
|
-
return this.sendRequest('find_by_name', { name: name.trim() }, 'control_actor');
|
|
425
|
+
return this.sendRequest<StandardActionResponse>('find_by_name', { name: name.trim() }, 'control_actor');
|
|
418
426
|
}
|
|
419
427
|
|
|
420
428
|
async detach(actorName: string) {
|
|
@@ -428,7 +436,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
428
436
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
429
437
|
throw new Error('Invalid actorName');
|
|
430
438
|
}
|
|
431
|
-
return this.sendRequest('detach', { actorName }, 'control_actor');
|
|
439
|
+
return this.sendRequest<StandardActionResponse>('detach', { actorName }, 'control_actor');
|
|
432
440
|
}
|
|
433
441
|
|
|
434
442
|
async attach(params: { childActor: string; parentActor: string }) {
|
|
@@ -437,20 +445,20 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
437
445
|
if (!child) throw new Error('Invalid childActor');
|
|
438
446
|
if (!parent) throw new Error('Invalid parentActor');
|
|
439
447
|
|
|
440
|
-
return this.sendRequest('attach', { childActor: child, parentActor: parent }, 'control_actor');
|
|
448
|
+
return this.sendRequest<StandardActionResponse>('attach', { childActor: child, parentActor: parent }, 'control_actor');
|
|
441
449
|
}
|
|
442
450
|
|
|
443
451
|
async deleteByTag(tag: string) {
|
|
444
452
|
if (typeof tag !== 'string' || tag.trim().length === 0) {
|
|
445
453
|
throw new Error('Invalid tag');
|
|
446
454
|
}
|
|
447
|
-
return this.sendRequest('delete_by_tag', { tag: tag.trim() }, 'control_actor');
|
|
455
|
+
return this.sendRequest<StandardActionResponse>('delete_by_tag', { tag: tag.trim() }, 'control_actor');
|
|
448
456
|
}
|
|
449
457
|
|
|
450
458
|
async setBlueprintVariables(params: { actorName: string; variables: Record<string, unknown> }) {
|
|
451
459
|
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
452
460
|
if (!actorName) throw new Error('Invalid actorName');
|
|
453
|
-
return this.sendRequest('set_blueprint_variables', { actorName, variables: params.variables ?? {} }, 'control_actor');
|
|
461
|
+
return this.sendRequest<StandardActionResponse>('set_blueprint_variables', { actorName, variables: params.variables ?? {} }, 'control_actor');
|
|
454
462
|
}
|
|
455
463
|
|
|
456
464
|
async createSnapshot(params: { actorName: string; snapshotName: string }) {
|
|
@@ -458,7 +466,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
458
466
|
const snapshotName = typeof params.snapshotName === 'string' ? params.snapshotName.trim() : '';
|
|
459
467
|
if (!actorName) throw new Error('Invalid actorName');
|
|
460
468
|
if (!snapshotName) throw new Error('Invalid snapshotName');
|
|
461
|
-
return this.sendRequest('create_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
469
|
+
return this.sendRequest<StandardActionResponse>('create_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
462
470
|
}
|
|
463
471
|
|
|
464
472
|
async restoreSnapshot(params: { actorName: string; snapshotName: string }) {
|
|
@@ -466,12 +474,12 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
466
474
|
const snapshotName = typeof params.snapshotName === 'string' ? params.snapshotName.trim() : '';
|
|
467
475
|
if (!actorName) throw new Error('Invalid actorName');
|
|
468
476
|
if (!snapshotName) throw new Error('Invalid snapshotName');
|
|
469
|
-
return this.sendRequest('restore_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
477
|
+
return this.sendRequest<StandardActionResponse>('restore_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
470
478
|
}
|
|
471
479
|
async exportActor(params: { actorName: string; destinationPath?: string }) {
|
|
472
480
|
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
473
481
|
if (!actorName) throw new Error('Invalid actorName');
|
|
474
|
-
return this.sendRequest('export', {
|
|
482
|
+
return this.sendRequest<StandardActionResponse>('export', {
|
|
475
483
|
actorName,
|
|
476
484
|
destinationPath: params.destinationPath
|
|
477
485
|
}, 'control_actor');
|
|
@@ -481,7 +489,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
481
489
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
482
490
|
throw new Error('Invalid actorName');
|
|
483
491
|
}
|
|
484
|
-
const response = await this.sendRequest('get_bounding_box', { actorName }, 'control_actor');
|
|
492
|
+
const response = await this.sendRequest<StandardActionResponse>('get_bounding_box', { actorName }, 'control_actor');
|
|
485
493
|
if (!response.success) {
|
|
486
494
|
return { success: false, error: response.error || `Failed to get bounding box for actor ${actorName}` };
|
|
487
495
|
}
|
|
@@ -496,7 +504,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
496
504
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
497
505
|
throw new Error('Invalid actorName');
|
|
498
506
|
}
|
|
499
|
-
const response = await this.sendRequest('get_metadata', { actorName }, 'control_actor');
|
|
507
|
+
const response = await this.sendRequest<StandardActionResponse>('get_metadata', { actorName }, 'control_actor');
|
|
500
508
|
if (!response.success) {
|
|
501
509
|
return { success: false, error: response.error || `Failed to get metadata for actor ${actorName}` };
|
|
502
510
|
}
|
|
@@ -512,7 +520,7 @@ export class ActorTools extends BaseTool implements IActorTools {
|
|
|
512
520
|
if (params?.filter) {
|
|
513
521
|
payload.filter = params.filter;
|
|
514
522
|
}
|
|
515
|
-
const response = await this.sendRequest('list_actors', payload, 'control_actor');
|
|
523
|
+
const response = await this.sendRequest<StandardActionResponse>('list_actors', payload, 'control_actor');
|
|
516
524
|
if (!response.success) {
|
|
517
525
|
return { success: false, error: response.error || 'Failed to list actors' };
|
|
518
526
|
}
|
package/src/tools/animation.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
import { AutomationBridge } from '../automation/index.js';
|
|
3
3
|
import { cleanObject } from '../utils/safe-json.js';
|
|
4
4
|
import { validateAssetParams } from '../utils/validation.js';
|
|
5
|
+
import { wasmIntegration as _wasmIntegration } from '../wasm/index.js';
|
|
5
6
|
|
|
6
7
|
type CreateAnimationBlueprintSuccess = {
|
|
7
8
|
success: true;
|