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.
- package/.env.production +6 -1
- package/Dockerfile +11 -28
- package/README.md +1 -2
- package/dist/index.js +120 -54
- package/dist/resources/actors.js +71 -13
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +96 -72
- package/dist/resources/levels.js +2 -2
- package/dist/tools/assets.js +6 -2
- package/dist/tools/build_environment_advanced.js +46 -42
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +173 -8
- package/dist/tools/consolidated-tool-handlers.js +331 -718
- package/dist/tools/debug.js +4 -6
- package/dist/tools/rc.js +2 -2
- package/dist/tools/sequence.js +21 -2
- package/dist/unreal-bridge.d.ts +4 -1
- package/dist/unreal-bridge.js +211 -53
- package/dist/utils/http.js +4 -2
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +43 -15
- package/package.json +5 -5
- package/server.json +2 -2
- package/src/index.ts +120 -56
- package/src/resources/actors.ts +51 -13
- package/src/resources/assets.ts +97 -73
- package/src/resources/levels.ts +2 -2
- package/src/tools/assets.ts +6 -2
- package/src/tools/build_environment_advanced.ts +46 -42
- package/src/tools/consolidated-tool-definitions.ts +173 -8
- package/src/tools/consolidated-tool-handlers.ts +318 -747
- package/src/tools/debug.ts +4 -6
- package/src/tools/rc.ts +2 -2
- package/src/tools/sequence.ts +21 -2
- package/src/unreal-bridge.ts +163 -60
- package/src/utils/http.ts +7 -4
- package/src/utils/response-validator.ts +48 -19
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1007
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/src/tools/tool-definitions.ts +0 -1023
- 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.
|
|
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:
|
|
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:
|
|
21
|
+
FROM cgr.dev/chainguard/node:latest
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
RUN apk add --no-cache dumb-init
|
|
23
|
+
ENV NODE_ENV=production
|
|
25
24
|
|
|
26
|
-
#
|
|
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
|
|
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
|
-
#
|
|
54
|
-
|
|
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
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
|
|
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.
|
|
114
|
-
const toolDefs =
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
333
|
+
// Query engine version and feature flags only when connected
|
|
286
334
|
let versionInfo = {};
|
|
287
335
|
let featureFlags = {};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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 -
|
|
396
|
+
// Handle tool listing - consolidated tools only
|
|
343
397
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
344
|
-
log.info(
|
|
398
|
+
log.info('Serving consolidated tools');
|
|
345
399
|
return {
|
|
346
|
-
tools:
|
|
400
|
+
tools: consolidatedToolDefinitions
|
|
347
401
|
};
|
|
348
402
|
});
|
|
349
|
-
// Handle tool calls -
|
|
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
|
-
//
|
|
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);
|
package/dist/resources/actors.js
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
18
|
-
*
|
|
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>;
|