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
|
@@ -27,7 +27,8 @@ export class ResponseValidator {
|
|
|
27
27
|
try {
|
|
28
28
|
const validator = this.ajv.compile(outputSchema);
|
|
29
29
|
this.validators.set(toolName, validator);
|
|
30
|
-
|
|
30
|
+
// Demote per-tool schema registration to debug to reduce log noise
|
|
31
|
+
log.debug(`Registered output schema for tool: ${toolName}`);
|
|
31
32
|
}
|
|
32
33
|
catch (_error) {
|
|
33
34
|
log.error(`Failed to compile output schema for ${toolName}:`, _error);
|
|
@@ -75,14 +76,17 @@ export class ResponseValidator {
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Wrap a tool response with validation
|
|
79
|
+
* Wrap a tool response with validation and MCP-compliant content shape.
|
|
80
|
+
*
|
|
81
|
+
* MCP tools/call responses must contain a `content` array. Many internal
|
|
82
|
+
* handlers return structured JSON objects (e.g., { success, message, ... }).
|
|
83
|
+
* This wrapper serializes such objects into a single text block while keeping
|
|
84
|
+
* existing `content` responses intact.
|
|
79
85
|
*/
|
|
80
86
|
wrapResponse(toolName, response) {
|
|
81
87
|
// Ensure response is safe to serialize first
|
|
82
88
|
try {
|
|
83
|
-
// The response should already be cleaned, but double-check
|
|
84
89
|
if (response && typeof response === 'object') {
|
|
85
|
-
// Make sure we can serialize it
|
|
86
90
|
JSON.stringify(response);
|
|
87
91
|
}
|
|
88
92
|
}
|
|
@@ -90,21 +94,45 @@ export class ResponseValidator {
|
|
|
90
94
|
log.error(`Response for ${toolName} contains circular references, cleaning...`);
|
|
91
95
|
response = cleanObject(response);
|
|
92
96
|
}
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
// If handler already returned MCP content, keep it as-is (still validate)
|
|
98
|
+
const alreadyMcpShaped = response && typeof response === 'object' && Array.isArray(response.content);
|
|
99
|
+
// Choose the payload to validate: if already MCP-shaped, validate the
|
|
100
|
+
// structured content extracted from text; otherwise validate the object directly.
|
|
101
|
+
const validationTarget = alreadyMcpShaped ? response : response;
|
|
102
|
+
const validation = this.validateResponse(toolName, validationTarget);
|
|
95
103
|
if (!validation.valid) {
|
|
96
104
|
log.warn(`Tool ${toolName} response validation failed:`, validation.errors);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
}
|
|
106
|
+
// If it's already MCP-shaped, return as-is (optionally append validation meta)
|
|
107
|
+
if (alreadyMcpShaped) {
|
|
108
|
+
if (!validation.valid) {
|
|
109
|
+
try {
|
|
110
|
+
response._validation = { valid: false, errors: validation.errors };
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
103
113
|
}
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
// Otherwise, wrap structured result into MCP content
|
|
117
|
+
let text;
|
|
118
|
+
try {
|
|
119
|
+
// Pretty-print small objects for readability
|
|
120
|
+
text = typeof response === 'string'
|
|
121
|
+
? response
|
|
122
|
+
: JSON.stringify(response ?? { success: true }, null, 2);
|
|
123
|
+
}
|
|
124
|
+
catch (_e) {
|
|
125
|
+
text = String(response);
|
|
126
|
+
}
|
|
127
|
+
const wrapped = {
|
|
128
|
+
content: [
|
|
129
|
+
{ type: 'text', text }
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
if (!validation.valid) {
|
|
133
|
+
wrapped._validation = { valid: false, errors: validation.errors };
|
|
104
134
|
}
|
|
105
|
-
|
|
106
|
-
// Adding it can cause circular references
|
|
107
|
-
return response;
|
|
135
|
+
return wrapped;
|
|
108
136
|
}
|
|
109
137
|
/**
|
|
110
138
|
* Get validation statistics
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unreal-engine-mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Production-ready MCP server for Unreal Engine integration
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Production-ready MCP server for Unreal Engine integration using consolidated tools only",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"unreal-mcp-server": "dist/cli.js"
|
|
9
|
+
"unreal-engine-mcp-server": "dist/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.json",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"mcpName": "io.github.ChiR24/unreal-engine-mcp",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
38
38
|
"ajv": "^8.12.0",
|
|
39
|
-
"axios": "^1.
|
|
39
|
+
"axios": "^1.12.2",
|
|
40
40
|
"dotenv": "^16.4.5",
|
|
41
41
|
"ws": "^8.18.0",
|
|
42
42
|
"yargs": "^17.7.2",
|
package/server.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
|
|
3
3
|
"name": "io.github.ChiR24/unreal-engine-mcp",
|
|
4
4
|
"description": "Production-ready MCP server for Unreal Engine with comprehensive game development tools",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0",
|
|
6
6
|
"packages": [
|
|
7
7
|
{
|
|
8
8
|
"registry_type": "npm",
|
|
9
9
|
"registry_base_url": "https://registry.npmjs.org",
|
|
10
10
|
"identifier": "unreal-engine-mcp-server",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.4.0",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|
package/src/index.ts
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';
|
|
@@ -75,16 +74,19 @@ const metrics: PerformanceMetrics = {
|
|
|
75
74
|
recentErrors: []
|
|
76
75
|
};
|
|
77
76
|
|
|
77
|
+
// Health check timer and last success tracking (stop pings after inactivity)
|
|
78
|
+
let healthCheckTimer: NodeJS.Timeout | undefined;
|
|
79
|
+
let lastHealthSuccessAt = 0;
|
|
80
|
+
|
|
78
81
|
// Configuration
|
|
79
82
|
const CONFIG = {
|
|
80
|
-
//
|
|
81
|
-
USE_CONSOLIDATED_TOOLS: process.env.USE_CONSOLIDATED_TOOLS !== 'false',
|
|
83
|
+
// Tooling: use consolidated tools only (13 tools)
|
|
82
84
|
// Connection retry settings
|
|
83
85
|
MAX_RETRY_ATTEMPTS: 3,
|
|
84
86
|
RETRY_DELAY_MS: 2000,
|
|
85
87
|
// Server info
|
|
86
88
|
SERVER_NAME: 'unreal-engine-mcp',
|
|
87
|
-
SERVER_VERSION: '0.
|
|
89
|
+
SERVER_VERSION: '0.4.0',
|
|
88
90
|
// Monitoring
|
|
89
91
|
HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
|
|
90
92
|
};
|
|
@@ -111,11 +113,16 @@ function trackPerformance(startTime: number, success: boolean) {
|
|
|
111
113
|
|
|
112
114
|
// Health check function
|
|
113
115
|
async function performHealthCheck(bridge: UnrealBridge): Promise<boolean> {
|
|
116
|
+
// If not connected, do not attempt any ping (stay quiet)
|
|
117
|
+
if (!bridge.isConnected) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
114
120
|
try {
|
|
115
121
|
// Use a safe echo command that doesn't affect any settings
|
|
116
122
|
await bridge.executeConsoleCommand('echo MCP Server Health Check');
|
|
117
123
|
metrics.connectionStatus = 'connected';
|
|
118
124
|
metrics.lastHealthCheck = new Date();
|
|
125
|
+
lastHealthSuccessAt = Date.now();
|
|
119
126
|
return true;
|
|
120
127
|
} catch (err1) {
|
|
121
128
|
// Fallback: minimal Python ping (if Python plugin is enabled)
|
|
@@ -123,11 +130,13 @@ async function performHealthCheck(bridge: UnrealBridge): Promise<boolean> {
|
|
|
123
130
|
await bridge.executePython("import sys; sys.stdout.write('OK')");
|
|
124
131
|
metrics.connectionStatus = 'connected';
|
|
125
132
|
metrics.lastHealthCheck = new Date();
|
|
133
|
+
lastHealthSuccessAt = Date.now();
|
|
126
134
|
return true;
|
|
127
135
|
} catch (err2) {
|
|
128
136
|
metrics.connectionStatus = 'error';
|
|
129
137
|
metrics.lastHealthCheck = new Date();
|
|
130
|
-
|
|
138
|
+
// Avoid noisy warnings when engine may be shutting down; log at debug
|
|
139
|
+
log.debug('Health check failed (console and python):', err1, err2);
|
|
131
140
|
return false;
|
|
132
141
|
}
|
|
133
142
|
}
|
|
@@ -135,50 +144,68 @@ async function performHealthCheck(bridge: UnrealBridge): Promise<boolean> {
|
|
|
135
144
|
|
|
136
145
|
export async function createServer() {
|
|
137
146
|
const bridge = new UnrealBridge();
|
|
147
|
+
// Disable auto-reconnect loops; connect only on-demand
|
|
148
|
+
bridge.setAutoReconnectEnabled(false);
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
log.
|
|
141
|
-
const toolDefs =
|
|
150
|
+
// Initialize response validation with schemas
|
|
151
|
+
log.debug('Initializing response validation...');
|
|
152
|
+
const toolDefs = consolidatedToolDefinitions;
|
|
142
153
|
toolDefs.forEach((tool: any) => {
|
|
143
154
|
if (tool.outputSchema) {
|
|
144
155
|
responseValidator.registerSchema(tool.name, tool.outputSchema);
|
|
145
156
|
}
|
|
146
157
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
log.warn('Could not connect to Unreal Engine after retries');
|
|
161
|
-
log.info('Server will start anyway - connection will be retried periodically');
|
|
162
|
-
log.info('Make sure Unreal Engine is running with Remote Control enabled');
|
|
163
|
-
metrics.connectionStatus = 'disconnected';
|
|
164
|
-
|
|
165
|
-
// Schedule automatic reconnection attempts
|
|
166
|
-
setInterval(async () => {
|
|
158
|
+
// Summary at debug level to avoid repeated noisy blocks in some shells
|
|
159
|
+
log.debug(`Registered ${responseValidator.getStats().totalSchemas} output schemas for validation`);
|
|
160
|
+
|
|
161
|
+
// Do NOT connect to Unreal at startup; connect on demand
|
|
162
|
+
log.debug('Server starting without connecting to Unreal Engine');
|
|
163
|
+
metrics.connectionStatus = 'disconnected';
|
|
164
|
+
|
|
165
|
+
// Health checks manager (only active when connected)
|
|
166
|
+
const startHealthChecks = () => {
|
|
167
|
+
if (healthCheckTimer) return;
|
|
168
|
+
lastHealthSuccessAt = Date.now();
|
|
169
|
+
healthCheckTimer = setInterval(async () => {
|
|
170
|
+
// Only attempt health pings while connected; stay silent otherwise
|
|
167
171
|
if (!bridge.isConnected) {
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
// Optionally pause fully after 5 minutes of no success
|
|
173
|
+
const FIVE_MIN_MS = 5 * 60 * 1000;
|
|
174
|
+
if (!lastHealthSuccessAt || Date.now() - lastHealthSuccessAt > FIVE_MIN_MS) {
|
|
175
|
+
if (healthCheckTimer) {
|
|
176
|
+
clearInterval(healthCheckTimer);
|
|
177
|
+
healthCheckTimer = undefined;
|
|
178
|
+
}
|
|
179
|
+
log.info('Health checks paused after 5 minutes without a successful response');
|
|
173
180
|
}
|
|
181
|
+
return;
|
|
174
182
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
|
|
184
|
+
await performHealthCheck(bridge);
|
|
185
|
+
// Stop sending echoes if we haven't had a successful response in > 5 minutes
|
|
186
|
+
const FIVE_MIN_MS = 5 * 60 * 1000;
|
|
187
|
+
if (!lastHealthSuccessAt || Date.now() - lastHealthSuccessAt > FIVE_MIN_MS) {
|
|
188
|
+
if (healthCheckTimer) {
|
|
189
|
+
clearInterval(healthCheckTimer);
|
|
190
|
+
healthCheckTimer = undefined;
|
|
191
|
+
log.info('Health checks paused after 5 minutes without a successful response');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}, CONFIG.HEALTH_CHECK_INTERVAL_MS);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// On-demand connection helper
|
|
198
|
+
const ensureConnectedOnDemand = async (): Promise<boolean> => {
|
|
199
|
+
if (bridge.isConnected) return true;
|
|
200
|
+
const ok = await bridge.tryConnect(3, 5000, 1000);
|
|
201
|
+
if (ok) {
|
|
202
|
+
metrics.connectionStatus = 'connected';
|
|
203
|
+
startHealthChecks();
|
|
204
|
+
} else {
|
|
205
|
+
metrics.connectionStatus = 'disconnected';
|
|
206
|
+
}
|
|
207
|
+
return ok;
|
|
208
|
+
};
|
|
182
209
|
|
|
183
210
|
// Resources
|
|
184
211
|
const assetResources = new AssetResources(bridge);
|
|
@@ -198,6 +225,7 @@ export async function createServer() {
|
|
|
198
225
|
const lightingTools = new LightingTools(bridge);
|
|
199
226
|
const landscapeTools = new LandscapeTools(bridge);
|
|
200
227
|
const foliageTools = new FoliageTools(bridge);
|
|
228
|
+
const buildEnvAdvanced = new BuildEnvironmentAdvanced(bridge);
|
|
201
229
|
const debugTools = new DebugVisualizationTools(bridge);
|
|
202
230
|
const performanceTools = new PerformanceTools(bridge);
|
|
203
231
|
const audioTools = new AudioTools(bridge);
|
|
@@ -272,6 +300,10 @@ export async function createServer() {
|
|
|
272
300
|
const uri = request.params.uri;
|
|
273
301
|
|
|
274
302
|
if (uri === 'ue://assets') {
|
|
303
|
+
const ok = await ensureConnectedOnDemand();
|
|
304
|
+
if (!ok) {
|
|
305
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
|
|
306
|
+
}
|
|
275
307
|
const list = await assetResources.list('/Game', true);
|
|
276
308
|
return {
|
|
277
309
|
contents: [{
|
|
@@ -283,6 +315,10 @@ export async function createServer() {
|
|
|
283
315
|
}
|
|
284
316
|
|
|
285
317
|
if (uri === 'ue://actors') {
|
|
318
|
+
const ok = await ensureConnectedOnDemand();
|
|
319
|
+
if (!ok) {
|
|
320
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
|
|
321
|
+
}
|
|
286
322
|
const list = await actorResources.listActors();
|
|
287
323
|
return {
|
|
288
324
|
contents: [{
|
|
@@ -294,6 +330,10 @@ export async function createServer() {
|
|
|
294
330
|
}
|
|
295
331
|
|
|
296
332
|
if (uri === 'ue://level') {
|
|
333
|
+
const ok = await ensureConnectedOnDemand();
|
|
334
|
+
if (!ok) {
|
|
335
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
|
|
336
|
+
}
|
|
297
337
|
const level = await levelResources.getCurrentLevel();
|
|
298
338
|
return {
|
|
299
339
|
contents: [{
|
|
@@ -305,6 +345,10 @@ export async function createServer() {
|
|
|
305
345
|
}
|
|
306
346
|
|
|
307
347
|
if (uri === 'ue://exposed') {
|
|
348
|
+
const ok = await ensureConnectedOnDemand();
|
|
349
|
+
if (!ok) {
|
|
350
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
|
|
351
|
+
}
|
|
308
352
|
try {
|
|
309
353
|
const exposed = await bridge.getExposed();
|
|
310
354
|
return {
|
|
@@ -327,11 +371,13 @@ export async function createServer() {
|
|
|
327
371
|
|
|
328
372
|
if (uri === 'ue://health') {
|
|
329
373
|
const uptimeMs = Date.now() - metrics.uptime;
|
|
330
|
-
// Query engine version and feature flags
|
|
374
|
+
// Query engine version and feature flags only when connected
|
|
331
375
|
let versionInfo: any = {};
|
|
332
376
|
let featureFlags: any = {};
|
|
333
|
-
|
|
334
|
-
|
|
377
|
+
if (bridge.isConnected) {
|
|
378
|
+
try { versionInfo = await bridge.getEngineVersion(); } catch {}
|
|
379
|
+
try { featureFlags = await bridge.getFeatureFlags(); } catch {}
|
|
380
|
+
}
|
|
335
381
|
const health = {
|
|
336
382
|
status: metrics.connectionStatus,
|
|
337
383
|
uptime: Math.floor(uptimeMs / 1000),
|
|
@@ -369,6 +415,10 @@ export async function createServer() {
|
|
|
369
415
|
}
|
|
370
416
|
|
|
371
417
|
if (uri === 'ue://version') {
|
|
418
|
+
const ok = await ensureConnectedOnDemand();
|
|
419
|
+
if (!ok) {
|
|
420
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: 'Unreal Engine not connected (after 3 attempts).' }] };
|
|
421
|
+
}
|
|
372
422
|
const info = await bridge.getEngineVersion();
|
|
373
423
|
return {
|
|
374
424
|
contents: [{
|
|
@@ -382,20 +432,34 @@ export async function createServer() {
|
|
|
382
432
|
throw new Error(`Unknown resource: ${uri}`);
|
|
383
433
|
});
|
|
384
434
|
|
|
385
|
-
|
|
435
|
+
// Handle tool listing - consolidated tools only
|
|
386
436
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
387
|
-
log.info(
|
|
437
|
+
log.info('Serving consolidated tools');
|
|
388
438
|
return {
|
|
389
|
-
tools:
|
|
439
|
+
tools: consolidatedToolDefinitions
|
|
390
440
|
};
|
|
391
441
|
});
|
|
392
442
|
|
|
393
|
-
// Handle tool calls -
|
|
443
|
+
// Handle tool calls - consolidated tools only (13)
|
|
394
444
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
395
445
|
const { name, arguments: args } = request.params;
|
|
396
446
|
const startTime = Date.now();
|
|
447
|
+
|
|
448
|
+
// Ensure connection only when needed, with 3 attempts
|
|
449
|
+
const connected = await ensureConnectedOnDemand();
|
|
450
|
+
if (!connected) {
|
|
451
|
+
const notConnected = {
|
|
452
|
+
content: [{ type: 'text', text: 'Unreal Engine is not connected (after 3 attempts). Please open UE and try again.' }],
|
|
453
|
+
success: false,
|
|
454
|
+
error: 'UE_NOT_CONNECTED',
|
|
455
|
+
retriable: false,
|
|
456
|
+
scope: `tool-call/${name}`
|
|
457
|
+
} as any;
|
|
458
|
+
trackPerformance(startTime, false);
|
|
459
|
+
return notConnected;
|
|
460
|
+
}
|
|
397
461
|
|
|
398
|
-
|
|
462
|
+
// Create tools object for handler
|
|
399
463
|
const tools = {
|
|
400
464
|
actorTools,
|
|
401
465
|
assetTools,
|
|
@@ -409,6 +473,7 @@ export async function createServer() {
|
|
|
409
473
|
lightingTools,
|
|
410
474
|
landscapeTools,
|
|
411
475
|
foliageTools,
|
|
476
|
+
buildEnvAdvanced,
|
|
412
477
|
debugTools,
|
|
413
478
|
performanceTools,
|
|
414
479
|
audioTools,
|
|
@@ -418,19 +483,18 @@ export async function createServer() {
|
|
|
418
483
|
introspectionTools,
|
|
419
484
|
visualTools,
|
|
420
485
|
engineTools,
|
|
486
|
+
// Resources for listing and info
|
|
487
|
+
assetResources,
|
|
488
|
+
actorResources,
|
|
489
|
+
levelResources,
|
|
421
490
|
bridge
|
|
422
491
|
};
|
|
423
492
|
|
|
424
|
-
|
|
493
|
+
// Execute consolidated tool handler
|
|
425
494
|
try {
|
|
426
495
|
log.debug(`Executing tool: ${name}`);
|
|
427
496
|
|
|
428
|
-
let result;
|
|
429
|
-
if (CONFIG.USE_CONSOLIDATED_TOOLS) {
|
|
430
|
-
result = await handleConsolidatedToolCall(name, args, tools);
|
|
431
|
-
} else {
|
|
432
|
-
result = await handleToolCall(name, args, tools);
|
|
433
|
-
}
|
|
497
|
+
let result = await handleConsolidatedToolCall(name, args, tools);
|
|
434
498
|
|
|
435
499
|
log.debug(`Tool ${name} returned result`);
|
|
436
500
|
|
package/src/resources/actors.ts
CHANGED
|
@@ -34,25 +34,63 @@ export class ActorResources {
|
|
|
34
34
|
// Use Python to get actors via EditorActorSubsystem
|
|
35
35
|
try {
|
|
36
36
|
const pythonCode = `
|
|
37
|
-
import unreal
|
|
37
|
+
import unreal, json
|
|
38
38
|
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
39
|
-
actors = actor_subsystem.get_all_level_actors()
|
|
39
|
+
actors = actor_subsystem.get_all_level_actors() if actor_subsystem else []
|
|
40
40
|
actor_list = []
|
|
41
41
|
for actor in actors:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
try:
|
|
43
|
+
if actor:
|
|
44
|
+
actor_list.append({
|
|
45
|
+
'name': actor.get_name(),
|
|
46
|
+
'class': actor.get_class().get_name(),
|
|
47
|
+
'path': actor.get_path_name()
|
|
48
|
+
})
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
print('RESULT:' + json.dumps({'success': True, 'count': len(actor_list), 'actors': actor_list}))
|
|
49
52
|
`.trim();
|
|
50
53
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const resp = await this.bridge.executePythonWithResult(pythonCode);
|
|
55
|
+
if (resp && typeof resp === 'object' && resp.success === true && Array.isArray((resp as any).actors)) {
|
|
56
|
+
this.setCache('listActors', resp);
|
|
57
|
+
return resp;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Fallback manual extraction with bracket matching
|
|
61
|
+
const raw = await this.bridge.executePython(pythonCode);
|
|
62
|
+
let output = '';
|
|
63
|
+
if (raw?.LogOutput && Array.isArray(raw.LogOutput)) output = raw.LogOutput.map((l: any) => l.Output || '').join('');
|
|
64
|
+
else if (typeof raw === 'string') output = raw; else output = JSON.stringify(raw);
|
|
65
|
+
const marker = 'RESULT:';
|
|
66
|
+
const idx = output.lastIndexOf(marker);
|
|
67
|
+
if (idx !== -1) {
|
|
68
|
+
let i = idx + marker.length;
|
|
69
|
+
while (i < output.length && output[i] !== '{') i++;
|
|
70
|
+
if (i < output.length) {
|
|
71
|
+
let depth = 0, inStr = false, esc = false, j = i;
|
|
72
|
+
for (; j < output.length; j++) {
|
|
73
|
+
const ch = output[j];
|
|
74
|
+
if (esc) { esc = false; continue; }
|
|
75
|
+
if (ch === '\\') { esc = true; continue; }
|
|
76
|
+
if (ch === '"') { inStr = !inStr; continue; }
|
|
77
|
+
if (!inStr) {
|
|
78
|
+
if (ch === '{') depth++;
|
|
79
|
+
else if (ch === '}') { depth--; if (depth === 0) { j++; break; } }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const jsonStr = output.slice(i, j);
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(jsonStr);
|
|
85
|
+
this.setCache('listActors', parsed);
|
|
86
|
+
return parsed;
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { success: false, error: 'Failed to parse actors list' };
|
|
54
92
|
} catch (err) {
|
|
55
|
-
return { error: `Failed to list actors: ${err}` };
|
|
93
|
+
return { success: false, error: `Failed to list actors: ${err}` };
|
|
56
94
|
}
|
|
57
95
|
}
|
|
58
96
|
|