unreal-engine-mcp-server 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.env.production +6 -1
  2. package/Dockerfile +11 -28
  3. package/README.md +1 -2
  4. package/dist/index.js +120 -54
  5. package/dist/resources/actors.js +71 -13
  6. package/dist/resources/assets.d.ts +3 -2
  7. package/dist/resources/assets.js +96 -72
  8. package/dist/resources/levels.js +2 -2
  9. package/dist/tools/assets.js +6 -2
  10. package/dist/tools/build_environment_advanced.js +46 -42
  11. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  12. package/dist/tools/consolidated-tool-definitions.js +173 -8
  13. package/dist/tools/consolidated-tool-handlers.js +331 -718
  14. package/dist/tools/debug.js +4 -6
  15. package/dist/tools/rc.js +2 -2
  16. package/dist/tools/sequence.js +21 -2
  17. package/dist/unreal-bridge.d.ts +4 -1
  18. package/dist/unreal-bridge.js +211 -53
  19. package/dist/utils/http.js +4 -2
  20. package/dist/utils/response-validator.d.ts +6 -1
  21. package/dist/utils/response-validator.js +43 -15
  22. package/package.json +5 -5
  23. package/server.json +2 -2
  24. package/src/index.ts +120 -56
  25. package/src/resources/actors.ts +51 -13
  26. package/src/resources/assets.ts +97 -73
  27. package/src/resources/levels.ts +2 -2
  28. package/src/tools/assets.ts +6 -2
  29. package/src/tools/build_environment_advanced.ts +46 -42
  30. package/src/tools/consolidated-tool-definitions.ts +173 -8
  31. package/src/tools/consolidated-tool-handlers.ts +318 -747
  32. package/src/tools/debug.ts +4 -6
  33. package/src/tools/rc.ts +2 -2
  34. package/src/tools/sequence.ts +21 -2
  35. package/src/unreal-bridge.ts +163 -60
  36. package/src/utils/http.ts +7 -4
  37. package/src/utils/response-validator.ts +48 -19
  38. package/dist/tools/tool-definitions.d.ts +0 -4919
  39. package/dist/tools/tool-definitions.js +0 -1007
  40. package/dist/tools/tool-handlers.d.ts +0 -47
  41. package/dist/tools/tool-handlers.js +0 -863
  42. package/src/tools/tool-definitions.ts +0 -1023
  43. package/src/tools/tool-handlers.ts +0 -973
package/.env.production CHANGED
@@ -10,11 +10,16 @@ UE_RC_WS_PORT=30020
10
10
  USE_CONSOLIDATED_TOOLS=true
11
11
 
12
12
  # Logging Level
13
+ # Available levels (from quietest to most verbose):
14
+ # - error : only errors
15
+ # - warn : warnings and errors
16
+ # - info : info, warnings, errors (default)
17
+ # - debug : full debug, very verbose
13
18
  LOG_LEVEL=info
14
19
 
15
20
  # Server Settings
16
21
  SERVER_NAME=unreal-engine-mcp
17
- SERVER_VERSION=0.3.0
22
+ SERVER_VERSION=0.4.0
18
23
 
19
24
  # Connection Settings
20
25
  MAX_RETRY_ATTEMPTS=3
package/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  # Multi-stage build for efficient image size
2
- FROM node:20-alpine AS builder
2
+ FROM node:22-alpine AS builder
3
3
 
4
4
  # Set working directory
5
5
  WORKDIR /app
@@ -18,37 +18,20 @@ COPY src ./src
18
18
  RUN npm run build
19
19
 
20
20
  # Production stage
21
- FROM node:20-alpine
21
+ FROM cgr.dev/chainguard/node:latest
22
22
 
23
- # Install dumb-init for proper signal handling
24
- RUN apk add --no-cache dumb-init
23
+ ENV NODE_ENV=production
25
24
 
26
- # Create non-root user
27
- RUN addgroup -g 1001 -S nodejs && \
28
- adduser -S nodejs -u 1001
29
-
30
- # Set working directory
25
+ # Chainguard node runs as nonroot by default (user: node)
31
26
  WORKDIR /app
32
27
 
33
- # Copy package files
34
- COPY package*.json ./
35
-
36
- # Install production dependencies only (skip prepare script)
37
- RUN npm ci --only=production --ignore-scripts && \
38
- npm cache clean --force
28
+ # Copy only package manifests and install production deps in a clean layer
29
+ COPY --chown=node:node package*.json ./
30
+ RUN npm ci --omit=dev --ignore-scripts && npm cache clean --force
39
31
 
40
32
  # Copy built application from builder stage
41
- COPY --from=builder /app/dist ./dist
42
-
43
- # Change ownership to nodejs user
44
- RUN chown -R nodejs:nodejs /app
45
-
46
- # Switch to non-root user
47
- USER nodejs
48
-
49
- # MCP servers use stdio, no port exposure needed
50
- # Use dumb-init to handle signals properly
51
- ENTRYPOINT ["dumb-init", "--"]
33
+ COPY --chown=node:node --from=builder /app/dist ./dist
52
34
 
53
- # Run the MCP server
54
- CMD ["node", "dist/cli.js"]
35
+ # No shell, no init needed; run as provided nonroot user
36
+ ENTRYPOINT ["/usr/bin/node"]
37
+ CMD ["dist/cli.js"]
package/README.md CHANGED
@@ -114,7 +114,7 @@ Then enable Python execution in: Edit > Project Settings > Plugins > Remote Cont
114
114
  }
115
115
  ```
116
116
 
117
- ## Available Tools (13 Consolidated)
117
+ ## Available Tools (13)
118
118
 
119
119
  | Tool | Description |
120
120
  |------|-------------|
@@ -161,7 +161,6 @@ Blueprints, Materials, Textures, Static/Skeletal Meshes, Levels, Sounds, Particl
161
161
  UE_HOST=127.0.0.1 # Unreal Engine host
162
162
  UE_RC_HTTP_PORT=30010 # Remote Control HTTP port
163
163
  UE_RC_WS_PORT=30020 # Remote Control WebSocket port
164
- USE_CONSOLIDATED_TOOLS=true # Use 13 consolidated tools (false = 37 individual)
165
164
  LOG_LEVEL=info # debug | info | warn | error
166
165
  ```
167
166
 
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ import { BlueprintTools } from './tools/blueprint.js';
17
17
  import { LevelTools } from './tools/level.js';
18
18
  import { LightingTools } from './tools/lighting.js';
19
19
  import { LandscapeTools } from './tools/landscape.js';
20
+ import { BuildEnvironmentAdvanced } from './tools/build_environment_advanced.js';
20
21
  import { FoliageTools } from './tools/foliage.js';
21
22
  import { DebugVisualizationTools } from './tools/debug.js';
22
23
  import { PerformanceTools } from './tools/performance.js';
@@ -27,8 +28,6 @@ import { SequenceTools } from './tools/sequence.js';
27
28
  import { IntrospectionTools } from './tools/introspection.js';
28
29
  import { VisualTools } from './tools/visual.js';
29
30
  import { EngineTools } from './tools/engine.js';
30
- import { toolDefinitions } from './tools/tool-definitions.js';
31
- import { handleToolCall } from './tools/tool-handlers.js';
32
31
  import { consolidatedToolDefinitions } from './tools/consolidated-tool-definitions.js';
33
32
  import { handleConsolidatedToolCall } from './tools/consolidated-tool-handlers.js';
34
33
  import { prompts } from './prompts/index.js';
@@ -51,16 +50,18 @@ const metrics = {
51
50
  uptime: Date.now(),
52
51
  recentErrors: []
53
52
  };
53
+ // Health check timer and last success tracking (stop pings after inactivity)
54
+ let healthCheckTimer;
55
+ let lastHealthSuccessAt = 0;
54
56
  // Configuration
55
57
  const CONFIG = {
56
- // Tool mode: true = consolidated (13 tools), false = individual (36+ tools)
57
- USE_CONSOLIDATED_TOOLS: process.env.USE_CONSOLIDATED_TOOLS !== 'false',
58
+ // Tooling: use consolidated tools only (13 tools)
58
59
  // Connection retry settings
59
60
  MAX_RETRY_ATTEMPTS: 3,
60
61
  RETRY_DELAY_MS: 2000,
61
62
  // Server info
62
63
  SERVER_NAME: 'unreal-engine-mcp',
63
- SERVER_VERSION: '0.3.0',
64
+ SERVER_VERSION: '0.4.0',
64
65
  // Monitoring
65
66
  HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
66
67
  };
@@ -84,11 +85,16 @@ function trackPerformance(startTime, success) {
84
85
  }
85
86
  // Health check function
86
87
  async function performHealthCheck(bridge) {
88
+ // If not connected, do not attempt any ping (stay quiet)
89
+ if (!bridge.isConnected) {
90
+ return false;
91
+ }
87
92
  try {
88
93
  // Use a safe echo command that doesn't affect any settings
89
94
  await bridge.executeConsoleCommand('echo MCP Server Health Check');
90
95
  metrics.connectionStatus = 'connected';
91
96
  metrics.lastHealthCheck = new Date();
97
+ lastHealthSuccessAt = Date.now();
92
98
  return true;
93
99
  }
94
100
  catch (err1) {
@@ -97,55 +103,80 @@ async function performHealthCheck(bridge) {
97
103
  await bridge.executePython("import sys; sys.stdout.write('OK')");
98
104
  metrics.connectionStatus = 'connected';
99
105
  metrics.lastHealthCheck = new Date();
106
+ lastHealthSuccessAt = Date.now();
100
107
  return true;
101
108
  }
102
109
  catch (err2) {
103
110
  metrics.connectionStatus = 'error';
104
111
  metrics.lastHealthCheck = new Date();
105
- log.warn('Health check failed (console and python):', err1, err2);
112
+ // Avoid noisy warnings when engine may be shutting down; log at debug
113
+ log.debug('Health check failed (console and python):', err1, err2);
106
114
  return false;
107
115
  }
108
116
  }
109
117
  }
110
118
  export async function createServer() {
111
119
  const bridge = new UnrealBridge();
120
+ // Disable auto-reconnect loops; connect only on-demand
121
+ bridge.setAutoReconnectEnabled(false);
112
122
  // Initialize response validation with schemas
113
- log.info('Initializing response validation...');
114
- const toolDefs = CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions;
123
+ log.debug('Initializing response validation...');
124
+ const toolDefs = consolidatedToolDefinitions;
115
125
  toolDefs.forEach((tool) => {
116
126
  if (tool.outputSchema) {
117
127
  responseValidator.registerSchema(tool.name, tool.outputSchema);
118
128
  }
119
129
  });
120
- log.info(`Registered ${responseValidator.getStats().totalSchemas} output schemas for validation`);
121
- // Connect to UE5 Remote Control with retries and timeout
122
- const connected = await bridge.tryConnect(CONFIG.MAX_RETRY_ATTEMPTS, 5000, // 5 second timeout per attempt
123
- CONFIG.RETRY_DELAY_MS);
124
- if (connected) {
125
- metrics.connectionStatus = 'connected';
126
- log.info('Successfully connected to Unreal Engine');
127
- }
128
- else {
129
- log.warn('Could not connect to Unreal Engine after retries');
130
- log.info('Server will start anyway - connection will be retried periodically');
131
- log.info('Make sure Unreal Engine is running with Remote Control enabled');
132
- metrics.connectionStatus = 'disconnected';
133
- // Schedule automatic reconnection attempts
134
- setInterval(async () => {
130
+ // Summary at debug level to avoid repeated noisy blocks in some shells
131
+ log.debug(`Registered ${responseValidator.getStats().totalSchemas} output schemas for validation`);
132
+ // Do NOT connect to Unreal at startup; connect on demand
133
+ log.debug('Server starting without connecting to Unreal Engine');
134
+ metrics.connectionStatus = 'disconnected';
135
+ // Health checks manager (only active when connected)
136
+ const startHealthChecks = () => {
137
+ if (healthCheckTimer)
138
+ return;
139
+ lastHealthSuccessAt = Date.now();
140
+ healthCheckTimer = setInterval(async () => {
141
+ // Only attempt health pings while connected; stay silent otherwise
135
142
  if (!bridge.isConnected) {
136
- log.info('Attempting to reconnect to Unreal Engine...');
137
- const reconnected = await bridge.tryConnect(1, 5000, 0); // Single attempt
138
- if (reconnected) {
139
- log.info('Reconnected to Unreal Engine successfully');
140
- metrics.connectionStatus = 'connected';
143
+ // Optionally pause fully after 5 minutes of no success
144
+ const FIVE_MIN_MS = 5 * 60 * 1000;
145
+ if (!lastHealthSuccessAt || Date.now() - lastHealthSuccessAt > FIVE_MIN_MS) {
146
+ if (healthCheckTimer) {
147
+ clearInterval(healthCheckTimer);
148
+ healthCheckTimer = undefined;
149
+ }
150
+ log.info('Health checks paused after 5 minutes without a successful response');
141
151
  }
152
+ return;
142
153
  }
143
- }, 10000); // Try every 10 seconds
144
- }
145
- // Start periodic health checks
146
- setInterval(() => {
147
- performHealthCheck(bridge);
148
- }, CONFIG.HEALTH_CHECK_INTERVAL_MS);
154
+ await performHealthCheck(bridge);
155
+ // Stop sending echoes if we haven't had a successful response in > 5 minutes
156
+ const FIVE_MIN_MS = 5 * 60 * 1000;
157
+ if (!lastHealthSuccessAt || Date.now() - lastHealthSuccessAt > FIVE_MIN_MS) {
158
+ if (healthCheckTimer) {
159
+ clearInterval(healthCheckTimer);
160
+ healthCheckTimer = undefined;
161
+ log.info('Health checks paused after 5 minutes without a successful response');
162
+ }
163
+ }
164
+ }, CONFIG.HEALTH_CHECK_INTERVAL_MS);
165
+ };
166
+ // On-demand connection helper
167
+ const ensureConnectedOnDemand = async () => {
168
+ if (bridge.isConnected)
169
+ return true;
170
+ const ok = await bridge.tryConnect(3, 5000, 1000);
171
+ if (ok) {
172
+ metrics.connectionStatus = 'connected';
173
+ startHealthChecks();
174
+ }
175
+ else {
176
+ metrics.connectionStatus = 'disconnected';
177
+ }
178
+ return ok;
179
+ };
149
180
  // Resources
150
181
  const assetResources = new AssetResources(bridge);
151
182
  const actorResources = new ActorResources(bridge);
@@ -163,6 +194,7 @@ export async function createServer() {
163
194
  const lightingTools = new LightingTools(bridge);
164
195
  const landscapeTools = new LandscapeTools(bridge);
165
196
  const foliageTools = new FoliageTools(bridge);
197
+ const buildEnvAdvanced = new BuildEnvironmentAdvanced(bridge);
166
198
  const debugTools = new DebugVisualizationTools(bridge);
167
199
  const performanceTools = new PerformanceTools(bridge);
168
200
  const audioTools = new AudioTools(bridge);
@@ -230,6 +262,10 @@ export async function createServer() {
230
262
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
231
263
  const uri = request.params.uri;
232
264
  if (uri === 'ue://assets') {
265
+ const ok = await ensureConnectedOnDemand();
266
+ if (!ok) {
267
+ return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
268
+ }
233
269
  const list = await assetResources.list('/Game', true);
234
270
  return {
235
271
  contents: [{
@@ -240,6 +276,10 @@ export async function createServer() {
240
276
  };
241
277
  }
242
278
  if (uri === 'ue://actors') {
279
+ const ok = await ensureConnectedOnDemand();
280
+ if (!ok) {
281
+ return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
282
+ }
243
283
  const list = await actorResources.listActors();
244
284
  return {
245
285
  contents: [{
@@ -250,6 +290,10 @@ export async function createServer() {
250
290
  };
251
291
  }
252
292
  if (uri === 'ue://level') {
293
+ const ok = await ensureConnectedOnDemand();
294
+ if (!ok) {
295
+ return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
296
+ }
253
297
  const level = await levelResources.getCurrentLevel();
254
298
  return {
255
299
  contents: [{
@@ -260,6 +304,10 @@ export async function createServer() {
260
304
  };
261
305
  }
262
306
  if (uri === 'ue://exposed') {
307
+ const ok = await ensureConnectedOnDemand();
308
+ if (!ok) {
309
+ return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
310
+ }
263
311
  try {
264
312
  const exposed = await bridge.getExposed();
265
313
  return {
@@ -282,17 +330,19 @@ export async function createServer() {
282
330
  }
283
331
  if (uri === 'ue://health') {
284
332
  const uptimeMs = Date.now() - metrics.uptime;
285
- // Query engine version and feature flags (best-effort)
333
+ // Query engine version and feature flags only when connected
286
334
  let versionInfo = {};
287
335
  let featureFlags = {};
288
- try {
289
- versionInfo = await bridge.getEngineVersion();
290
- }
291
- catch { }
292
- try {
293
- featureFlags = await bridge.getFeatureFlags();
336
+ if (bridge.isConnected) {
337
+ try {
338
+ versionInfo = await bridge.getEngineVersion();
339
+ }
340
+ catch { }
341
+ try {
342
+ featureFlags = await bridge.getFeatureFlags();
343
+ }
344
+ catch { }
294
345
  }
295
- catch { }
296
346
  const health = {
297
347
  status: metrics.connectionStatus,
298
348
  uptime: Math.floor(uptimeMs / 1000),
@@ -328,6 +378,10 @@ export async function createServer() {
328
378
  };
329
379
  }
330
380
  if (uri === 'ue://version') {
381
+ const ok = await ensureConnectedOnDemand();
382
+ if (!ok) {
383
+ return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
384
+ }
331
385
  const info = await bridge.getEngineVersion();
332
386
  return {
333
387
  contents: [{
@@ -339,17 +393,30 @@ export async function createServer() {
339
393
  }
340
394
  throw new Error(`Unknown resource: ${uri}`);
341
395
  });
342
- // Handle tool listing - switch between consolidated (13) or individual (36) tools
396
+ // Handle tool listing - consolidated tools only
343
397
  server.setRequestHandler(ListToolsRequestSchema, async () => {
344
- log.info(`Serving ${CONFIG.USE_CONSOLIDATED_TOOLS ? 'consolidated' : 'individual'} tools`);
398
+ log.info('Serving consolidated tools');
345
399
  return {
346
- tools: CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions
400
+ tools: consolidatedToolDefinitions
347
401
  };
348
402
  });
349
- // Handle tool calls - switch between consolidated (13) or individual (36) tools
403
+ // Handle tool calls - consolidated tools only (13)
350
404
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
351
405
  const { name, arguments: args } = request.params;
352
406
  const startTime = Date.now();
407
+ // Ensure connection only when needed, with 3 attempts
408
+ const connected = await ensureConnectedOnDemand();
409
+ if (!connected) {
410
+ const notConnected = {
411
+ content: [{ type: 'text', text: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.' }],
412
+ success: false,
413
+ error: 'UE_NOT_CONNECTED',
414
+ retriable: false,
415
+ scope: `tool-call/${name}`
416
+ };
417
+ trackPerformance(startTime, false);
418
+ return notConnected;
419
+ }
353
420
  // Create tools object for handler
354
421
  const tools = {
355
422
  actorTools,
@@ -364,6 +431,7 @@ export async function createServer() {
364
431
  lightingTools,
365
432
  landscapeTools,
366
433
  foliageTools,
434
+ buildEnvAdvanced,
367
435
  debugTools,
368
436
  performanceTools,
369
437
  audioTools,
@@ -373,18 +441,16 @@ export async function createServer() {
373
441
  introspectionTools,
374
442
  visualTools,
375
443
  engineTools,
444
+ // Resources for listing and info
445
+ assetResources,
446
+ actorResources,
447
+ levelResources,
376
448
  bridge
377
449
  };
378
- // Use consolidated or individual handler based on configuration
450
+ // Execute consolidated tool handler
379
451
  try {
380
452
  log.debug(`Executing tool: ${name}`);
381
- let result;
382
- if (CONFIG.USE_CONSOLIDATED_TOOLS) {
383
- result = await handleConsolidatedToolCall(name, args, tools);
384
- }
385
- else {
386
- result = await handleToolCall(name, args, tools);
387
- }
453
+ let result = await handleConsolidatedToolCall(name, args, tools);
388
454
  log.debug(`Tool ${name} returned result`);
389
455
  // Clean the result to remove circular references
390
456
  result = cleanObject(result);
@@ -25,25 +25,83 @@ export class ActorResources {
25
25
  // Use Python to get actors via EditorActorSubsystem
26
26
  try {
27
27
  const pythonCode = `
28
- import unreal
28
+ import unreal, json
29
29
  actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
30
- actors = actor_subsystem.get_all_level_actors()
30
+ actors = actor_subsystem.get_all_level_actors() if actor_subsystem else []
31
31
  actor_list = []
32
32
  for actor in actors:
33
- if actor:
34
- actor_list.append({
35
- 'name': actor.get_name(),
36
- 'class': actor.get_class().get_name(),
37
- 'path': actor.get_path_name()
38
- })
39
- print(f"Found {len(actor_list)} actors")
33
+ try:
34
+ if actor:
35
+ actor_list.append({
36
+ 'name': actor.get_name(),
37
+ 'class': actor.get_class().get_name(),
38
+ 'path': actor.get_path_name()
39
+ })
40
+ except Exception:
41
+ pass
42
+ print('RESULT:' + json.dumps({'success': True, 'count': len(actor_list), 'actors': actor_list}))
40
43
  `.trim();
41
- const result = await this.bridge.executePython(pythonCode);
42
- this.setCache('listActors', result);
43
- return result;
44
+ const resp = await this.bridge.executePythonWithResult(pythonCode);
45
+ if (resp && typeof resp === 'object' && resp.success === true && Array.isArray(resp.actors)) {
46
+ this.setCache('listActors', resp);
47
+ return resp;
48
+ }
49
+ // Fallback manual extraction with bracket matching
50
+ const raw = await this.bridge.executePython(pythonCode);
51
+ let output = '';
52
+ if (raw?.LogOutput && Array.isArray(raw.LogOutput))
53
+ output = raw.LogOutput.map((l) => l.Output || '').join('');
54
+ else if (typeof raw === 'string')
55
+ output = raw;
56
+ else
57
+ output = JSON.stringify(raw);
58
+ const marker = 'RESULT:';
59
+ const idx = output.lastIndexOf(marker);
60
+ if (idx !== -1) {
61
+ let i = idx + marker.length;
62
+ while (i < output.length && output[i] !== '{')
63
+ i++;
64
+ if (i < output.length) {
65
+ let depth = 0, inStr = false, esc = false, j = i;
66
+ for (; j < output.length; j++) {
67
+ const ch = output[j];
68
+ if (esc) {
69
+ esc = false;
70
+ continue;
71
+ }
72
+ if (ch === '\\') {
73
+ esc = true;
74
+ continue;
75
+ }
76
+ if (ch === '"') {
77
+ inStr = !inStr;
78
+ continue;
79
+ }
80
+ if (!inStr) {
81
+ if (ch === '{')
82
+ depth++;
83
+ else if (ch === '}') {
84
+ depth--;
85
+ if (depth === 0) {
86
+ j++;
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ const jsonStr = output.slice(i, j);
93
+ try {
94
+ const parsed = JSON.parse(jsonStr);
95
+ this.setCache('listActors', parsed);
96
+ return parsed;
97
+ }
98
+ catch { }
99
+ }
100
+ }
101
+ return { success: false, error: 'Failed to parse actors list' };
44
102
  }
45
103
  catch (err) {
46
- return { error: `Failed to list actors: ${err}` };
104
+ return { success: false, error: `Failed to list actors: ${err}` };
47
105
  }
48
106
  }
49
107
  async getActorByName(actorName) {
@@ -5,6 +5,7 @@ export declare class AssetResources {
5
5
  private cache;
6
6
  private get ttlMs();
7
7
  private makeKey;
8
+ private normalizeDir;
8
9
  list(dir?: string, _recursive?: boolean, limit?: number): Promise<any>;
9
10
  /**
10
11
  * List assets with pagination support
@@ -14,8 +15,8 @@ export declare class AssetResources {
14
15
  */
15
16
  listPaged(dir?: string, page?: number, pageSize?: number, recursive?: boolean): Promise<any>;
16
17
  /**
17
- * Directory-based listing for paths with too many assets
18
- * Shows only immediate children (folders and files) to avoid timeouts
18
+ * Directory-based listing of immediate children using AssetRegistry.
19
+ * Returns both subfolders and assets at the given path.
19
20
  */
20
21
  private listDirectoryOnly;
21
22
  find(assetPath: string): Promise<boolean>;