unreal-engine-mcp-server 0.2.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.
Files changed (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
package/src/index.ts ADDED
@@ -0,0 +1,543 @@
1
+ import 'dotenv/config';
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { Logger } from './utils/logger.js';
5
+ import { UnrealBridge } from './unreal-bridge.js';
6
+ import { AssetResources } from './resources/assets.js';
7
+ import { ActorResources } from './resources/actors.js';
8
+ import { LevelResources } from './resources/levels.js';
9
+ import { ActorTools } from './tools/actors.js';
10
+ import { AssetTools } from './tools/assets.js';
11
+ import { EditorTools } from './tools/editor.js';
12
+ import { MaterialTools } from './tools/materials.js';
13
+ import { AnimationTools } from './tools/animation.js';
14
+ import { PhysicsTools } from './tools/physics.js';
15
+ import { NiagaraTools } from './tools/niagara.js';
16
+ import { BlueprintTools } from './tools/blueprint.js';
17
+ import { LevelTools } from './tools/level.js';
18
+ import { LightingTools } from './tools/lighting.js';
19
+ import { LandscapeTools } from './tools/landscape.js';
20
+ import { FoliageTools } from './tools/foliage.js';
21
+ import { DebugVisualizationTools } from './tools/debug.js';
22
+ import { PerformanceTools } from './tools/performance.js';
23
+ import { AudioTools } from './tools/audio.js';
24
+ import { UITools } from './tools/ui.js';
25
+ import { RcTools } from './tools/rc.js';
26
+ import { SequenceTools } from './tools/sequence.js';
27
+ import { IntrospectionTools } from './tools/introspection.js';
28
+ import { VisualTools } from './tools/visual.js';
29
+ import { EngineTools } from './tools/engine.js';
30
+ import { toolDefinitions } from './tools/tool-definitions.js';
31
+ import { handleToolCall } from './tools/tool-handlers.js';
32
+ import { consolidatedToolDefinitions } from './tools/consolidated-tool-definitions.js';
33
+ import { handleConsolidatedToolCall } from './tools/consolidated-tool-handlers.js';
34
+ import { prompts } from './prompts/index.js';
35
+ import {
36
+ CallToolRequestSchema,
37
+ ListToolsRequestSchema,
38
+ ListResourcesRequestSchema,
39
+ ReadResourceRequestSchema,
40
+ ListPromptsRequestSchema,
41
+ GetPromptRequestSchema
42
+ } from '@modelcontextprotocol/sdk/types.js';
43
+ import { responseValidator } from './utils/response-validator.js';
44
+ import { ErrorHandler } from './utils/error-handler.js';
45
+ import { routeStdoutLogsToStderr } from './utils/stdio-redirect.js';
46
+ import { sanitizeResponse, cleanObject } from './utils/safe-json.js';
47
+
48
+ const log = new Logger('UE-MCP');
49
+
50
+ // Ensure stdout remains JSON-only for MCP by routing logs to stderr unless opted out.
51
+ routeStdoutLogsToStderr();
52
+
53
+ // Performance metrics
54
+ interface PerformanceMetrics {
55
+ totalRequests: number;
56
+ successfulRequests: number;
57
+ failedRequests: number;
58
+ averageResponseTime: number;
59
+ responseTimes: number[];
60
+ connectionStatus: 'connected' | 'disconnected' | 'error';
61
+ lastHealthCheck: Date;
62
+ uptime: number;
63
+ recentErrors: Array<{ time: string; scope: string; type: string; message: string; retriable: boolean }>;
64
+ }
65
+
66
+ const metrics: PerformanceMetrics = {
67
+ totalRequests: 0,
68
+ successfulRequests: 0,
69
+ failedRequests: 0,
70
+ averageResponseTime: 0,
71
+ responseTimes: [],
72
+ connectionStatus: 'disconnected',
73
+ lastHealthCheck: new Date(),
74
+ uptime: Date.now(),
75
+ recentErrors: []
76
+ };
77
+
78
+ // Configuration
79
+ const CONFIG = {
80
+ // Tool mode: true = consolidated (10 tools), false = individual (36+ tools)
81
+ USE_CONSOLIDATED_TOOLS: process.env.USE_CONSOLIDATED_TOOLS !== 'false',
82
+ // Connection retry settings
83
+ MAX_RETRY_ATTEMPTS: 3,
84
+ RETRY_DELAY_MS: 2000,
85
+ // Server info
86
+ SERVER_NAME: 'unreal-engine-mcp',
87
+ SERVER_VERSION: '0.2.1',
88
+ // Monitoring
89
+ HEALTH_CHECK_INTERVAL_MS: 30000 // 30 seconds
90
+ };
91
+
92
+ // Helper function to track performance
93
+ function trackPerformance(startTime: number, success: boolean) {
94
+ const responseTime = Date.now() - startTime;
95
+ metrics.totalRequests++;
96
+ if (success) {
97
+ metrics.successfulRequests++;
98
+ } else {
99
+ metrics.failedRequests++;
100
+ }
101
+
102
+ // Keep last 100 response times for average calculation
103
+ metrics.responseTimes.push(responseTime);
104
+ if (metrics.responseTimes.length > 100) {
105
+ metrics.responseTimes.shift();
106
+ }
107
+
108
+ // Calculate average
109
+ metrics.averageResponseTime = metrics.responseTimes.reduce((a, b) => a + b, 0) / metrics.responseTimes.length;
110
+ }
111
+
112
+ // Health check function
113
+ async function performHealthCheck(bridge: UnrealBridge): Promise<boolean> {
114
+ try {
115
+ // Use a safe echo command that doesn't affect any settings
116
+ await bridge.executeConsoleCommand('echo MCP Server Health Check');
117
+ metrics.connectionStatus = 'connected';
118
+ metrics.lastHealthCheck = new Date();
119
+ return true;
120
+ } catch (err1) {
121
+ // Fallback: minimal Python ping (if Python plugin is enabled)
122
+ try {
123
+ await bridge.executePython("import sys; sys.stdout.write('OK')");
124
+ metrics.connectionStatus = 'connected';
125
+ metrics.lastHealthCheck = new Date();
126
+ return true;
127
+ } catch (err2) {
128
+ metrics.connectionStatus = 'error';
129
+ metrics.lastHealthCheck = new Date();
130
+ log.warn('Health check failed (console and python):', err1, err2);
131
+ return false;
132
+ }
133
+ }
134
+ }
135
+
136
+ export async function createServer() {
137
+ const bridge = new UnrealBridge();
138
+
139
+ // Initialize response validation with schemas
140
+ log.info('Initializing response validation...');
141
+ const toolDefs = CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions;
142
+ toolDefs.forEach((tool: any) => {
143
+ if (tool.outputSchema) {
144
+ responseValidator.registerSchema(tool.name, tool.outputSchema);
145
+ }
146
+ });
147
+ log.info(`Registered ${responseValidator.getStats().totalSchemas} output schemas for validation`);
148
+
149
+ // Connect to UE5 Remote Control with retries and timeout
150
+ const connected = await bridge.tryConnect(
151
+ CONFIG.MAX_RETRY_ATTEMPTS,
152
+ 5000, // 5 second timeout per attempt
153
+ CONFIG.RETRY_DELAY_MS
154
+ );
155
+
156
+ if (connected) {
157
+ metrics.connectionStatus = 'connected';
158
+ log.info('Successfully connected to Unreal Engine');
159
+ } else {
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 () => {
167
+ if (!bridge.isConnected) {
168
+ log.info('Attempting to reconnect to Unreal Engine...');
169
+ const reconnected = await bridge.tryConnect(1, 5000, 0); // Single attempt
170
+ if (reconnected) {
171
+ log.info('Reconnected to Unreal Engine successfully');
172
+ metrics.connectionStatus = 'connected';
173
+ }
174
+ }
175
+ }, 10000); // Try every 10 seconds
176
+ }
177
+
178
+ // Start periodic health checks
179
+ setInterval(() => {
180
+ performHealthCheck(bridge);
181
+ }, CONFIG.HEALTH_CHECK_INTERVAL_MS);
182
+
183
+ // Resources
184
+ const assetResources = new AssetResources(bridge);
185
+ const actorResources = new ActorResources(bridge);
186
+ const levelResources = new LevelResources(bridge);
187
+
188
+ // Tools
189
+ const actorTools = new ActorTools(bridge);
190
+ const assetTools = new AssetTools(bridge);
191
+ const editorTools = new EditorTools(bridge);
192
+ const materialTools = new MaterialTools(bridge);
193
+ const animationTools = new AnimationTools(bridge);
194
+ const physicsTools = new PhysicsTools(bridge);
195
+ const niagaraTools = new NiagaraTools(bridge);
196
+ const blueprintTools = new BlueprintTools(bridge);
197
+ const levelTools = new LevelTools(bridge);
198
+ const lightingTools = new LightingTools(bridge);
199
+ const landscapeTools = new LandscapeTools(bridge);
200
+ const foliageTools = new FoliageTools(bridge);
201
+ const debugTools = new DebugVisualizationTools(bridge);
202
+ const performanceTools = new PerformanceTools(bridge);
203
+ const audioTools = new AudioTools(bridge);
204
+ const uiTools = new UITools(bridge);
205
+ const rcTools = new RcTools(bridge);
206
+ const sequenceTools = new SequenceTools(bridge);
207
+ const introspectionTools = new IntrospectionTools(bridge);
208
+ const visualTools = new VisualTools(bridge);
209
+ const engineTools = new EngineTools(bridge);
210
+
211
+ const server = new Server(
212
+ {
213
+ name: CONFIG.SERVER_NAME,
214
+ version: CONFIG.SERVER_VERSION
215
+ },
216
+ {
217
+ capabilities: {
218
+ resources: {},
219
+ tools: {},
220
+ prompts: {},
221
+ logging: {}
222
+ }
223
+ }
224
+ );
225
+
226
+ // Handle resource listing
227
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
228
+ return {
229
+ resources: [
230
+ {
231
+ uri: 'ue://assets',
232
+ name: 'Project Assets',
233
+ description: 'List all assets in the project',
234
+ mimeType: 'application/json'
235
+ },
236
+ {
237
+ uri: 'ue://actors',
238
+ name: 'Level Actors',
239
+ description: 'List all actors in the current level',
240
+ mimeType: 'application/json'
241
+ },
242
+ {
243
+ uri: 'ue://level',
244
+ name: 'Current Level',
245
+ description: 'Information about the current level',
246
+ mimeType: 'application/json'
247
+ },
248
+ {
249
+ uri: 'ue://exposed',
250
+ name: 'Remote Control Exposed',
251
+ description: 'List all exposed properties via Remote Control',
252
+ mimeType: 'application/json'
253
+ },
254
+ {
255
+ uri: 'ue://health',
256
+ name: 'Health Status',
257
+ description: 'Server health and performance metrics',
258
+ mimeType: 'application/json'
259
+ },
260
+ {
261
+ uri: 'ue://version',
262
+ name: 'Engine Version',
263
+ description: 'Unreal Engine version and compatibility info',
264
+ mimeType: 'application/json'
265
+ }
266
+ ]
267
+ };
268
+ });
269
+
270
+ // Handle resource reading
271
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
272
+ const uri = request.params.uri;
273
+
274
+ if (uri === 'ue://assets') {
275
+ const list = await assetResources.list('/Game', true);
276
+ return {
277
+ contents: [{
278
+ uri,
279
+ mimeType: 'application/json',
280
+ text: JSON.stringify(list, null, 2)
281
+ }]
282
+ };
283
+ }
284
+
285
+ if (uri === 'ue://actors') {
286
+ const list = await actorResources.listActors();
287
+ return {
288
+ contents: [{
289
+ uri,
290
+ mimeType: 'application/json',
291
+ text: JSON.stringify(list, null, 2)
292
+ }]
293
+ };
294
+ }
295
+
296
+ if (uri === 'ue://level') {
297
+ const level = await levelResources.getCurrentLevel();
298
+ return {
299
+ contents: [{
300
+ uri,
301
+ mimeType: 'application/json',
302
+ text: JSON.stringify(level, null, 2)
303
+ }]
304
+ };
305
+ }
306
+
307
+ if (uri === 'ue://exposed') {
308
+ try {
309
+ const exposed = await bridge.getExposed();
310
+ return {
311
+ contents: [{
312
+ uri,
313
+ mimeType: 'application/json',
314
+ text: JSON.stringify(exposed, null, 2)
315
+ }]
316
+ };
317
+ } catch {
318
+ return {
319
+ contents: [{
320
+ uri,
321
+ mimeType: 'text/plain',
322
+ text: 'Failed to get exposed properties. Ensure Remote Control is configured.'
323
+ }]
324
+ };
325
+ }
326
+ }
327
+
328
+ if (uri === 'ue://health') {
329
+ const uptimeMs = Date.now() - metrics.uptime;
330
+ // Query engine version and feature flags (best-effort)
331
+ let versionInfo: any = {};
332
+ let featureFlags: any = {};
333
+ try { versionInfo = await bridge.getEngineVersion(); } catch {}
334
+ try { featureFlags = await bridge.getFeatureFlags(); } catch {}
335
+ const health = {
336
+ status: metrics.connectionStatus,
337
+ uptime: Math.floor(uptimeMs / 1000),
338
+ performance: {
339
+ totalRequests: metrics.totalRequests,
340
+ successfulRequests: metrics.successfulRequests,
341
+ failedRequests: metrics.failedRequests,
342
+ successRate: metrics.totalRequests > 0 ?
343
+ (metrics.successfulRequests / metrics.totalRequests * 100).toFixed(2) + '%' : 'N/A',
344
+ averageResponseTime: Math.round(metrics.averageResponseTime) + 'ms'
345
+ },
346
+ lastHealthCheck: metrics.lastHealthCheck.toISOString(),
347
+ unrealConnection: {
348
+ status: bridge.isConnected ? 'connected' : 'disconnected',
349
+ host: process.env.UE_HOST || 'localhost',
350
+ httpPort: process.env.UE_RC_HTTP_PORT || 30010,
351
+ wsPort: process.env.UE_RC_WS_PORT || 30020,
352
+ engineVersion: versionInfo,
353
+ features: {
354
+ pythonEnabled: featureFlags.pythonEnabled === true,
355
+ subsystems: featureFlags.subsystems || {},
356
+ rcHttpReachable: bridge.isConnected
357
+ }
358
+ },
359
+ recentErrors: metrics.recentErrors.slice(-5)
360
+ };
361
+
362
+ return {
363
+ contents: [{
364
+ uri,
365
+ mimeType: 'application/json',
366
+ text: JSON.stringify(health, null, 2)
367
+ }]
368
+ };
369
+ }
370
+
371
+ if (uri === 'ue://version') {
372
+ const info = await bridge.getEngineVersion();
373
+ return {
374
+ contents: [{
375
+ uri,
376
+ mimeType: 'application/json',
377
+ text: JSON.stringify(info, null, 2)
378
+ }]
379
+ };
380
+ }
381
+
382
+ throw new Error(`Unknown resource: ${uri}`);
383
+ });
384
+
385
+ // Handle tool listing - switch between consolidated (10) or individual (36) tools
386
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
387
+ log.info(`Serving ${CONFIG.USE_CONSOLIDATED_TOOLS ? 'consolidated' : 'individual'} tools`);
388
+ return {
389
+ tools: CONFIG.USE_CONSOLIDATED_TOOLS ? consolidatedToolDefinitions : toolDefinitions
390
+ };
391
+ });
392
+
393
+ // Handle tool calls - switch between consolidated (10) or individual (36) tools
394
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
395
+ const { name, arguments: args } = request.params;
396
+ const startTime = Date.now();
397
+
398
+ // Create tools object for handler
399
+ const tools = {
400
+ actorTools,
401
+ assetTools,
402
+ materialTools,
403
+ editorTools,
404
+ animationTools,
405
+ physicsTools,
406
+ niagaraTools,
407
+ blueprintTools,
408
+ levelTools,
409
+ lightingTools,
410
+ landscapeTools,
411
+ foliageTools,
412
+ debugTools,
413
+ performanceTools,
414
+ audioTools,
415
+ uiTools,
416
+ rcTools,
417
+ sequenceTools,
418
+ introspectionTools,
419
+ visualTools,
420
+ engineTools,
421
+ bridge
422
+ };
423
+
424
+ // Use consolidated or individual handler based on configuration
425
+ try {
426
+ log.debug(`Executing tool: ${name}`);
427
+
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
+ }
434
+
435
+ log.debug(`Tool ${name} returned result`);
436
+
437
+ // Clean the result to remove circular references
438
+ result = cleanObject(result);
439
+
440
+ // Validate and enhance response
441
+ result = responseValidator.wrapResponse(name, result);
442
+
443
+ trackPerformance(startTime, true);
444
+
445
+ log.info(`Tool ${name} completed successfully in ${Date.now() - startTime}ms`);
446
+
447
+ // Log that we're returning the response
448
+ const responsePreview = JSON.stringify(result).substring(0, 100);
449
+ log.debug(`Returning response to MCP client: ${responsePreview}...`);
450
+
451
+ return result;
452
+ } catch (error) {
453
+ trackPerformance(startTime, false);
454
+
455
+ // Use consistent error handling
456
+ const errorResponse = ErrorHandler.createErrorResponse(error, name, { ...args, scope: `tool-call/${name}` });
457
+ log.error(`Tool execution failed: ${name}`, errorResponse);
458
+ // Record error for health diagnostics
459
+ try {
460
+ metrics.recentErrors.push({
461
+ time: new Date().toISOString(),
462
+ scope: (errorResponse as any).scope || `tool-call/${name}`,
463
+ type: (errorResponse as any)._debug?.errorType || 'UNKNOWN',
464
+ message: (errorResponse as any).error || (errorResponse as any).message || 'Unknown error',
465
+ retriable: Boolean((errorResponse as any).retriable)
466
+ });
467
+ if (metrics.recentErrors.length > 20) metrics.recentErrors.splice(0, metrics.recentErrors.length - 20);
468
+ } catch {}
469
+
470
+ return {
471
+ content: [{
472
+ type: 'text',
473
+ text: errorResponse.message || `Failed to execute ${name}`
474
+ }],
475
+ ...errorResponse
476
+ };
477
+ }
478
+ });
479
+
480
+ // Handle prompt listing
481
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
482
+ return {
483
+ prompts: prompts.map(p => ({
484
+ name: p.name,
485
+ description: p.description,
486
+ arguments: Object.entries(p.arguments || {}).map(([name, schema]) => ({
487
+ name,
488
+ description: schema.description,
489
+ required: schema.required || false
490
+ }))
491
+ }))
492
+ };
493
+ });
494
+
495
+ // Handle prompt retrieval
496
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
497
+ const prompt = prompts.find(p => p.name === request.params.name);
498
+ if (!prompt) {
499
+ throw new Error(`Unknown prompt: ${request.params.name}`);
500
+ }
501
+
502
+ // Return a template for the lighting setup
503
+ return {
504
+ messages: [
505
+ {
506
+ role: 'user',
507
+ content: {
508
+ type: 'text',
509
+ text: `Set up three-point lighting with ${request.params.arguments?.intensity || 'medium'} intensity`
510
+ }
511
+ }
512
+ ]
513
+ };
514
+ });
515
+
516
+ return { server, bridge };
517
+ }
518
+
519
+ export async function startStdioServer() {
520
+ const { server } = await createServer();
521
+ const transport = new StdioServerTransport();
522
+
523
+ // Add debugging for transport messages
524
+ const originalWrite = process.stdout.write;
525
+ process.stdout.write = function(...args: any[]) {
526
+ const message = args[0];
527
+ if (typeof message === 'string' && message.includes('jsonrpc')) {
528
+ log.debug(`Sending to client: ${message.substring(0, 200)}...`);
529
+ }
530
+ return originalWrite.apply(process.stdout, args as any);
531
+ } as any;
532
+
533
+ await server.connect(transport);
534
+ log.info('Unreal Engine MCP Server started on stdio');
535
+ }
536
+
537
+ // Start the server when run directly
538
+ if (import.meta.url === `file://${process.argv[1].replace(/\\/g, '/')}`) {
539
+ startStdioServer().catch(error => {
540
+ console.error('Failed to start server:', error);
541
+ process.exit(1);
542
+ });
543
+ }
@@ -0,0 +1,51 @@
1
+ export interface PromptArgument {
2
+ type: string;
3
+ description?: string;
4
+ enum?: string[];
5
+ default?: any;
6
+ required?: boolean;
7
+ }
8
+
9
+ export interface Prompt {
10
+ name: string;
11
+ description: string;
12
+ arguments?: Record<string, PromptArgument>;
13
+ }
14
+
15
+ export const prompts: Prompt[] = [
16
+ {
17
+ name: 'setup_three_point_lighting',
18
+ description: 'Set up a basic three-point lighting rig around the current camera focus',
19
+ arguments: {
20
+ intensity: {
21
+ type: 'string',
22
+ enum: ['low', 'medium', 'high'],
23
+ default: 'medium',
24
+ description: 'Light intensity level'
25
+ }
26
+ }
27
+ },
28
+ {
29
+ name: 'create_fps_controller',
30
+ description: 'Create a first-person shooter character controller',
31
+ arguments: {
32
+ spawnLocation: {
33
+ type: 'object',
34
+ description: 'Location to spawn the controller',
35
+ required: false
36
+ }
37
+ }
38
+ },
39
+ {
40
+ name: 'setup_post_processing',
41
+ description: 'Configure post-processing volume with cinematic settings',
42
+ arguments: {
43
+ style: {
44
+ type: 'string',
45
+ enum: ['cinematic', 'realistic', 'stylized', 'noir'],
46
+ default: 'cinematic',
47
+ description: 'Visual style preset'
48
+ }
49
+ }
50
+ }
51
+ ];