unreal-engine-mcp-server 0.5.2 → 0.5.4

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 (98) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +9 -6
  3. package/dist/automation/bridge.d.ts +1 -0
  4. package/dist/automation/bridge.js +62 -4
  5. package/dist/automation/types.d.ts +1 -0
  6. package/dist/config/class-aliases.d.ts +5 -0
  7. package/dist/config/class-aliases.js +30 -0
  8. package/dist/constants.d.ts +5 -0
  9. package/dist/constants.js +5 -0
  10. package/dist/graphql/server.d.ts +0 -1
  11. package/dist/graphql/server.js +15 -16
  12. package/dist/index.js +1 -1
  13. package/dist/services/metrics-server.d.ts +2 -1
  14. package/dist/services/metrics-server.js +29 -4
  15. package/dist/tools/consolidated-tool-definitions.js +3 -3
  16. package/dist/tools/debug.d.ts +5 -0
  17. package/dist/tools/debug.js +7 -0
  18. package/dist/tools/handlers/actor-handlers.js +4 -27
  19. package/dist/tools/handlers/asset-handlers.js +13 -1
  20. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -1
  21. package/dist/tools/handlers/common-handlers.d.ts +11 -11
  22. package/dist/tools/handlers/common-handlers.js +6 -4
  23. package/dist/tools/handlers/editor-handlers.d.ts +2 -1
  24. package/dist/tools/handlers/editor-handlers.js +6 -6
  25. package/dist/tools/handlers/effect-handlers.js +3 -0
  26. package/dist/tools/handlers/graph-handlers.d.ts +2 -1
  27. package/dist/tools/handlers/graph-handlers.js +1 -1
  28. package/dist/tools/handlers/input-handlers.d.ts +5 -1
  29. package/dist/tools/handlers/level-handlers.d.ts +2 -1
  30. package/dist/tools/handlers/level-handlers.js +3 -3
  31. package/dist/tools/handlers/lighting-handlers.d.ts +2 -1
  32. package/dist/tools/handlers/lighting-handlers.js +3 -0
  33. package/dist/tools/handlers/pipeline-handlers.d.ts +2 -1
  34. package/dist/tools/handlers/pipeline-handlers.js +64 -10
  35. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  36. package/dist/tools/handlers/system-handlers.d.ts +1 -1
  37. package/dist/tools/input.d.ts +5 -1
  38. package/dist/tools/input.js +37 -1
  39. package/dist/tools/lighting.d.ts +1 -0
  40. package/dist/tools/lighting.js +7 -0
  41. package/dist/tools/physics.d.ts +1 -1
  42. package/dist/tools/sequence.d.ts +1 -0
  43. package/dist/tools/sequence.js +7 -0
  44. package/dist/types/handler-types.d.ts +343 -0
  45. package/dist/types/handler-types.js +2 -0
  46. package/dist/unreal-bridge.d.ts +1 -1
  47. package/dist/unreal-bridge.js +8 -6
  48. package/dist/utils/command-validator.d.ts +1 -0
  49. package/dist/utils/command-validator.js +11 -1
  50. package/dist/utils/error-handler.js +3 -1
  51. package/dist/utils/response-validator.js +2 -2
  52. package/dist/utils/safe-json.d.ts +1 -1
  53. package/dist/utils/safe-json.js +3 -6
  54. package/dist/utils/unreal-command-queue.js +1 -1
  55. package/dist/utils/validation.js +6 -2
  56. package/docs/handler-mapping.md +6 -1
  57. package/package.json +2 -2
  58. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +25 -1
  59. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +40 -58
  60. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +27 -46
  61. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +16 -1
  62. package/server.json +2 -2
  63. package/src/automation/bridge.ts +80 -10
  64. package/src/automation/types.ts +1 -0
  65. package/src/config/class-aliases.ts +65 -0
  66. package/src/constants.ts +10 -0
  67. package/src/graphql/server.ts +23 -23
  68. package/src/index.ts +1 -1
  69. package/src/services/metrics-server.ts +40 -6
  70. package/src/tools/consolidated-tool-definitions.ts +3 -3
  71. package/src/tools/debug.ts +8 -0
  72. package/src/tools/handlers/actor-handlers.ts +5 -31
  73. package/src/tools/handlers/asset-handlers.ts +19 -1
  74. package/src/tools/handlers/blueprint-handlers.ts +1 -1
  75. package/src/tools/handlers/common-handlers.ts +32 -11
  76. package/src/tools/handlers/editor-handlers.ts +8 -7
  77. package/src/tools/handlers/effect-handlers.ts +4 -0
  78. package/src/tools/handlers/graph-handlers.ts +7 -6
  79. package/src/tools/handlers/level-handlers.ts +5 -4
  80. package/src/tools/handlers/lighting-handlers.ts +5 -1
  81. package/src/tools/handlers/pipeline-handlers.ts +83 -16
  82. package/src/tools/input.ts +60 -1
  83. package/src/tools/lighting.ts +11 -0
  84. package/src/tools/physics.ts +1 -1
  85. package/src/tools/sequence.ts +11 -0
  86. package/src/types/handler-types.ts +442 -0
  87. package/src/unreal-bridge.ts +8 -6
  88. package/src/utils/command-validator.ts +23 -1
  89. package/src/utils/error-handler.ts +4 -1
  90. package/src/utils/response-validator.ts +7 -9
  91. package/src/utils/safe-json.ts +20 -15
  92. package/src/utils/unreal-command-queue.ts +3 -1
  93. package/src/utils/validation.test.ts +3 -3
  94. package/src/utils/validation.ts +36 -26
  95. package/tests/test-console-command.mjs +1 -1
  96. package/tests/test-runner.mjs +63 -3
  97. package/tests/run-unreal-tool-tests.mjs +0 -948
  98. package/tests/test-asset-errors.mjs +0 -35
package/CHANGELOG.md CHANGED
@@ -7,6 +7,201 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## 🏷️ [0.5.4] - 2025-12-27
11
+
12
+ > [!IMPORTANT]
13
+ > ### 🛡️ Security Release
14
+ > This release focuses on **security hardening** and **defensive improvements** across the entire stack, including command injection prevention, network isolation, and resource management.
15
+
16
+ ### 🛡️ Security & Command Hardening
17
+
18
+ <details>
19
+ <summary><b>UBT Validation & Safe Execution</b></summary>
20
+
21
+ | Feature | Description |
22
+ |---------|-------------|
23
+ | **UBT Argument Validation** | Added `validateUbtArgumentsString` and `tokenizeArgs` to block dangerous characters (`;`, `|`, backticks) |
24
+ | **Safe Process Spawning** | Updated child process spawning to use `shell: false`, preventing shell injection attacks |
25
+ | **Console Command Validation** | Implemented strict input validation for the Unreal Automation Bridge to block chained or multi-line commands |
26
+ | **Argument Quoting** | Improved logging and execution logic to correctly quote arguments containing spaces |
27
+
28
+ </details>
29
+
30
+ ### 🌐 Network & Host Binding
31
+
32
+ <details>
33
+ <summary><b>Localhost Default & Remote Configuration</b></summary>
34
+
35
+ | Feature | Description |
36
+ |---------|-------------|
37
+ | **Localhost Default** | WebSocket, Metrics, and GraphQL servers now bind to `127.0.0.1` by default |
38
+ | **Remote Exposure Prevention** | Prevents accidental remote exposure of services |
39
+ | **GRAPHQL_ALLOW_REMOTE** | Added environment variable check for explicit remote binding configuration |
40
+ | **Security Warnings** | Warnings logged for unsafe/permissive network settings |
41
+
42
+ </details>
43
+
44
+ ### 🚦 Resource Management
45
+
46
+ <details>
47
+ <summary><b>Rate Limiting & Queue Management</b></summary>
48
+
49
+ | Feature | Description |
50
+ |---------|-------------|
51
+ | **IP-Based Rate Limiting** | Implemented rate limiting on the metrics server |
52
+ | **Queue Limits** | Introduced `maxQueuedRequests` to automation bridge to prevent memory exhaustion |
53
+ | **Message Size Enforcement** | Enforced `MAX_WS_MESSAGE_SIZE_BYTES` for WebSocket connections to reject oversized payloads |
54
+
55
+ </details>
56
+
57
+ ### 🧪 Testing & Cleanup
58
+
59
+ <details>
60
+ <summary><b>Test Updates & File Cleanup</b></summary>
61
+
62
+ | Change | Description |
63
+ |--------|-------------|
64
+ | **Path Sanitization Tests** | Modified validation tests to verify path sanitization and expect errors for traversal attempts |
65
+ | **Removed Legacy Tests** | Removed outdated test files (`run-unreal-tool-tests.mjs`, `test-asset-errors.mjs`) |
66
+ | **Response Logging** | Implemented better response logging in the test runner |
67
+
68
+ </details>
69
+
70
+ ### 🔄 Dependencies
71
+
72
+ - **dependencies group**: Bumped 2 updates via @dependabot ([#33](https://github.com/ChiR24/Unreal_mcp/pull/33))
73
+
74
+ ---
75
+
76
+ ## 🏷️ [0.5.3] - 2025-12-21
77
+
78
+ > [!IMPORTANT]
79
+ > ### 🔄 Major Enhancements
80
+ > - **Dynamic Type Discovery** - New runtime introspection for lights, debug shapes, and sequencer tracks
81
+ > - **Metrics Rate Limiting** - Per-IP rate limiting (60 req/min) on Prometheus endpoint
82
+ > - **Centralized Class Configuration** - Unified Unreal Engine class aliases
83
+ > - **Enhanced Type Safety** - Comprehensive TypeScript interfaces replacing `any` types
84
+
85
+ ### ✨ Added
86
+
87
+ <details>
88
+ <summary><b>🔍 Dynamic Discovery & Engine Handlers</b></summary>
89
+
90
+ | Feature | Description |
91
+ |---------|-------------|
92
+ | **list_light_types** | Discovers all available light class types at runtime |
93
+ | **list_debug_shapes** | Enumerates supported debug shape types |
94
+ | **list_track_types** | Lists all sequencer track types available in the engine |
95
+ | **Heuristic Resolution** | Improved C++ handlers use multiple naming conventions and inheritance validation |
96
+ | **Vehicle Type Support** | Expanded vehicle type from union to string for flexibility |
97
+
98
+ **C++ Changes:**
99
+ - `McpAutomationBridge_LightingHandlers.cpp` - Runtime `ResolveUClass` for lights
100
+ - `McpAutomationBridge_SequenceHandlers.cpp` - Runtime resolution for tracks
101
+ - Added `UObjectIterator.h` for dynamic type scanning
102
+ - Unified spawn/track-creation flows
103
+ - Removed editor/PIE branching logic
104
+
105
+ </details>
106
+
107
+ <details>
108
+ <summary><b>⚙️ Tooling & Configuration</b></summary>
109
+
110
+ | Feature | Description |
111
+ |---------|-------------|
112
+ | **class-aliases.ts** | Centralized Unreal Engine class name mappings |
113
+ | **handler-types.ts** | Comprehensive TypeScript interfaces (ActorArgs, EditorArgs, LightingArgs, etc.) |
114
+ | **timeout constants** | Command-specific operation timeouts in constants.ts |
115
+ | **listDebugShapes()** | Programmatic access in DebugVisualizationTools |
116
+
117
+ **Type System:**
118
+ - Geometry types: Vector3, Rotator, Transform
119
+ - Required-component lookups
120
+ - Centralized class-alias mappings
121
+
122
+ </details>
123
+
124
+ <details>
125
+ <summary><b>📈 Metrics Server Enhancements</b></summary>
126
+
127
+ | Feature | Description |
128
+ |---------|-------------|
129
+ | **Rate Limiting** | Per-IP limit of 60 requests/minute |
130
+ | **Server Lifecycle** | Returns instance for better management |
131
+ | **Error Handling** | Improved internal error handling |
132
+
133
+ </details>
134
+
135
+ <details>
136
+ <summary><b>📚 Documentation & DX</b></summary>
137
+
138
+ | Feature | Description |
139
+ |---------|-------------|
140
+ | **handler-mapping.md** | Updated with new discovery actions |
141
+ | **README.md** | Clarified WASM build instructions |
142
+ | **Tool Definitions** | Synchronized with new discovery actions |
143
+
144
+ </details>
145
+
146
+ ### 🔧 Changed
147
+
148
+ <details>
149
+ <summary><b>Handler Type Safety & Logic</b></summary>
150
+
151
+ **src/tools/handlers/common-handlers.ts:**
152
+ - Replaced `any` typings with strict `HandlerArgs`/`LocationInput`/`RotationInput`
153
+ - Added automation-bridge connectivity validation
154
+ - Enhanced location/rotation normalization with type guards
155
+
156
+ **Specialized Handlers:**
157
+ - `actor-handlers.ts` - Applied typed handler-args
158
+ - `asset-handlers.ts` - Improved argument normalization
159
+ - `blueprint-handlers.ts` - Added new action cases
160
+ - `editor-handlers.ts` - Enhanced default handling
161
+ - `effect-handlers.ts` - Added `list_debug_shapes`
162
+ - `graph-handlers.ts` - Improved validation
163
+ - `level-handlers.ts` - Type-safe operations
164
+ - `lighting-handlers.ts` - Added `list_light_types`
165
+ - `pipeline-handlers.ts` - Enhanced error handling
166
+
167
+ </details>
168
+
169
+ <details>
170
+ <summary><b>Infrastructure & Utilities</b></summary>
171
+
172
+ **Security & Validation:**
173
+ - `command-validator.ts` - Blocks semicolons, pipes, backticks
174
+ - `error-handler.ts` - Enhanced error logging
175
+ - `response-validator.ts` - Improved Ajv typing
176
+ - `safe-json.ts` - Generic typing for cleanObject
177
+ - `validation.ts` - Expanded path-traversal protection
178
+
179
+ **Performance:**
180
+ - `unreal-command-queue.ts` - Optimized queue processing (250ms interval)
181
+ - `unreal-bridge.ts` - Centralized timeout constants
182
+
183
+ </details>
184
+
185
+ ### 🛠️ Fixed
186
+
187
+ - **Command Injection Prevention** - Additional dangerous command patterns blocked
188
+ - **Path Security** - Enhanced asset-name validation
189
+ - **Type Safety** - Eliminated `any` types across handler functions
190
+ - **Error Messages** - Clearer error messages for class resolution failures
191
+
192
+ ### 📊 Statistics
193
+
194
+ - **Files Changed:** 20+
195
+ - **New Interfaces:** 15+ handler type definitions
196
+ - **Discovery Actions:** 3 new runtime introspection methods
197
+ - **Security Enhancements:** 5+ new validation patterns
198
+
199
+ ### 🔄 Dependencies
200
+
201
+ - **graphql-yoga**: Bumped from 5.17.1 to 5.18.0 (#31)
202
+
203
+ ---
204
+
10
205
  ## 🏷️ [0.5.2] - 2025-12-18
11
206
 
12
207
  > [!IMPORTANT]
package/README.md CHANGED
@@ -43,10 +43,13 @@ A comprehensive Model Context Protocol (MCP) server that enables AI assistants t
43
43
  ### Architecture
44
44
 
45
45
  - **Native C++ Automation** — All operations route through the MCP Automation Bridge plugin
46
+ - **Dynamic Type Discovery** — Runtime introspection for lights, debug shapes, and sequencer tracks
46
47
  - **Graceful Degradation** — Server starts even without an active Unreal connection
47
48
  - **On-Demand Connection** — Retries automation handshakes with exponential backoff
48
- - **Command Safety** — Blocks dangerous console commands
49
+ - **Command Safety** — Blocks dangerous console commands with pattern-based validation
49
50
  - **Asset Caching** — 10-second TTL for improved performance
51
+ - **Metrics Rate Limiting** — Per-IP rate limiting (60 req/min) on Prometheus endpoint
52
+ - **Centralized Configuration** — Unified class aliases and type definitions
50
53
 
51
54
  ---
52
55
 
@@ -174,15 +177,15 @@ ASSET_LIST_TTL_MS=10000
174
177
  | `control_actor` | Spawn, delete, transform, physics, tags |
175
178
  | `control_editor` | PIE, Camera, viewport, screenshots |
176
179
  | `manage_level` | Load/Save, World Partition, streaming |
177
- | `manage_lighting` | Spawn lights, GI, shadows, build lighting |
180
+ | `manage_lighting` | Spawn lights, GI, shadows, build lighting, **list_light_types** |
178
181
  | `manage_performance` | Profiling, optimization, scalability |
179
- | `animation_physics` | Animation BPs, Vehicles, Ragdolls |
180
- | `manage_effect` | Niagara, Particles, Debug Shapes |
182
+ | `animation_physics` | Animation BPs, Vehicles (custom types), Ragdolls |
183
+ | `manage_effect` | Niagara, Particles, Debug Shapes, **list_debug_shapes** |
181
184
  | `manage_blueprint` | Create, SCS, Graph Editing |
182
185
  | `manage_blueprint_graph` | Direct Blueprint Graph Manipulation |
183
186
  | `build_environment` | Landscape, Foliage, Procedural |
184
187
  | `system_control` | UBT, Tests, Logs, Project Settings, CVars |
185
- | `manage_sequence` | Sequencer / Cinematics |
188
+ | `manage_sequence` | Sequencer / Cinematics, **list_track_types** |
186
189
  | `inspect` | Object Introspection |
187
190
  | `manage_audio` | Audio Assets & Components |
188
191
  | `manage_behavior_tree` | Behavior Tree Graph Editing |
@@ -209,7 +212,7 @@ Optional WASM acceleration for computationally intensive operations. **Enabled b
209
212
 
210
213
  ```bash
211
214
  cargo install wasm-pack # Once per machine
212
- npm run build # Builds TS + WASM
215
+ npm run build:wasm # Builds WASM
213
216
  ```
214
217
 
215
218
  To disable: `WASM_ENABLED=false`
@@ -13,6 +13,7 @@ export declare class AutomationBridge extends EventEmitter {
13
13
  private readonly clientPort;
14
14
  private readonly serverLegacyEnabled;
15
15
  private readonly maxConcurrentConnections;
16
+ private readonly maxQueuedRequests;
16
17
  private connectionManager;
17
18
  private requestTracker;
18
19
  private handshakeHandler;
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { WebSocket } from 'ws';
3
3
  import { Logger } from '../utils/logger.js';
4
- import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT, DEFAULT_NEGOTIATED_PROTOCOLS, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_MAX_PENDING_REQUESTS } from '../constants.js';
4
+ import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT, DEFAULT_NEGOTIATED_PROTOCOLS, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_MAX_PENDING_REQUESTS, DEFAULT_MAX_QUEUED_REQUESTS, MAX_WS_MESSAGE_SIZE_BYTES } from '../constants.js';
5
5
  import { createRequire } from 'node:module';
6
6
  import { ConnectionManager } from './connection-manager.js';
7
7
  import { RequestTracker } from './request-tracker.js';
@@ -31,6 +31,7 @@ export class AutomationBridge extends EventEmitter {
31
31
  clientPort;
32
32
  serverLegacyEnabled;
33
33
  maxConcurrentConnections;
34
+ maxQueuedRequests;
34
35
  connectionManager;
35
36
  requestTracker;
36
37
  handshakeHandler;
@@ -107,6 +108,7 @@ export class AutomationBridge extends EventEmitter {
107
108
  : 0;
108
109
  const maxPendingRequests = Math.max(1, options.maxPendingRequests ?? DEFAULT_MAX_PENDING_REQUESTS);
109
110
  const maxConcurrentConnections = Math.max(1, options.maxConcurrentConnections ?? 10);
111
+ this.maxQueuedRequests = Math.max(0, options.maxQueuedRequests ?? DEFAULT_MAX_QUEUED_REQUESTS);
110
112
  this.clientHost = options.clientHost ?? process.env.MCP_AUTOMATION_CLIENT_HOST ?? DEFAULT_AUTOMATION_HOST;
111
113
  this.clientPort = options.clientPort ?? sanitizePort(process.env.MCP_AUTOMATION_CLIENT_PORT) ?? DEFAULT_AUTOMATION_PORT;
112
114
  this.maxConcurrentConnections = maxConcurrentConnections;
@@ -137,10 +139,18 @@ export class AutomationBridge extends EventEmitter {
137
139
  const url = `ws://${this.clientHost}:${this.clientPort}`;
138
140
  this.log.info(`Connecting to Unreal Engine automation server at ${url}`);
139
141
  this.log.debug(`Negotiated protocols: ${JSON.stringify(this.negotiatedProtocols)}`);
140
- const protocols = 'mcp-automation';
142
+ const protocols = this.negotiatedProtocols.length === 1
143
+ ? this.negotiatedProtocols[0]
144
+ : this.negotiatedProtocols;
141
145
  this.log.debug(`Using WebSocket protocols arg: ${JSON.stringify(protocols)}`);
146
+ const headers = this.capabilityToken
147
+ ? {
148
+ 'X-MCP-Capability': this.capabilityToken,
149
+ 'X-MCP-Capability-Token': this.capabilityToken
150
+ }
151
+ : undefined;
142
152
  const socket = new WebSocket(url, protocols, {
143
- headers: this.capabilityToken ? { 'X-MCP-Capability': this.capabilityToken } : undefined,
153
+ headers,
144
154
  perMessageDeflate: false
145
155
  });
146
156
  this.handleClientConnection(socket);
@@ -174,9 +184,54 @@ export class AutomationBridge extends EventEmitter {
174
184
  port: this.clientPort,
175
185
  protocol: socket.protocol || null
176
186
  });
187
+ const getRawDataByteLength = (data) => {
188
+ if (typeof data === 'string') {
189
+ return Buffer.byteLength(data, 'utf8');
190
+ }
191
+ if (Buffer.isBuffer(data)) {
192
+ return data.length;
193
+ }
194
+ if (Array.isArray(data)) {
195
+ return data.reduce((total, item) => total + (Buffer.isBuffer(item) ? item.length : 0), 0);
196
+ }
197
+ if (data instanceof ArrayBuffer) {
198
+ return data.byteLength;
199
+ }
200
+ if (ArrayBuffer.isView(data)) {
201
+ return data.byteLength;
202
+ }
203
+ return 0;
204
+ };
205
+ const rawDataToUtf8String = (data, byteLengthHint) => {
206
+ if (typeof data === 'string') {
207
+ return data;
208
+ }
209
+ if (Buffer.isBuffer(data)) {
210
+ return data.toString('utf8');
211
+ }
212
+ if (Array.isArray(data)) {
213
+ const buffers = data.filter((item) => Buffer.isBuffer(item));
214
+ const totalLength = typeof byteLengthHint === 'number'
215
+ ? byteLengthHint
216
+ : buffers.reduce((total, item) => total + item.length, 0);
217
+ return Buffer.concat(buffers, totalLength).toString('utf8');
218
+ }
219
+ if (data instanceof ArrayBuffer) {
220
+ return Buffer.from(data).toString('utf8');
221
+ }
222
+ if (ArrayBuffer.isView(data)) {
223
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf8');
224
+ }
225
+ return '';
226
+ };
177
227
  socket.on('message', (data) => {
178
228
  try {
179
- const text = typeof data === 'string' ? data : data.toString('utf8');
229
+ const byteLength = getRawDataByteLength(data);
230
+ if (byteLength > MAX_WS_MESSAGE_SIZE_BYTES) {
231
+ this.log.error(`Received oversized message (${byteLength} bytes, max: ${MAX_WS_MESSAGE_SIZE_BYTES}). Dropping.`);
232
+ return;
233
+ }
234
+ const text = rawDataToUtf8String(data, byteLength);
180
235
  this.log.debug(`[AutomationBridge Client] Received message: ${text.substring(0, 1000)}`);
181
236
  const parsed = JSON.parse(text);
182
237
  this.connectionManager.updateLastMessageTime();
@@ -350,6 +405,9 @@ export class AutomationBridge extends EventEmitter {
350
405
  throw new Error('Automation bridge not connected');
351
406
  }
352
407
  if (this.requestTracker.getPendingCount() >= this.requestTracker.getMaxPendingRequests()) {
408
+ if (this.queuedRequestItems.length >= this.maxQueuedRequests) {
409
+ throw new Error(`Automation bridge request queue is full (max: ${this.maxQueuedRequests}). Please retry later.`);
410
+ }
353
411
  return new Promise((resolve, reject) => {
354
412
  this.queuedRequestItems.push({
355
413
  resolve,
@@ -11,6 +11,7 @@ export interface AutomationBridgeOptions {
11
11
  heartbeatIntervalMs?: number;
12
12
  maxPendingRequests?: number;
13
13
  maxConcurrentConnections?: number;
14
+ maxQueuedRequests?: number;
14
15
  clientMode?: boolean;
15
16
  clientHost?: string;
16
17
  clientPort?: number;
@@ -0,0 +1,5 @@
1
+ export declare const ACTOR_CLASS_ALIASES: Record<string, string>;
2
+ export declare const CLASSES_REQUIRING_COMPONENT: Record<string, string>;
3
+ export declare function resolveClassAlias(classNameOrPath: string): string;
4
+ export declare function getRequiredComponent(className: string): string | undefined;
5
+ //# sourceMappingURL=class-aliases.d.ts.map
@@ -0,0 +1,30 @@
1
+ export const ACTOR_CLASS_ALIASES = {
2
+ 'SplineActor': '/Script/Engine.Actor',
3
+ 'Spline': '/Script/Engine.Actor',
4
+ 'PointLight': '/Script/Engine.PointLight',
5
+ 'SpotLight': '/Script/Engine.SpotLight',
6
+ 'DirectionalLight': '/Script/Engine.DirectionalLight',
7
+ 'RectLight': '/Script/Engine.RectLight',
8
+ 'Camera': '/Script/Engine.CameraActor',
9
+ 'CameraActor': '/Script/Engine.CameraActor',
10
+ 'StaticMeshActor': '/Script/Engine.StaticMeshActor',
11
+ 'SkeletalMeshActor': '/Script/Engine.SkeletalMeshActor',
12
+ 'PlayerStart': '/Script/Engine.PlayerStart',
13
+ 'Pawn': '/Script/Engine.Pawn',
14
+ 'Character': '/Script/Engine.Character',
15
+ 'Actor': '/Script/Engine.Actor',
16
+ 'TriggerBox': '/Script/Engine.TriggerBox',
17
+ 'TriggerSphere': '/Script/Engine.TriggerSphere',
18
+ 'BlockingVolume': '/Script/Engine.BlockingVolume',
19
+ };
20
+ export const CLASSES_REQUIRING_COMPONENT = {
21
+ 'SplineActor': 'SplineComponent',
22
+ 'Spline': 'SplineComponent',
23
+ };
24
+ export function resolveClassAlias(classNameOrPath) {
25
+ return ACTOR_CLASS_ALIASES[classNameOrPath] || classNameOrPath;
26
+ }
27
+ export function getRequiredComponent(className) {
28
+ return CLASSES_REQUIRING_COMPONENT[className];
29
+ }
30
+ //# sourceMappingURL=class-aliases.js.map
@@ -5,6 +5,7 @@ export declare const DEFAULT_NEGOTIATED_PROTOCOLS: string[];
5
5
  export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 10000;
6
6
  export declare const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
7
7
  export declare const DEFAULT_MAX_PENDING_REQUESTS = 25;
8
+ export declare const DEFAULT_MAX_QUEUED_REQUESTS = 100;
8
9
  export declare const DEFAULT_TIME_OF_DAY = 9;
9
10
  export declare const DEFAULT_SUN_INTENSITY = 10000;
10
11
  export declare const DEFAULT_SKYLIGHT_INTENSITY = 1;
@@ -13,4 +14,8 @@ export declare const DEFAULT_OPERATION_TIMEOUT_MS = 30000;
13
14
  export declare const DEFAULT_ASSET_OP_TIMEOUT_MS = 60000;
14
15
  export declare const EXTENDED_ASSET_OP_TIMEOUT_MS = 120000;
15
16
  export declare const LONG_RUNNING_OP_TIMEOUT_MS = 300000;
17
+ export declare const CONSOLE_COMMAND_TIMEOUT_MS = 30000;
18
+ export declare const ENGINE_QUERY_TIMEOUT_MS = 15000;
19
+ export declare const CONNECTION_TIMEOUT_MS = 15000;
20
+ export declare const MAX_WS_MESSAGE_SIZE_BYTES: number;
16
21
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -5,6 +5,7 @@ export const DEFAULT_NEGOTIATED_PROTOCOLS = ['mcp-automation'];
5
5
  export const DEFAULT_HEARTBEAT_INTERVAL_MS = 10000;
6
6
  export const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
7
7
  export const DEFAULT_MAX_PENDING_REQUESTS = 25;
8
+ export const DEFAULT_MAX_QUEUED_REQUESTS = 100;
8
9
  export const DEFAULT_TIME_OF_DAY = 9;
9
10
  export const DEFAULT_SUN_INTENSITY = 10000;
10
11
  export const DEFAULT_SKYLIGHT_INTENSITY = 1;
@@ -13,4 +14,8 @@ export const DEFAULT_OPERATION_TIMEOUT_MS = 30000;
13
14
  export const DEFAULT_ASSET_OP_TIMEOUT_MS = 60000;
14
15
  export const EXTENDED_ASSET_OP_TIMEOUT_MS = 120000;
15
16
  export const LONG_RUNNING_OP_TIMEOUT_MS = 300000;
17
+ export const CONSOLE_COMMAND_TIMEOUT_MS = 30000;
18
+ export const ENGINE_QUERY_TIMEOUT_MS = 15000;
19
+ export const CONNECTION_TIMEOUT_MS = 15000;
20
+ export const MAX_WS_MESSAGE_SIZE_BYTES = 5 * 1024 * 1024;
16
21
  //# sourceMappingURL=constants.js.map
@@ -19,7 +19,6 @@ export declare class GraphQLServer {
19
19
  constructor(bridge: UnrealBridge, automationBridge: AutomationBridge, config?: GraphQLServerConfig);
20
20
  start(): Promise<void>;
21
21
  stop(): Promise<void>;
22
- private setupShutdown;
23
22
  getConfig(): Required<GraphQLServerConfig>;
24
23
  isRunning(): boolean;
25
24
  }
@@ -28,6 +28,21 @@ export class GraphQLServer {
28
28
  this.log.info('GraphQL server is disabled');
29
29
  return;
30
30
  }
31
+ const isLoopback = this.config.host === '127.0.0.1' ||
32
+ this.config.host === '::1' ||
33
+ this.config.host.toLowerCase() === 'localhost';
34
+ const allowRemote = process.env.GRAPHQL_ALLOW_REMOTE === 'true';
35
+ if (!isLoopback && !allowRemote) {
36
+ this.log.warn(`GraphQL server is configured to bind to non-loopback host '${this.config.host}'. GraphQL is for local debugging only. ` +
37
+ 'To allow remote binding, set GRAPHQL_ALLOW_REMOTE=true. Aborting start.');
38
+ return;
39
+ }
40
+ if (!isLoopback && allowRemote) {
41
+ if (this.config.cors.origin === '*') {
42
+ this.log.warn("GraphQL server is binding to a remote host with permissive CORS origin '*'. " +
43
+ 'Set GRAPHQL_CORS_ORIGIN to specific origins for production. Using permissive CORS for now.');
44
+ }
45
+ }
31
46
  try {
32
47
  const schema = createGraphQLSchema(this.bridge, this.automationBridge);
33
48
  const yoga = createYoga({
@@ -67,7 +82,6 @@ export class GraphQLServer {
67
82
  resolve();
68
83
  });
69
84
  });
70
- this.setupShutdown();
71
85
  }
72
86
  catch (error) {
73
87
  this.log.error('Failed to start GraphQL server:', error);
@@ -92,21 +106,6 @@ export class GraphQLServer {
92
106
  });
93
107
  });
94
108
  }
95
- setupShutdown() {
96
- const gracefulShutdown = async (signal) => {
97
- this.log.info(`Received ${signal}, shutting down GraphQL server...`);
98
- try {
99
- await this.stop();
100
- process.exit(0);
101
- }
102
- catch (error) {
103
- this.log.error('Error during GraphQL server shutdown:', error);
104
- process.exit(1);
105
- }
106
- };
107
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
108
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
109
- }
110
109
  getConfig() {
111
110
  return this.config;
112
111
  }
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ const DEFAULT_SERVER_NAME = typeof packageInfo.name === 'string' && packageInfo.
29
29
  : 'unreal-engine-mcp';
30
30
  const DEFAULT_SERVER_VERSION = typeof packageInfo.version === 'string' && packageInfo.version.trim().length > 0
31
31
  ? packageInfo.version
32
- : '0.0.0';
32
+ : '0.5.4';
33
33
  function routeStdoutLogsToStderr() {
34
34
  if (!config.MCP_ROUTE_STDOUT_LOGS) {
35
35
  return;
@@ -1,3 +1,4 @@
1
+ import http from 'http';
1
2
  import { HealthMonitor } from './health-monitor.js';
2
3
  import { AutomationBridge } from '../automation/index.js';
3
4
  import { Logger } from '../utils/logger.js';
@@ -6,6 +7,6 @@ interface MetricsServerOptions {
6
7
  automationBridge: AutomationBridge;
7
8
  logger: Logger;
8
9
  }
9
- export declare function startMetricsServer(options: MetricsServerOptions): void;
10
+ export declare function startMetricsServer(options: MetricsServerOptions): http.Server | null;
10
11
  export {};
11
12
  //# sourceMappingURL=metrics-server.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import http from 'http';
2
2
  import { wasmIntegration } from '../wasm/index.js';
3
- import { DEFAULT_AUTOMATION_HOST } from '../constants.js';
4
3
  function formatPrometheusMetrics(options) {
5
4
  const { healthMonitor, automationBridge } = options;
6
5
  const m = healthMonitor.metrics;
@@ -59,7 +58,24 @@ export function startMetricsServer(options) {
59
58
  const port = portEnv ? Number(portEnv) : 0;
60
59
  if (!port || !Number.isFinite(port) || port <= 0) {
61
60
  logger.debug('Metrics server disabled (set MCP_METRICS_PORT to enable Prometheus /metrics endpoint).');
62
- return;
61
+ return null;
62
+ }
63
+ const host = process.env.MCP_METRICS_HOST || '127.0.0.1';
64
+ const RATE_LIMIT_WINDOW_MS = 60000;
65
+ const RATE_LIMIT_MAX_REQUESTS = 60;
66
+ const requestCounts = new Map();
67
+ function checkRateLimit(ip) {
68
+ const now = Date.now();
69
+ const record = requestCounts.get(ip);
70
+ if (!record || now >= record.resetAt) {
71
+ requestCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
72
+ return true;
73
+ }
74
+ if (record.count >= RATE_LIMIT_MAX_REQUESTS) {
75
+ return false;
76
+ }
77
+ record.count++;
78
+ return true;
63
79
  }
64
80
  try {
65
81
  const server = http.createServer((req, res) => {
@@ -79,6 +95,13 @@ export function startMetricsServer(options) {
79
95
  res.end('Not Found');
80
96
  return;
81
97
  }
98
+ const clientIp = req.socket.remoteAddress || 'unknown';
99
+ if (!checkRateLimit(clientIp)) {
100
+ res.statusCode = 429;
101
+ res.setHeader('Retry-After', '60');
102
+ res.end('Too Many Requests');
103
+ return;
104
+ }
82
105
  try {
83
106
  const body = formatPrometheusMetrics(options);
84
107
  res.statusCode = 200;
@@ -91,15 +114,17 @@ export function startMetricsServer(options) {
91
114
  res.end('Internal Server Error');
92
115
  }
93
116
  });
94
- server.listen(port, () => {
95
- logger.info(`Prometheus metrics server listening on http://${DEFAULT_AUTOMATION_HOST}:${port}/metrics`);
117
+ server.listen(port, host, () => {
118
+ logger.info(`Prometheus metrics server listening on http://${host}:${port}/metrics`);
96
119
  });
97
120
  server.on('error', (err) => {
98
121
  logger.warn('Metrics server error', err);
99
122
  });
123
+ return server;
100
124
  }
101
125
  catch (err) {
102
126
  logger.warn('Failed to start metrics server', err);
127
+ return null;
103
128
  }
104
129
  }
105
130
  //# sourceMappingURL=metrics-server.js.map
@@ -420,7 +420,7 @@ Supported actions:
420
420
  'create_volumetric_fog', 'create_particle_trail', 'create_environment_effect', 'create_impact_effect', 'create_niagara_ribbon',
421
421
  'activate', 'activate_effect', 'deactivate', 'reset', 'advance_simulation',
422
422
  'add_niagara_module', 'connect_niagara_pins', 'remove_niagara_node', 'set_niagara_parameter',
423
- 'clear_debug_shapes', 'cleanup'
423
+ 'clear_debug_shapes', 'cleanup', 'list_debug_shapes'
424
424
  ],
425
425
  description: 'Action'
426
426
  },
@@ -636,7 +636,7 @@ Supported actions:
636
636
  'get_properties', 'set_properties', 'duplicate', 'rename', 'delete', 'list', 'get_metadata', 'set_metadata',
637
637
  'add_spawnable_from_class', 'add_track', 'add_section', 'set_display_rate', 'set_tick_resolution',
638
638
  'set_work_range', 'set_view_range', 'set_track_muted', 'set_track_solo', 'set_track_locked',
639
- 'list_tracks', 'remove_track'
639
+ 'list_tracks', 'remove_track', 'list_track_types'
640
640
  ],
641
641
  description: 'Action'
642
642
  },
@@ -969,7 +969,7 @@ Supported actions:
969
969
  'spawn_light', 'create_light', 'spawn_sky_light', 'create_sky_light', 'ensure_single_sky_light',
970
970
  'create_lightmass_volume', 'create_lighting_enabled_level', 'create_dynamic_light',
971
971
  'setup_global_illumination', 'configure_shadows', 'set_exposure', 'set_ambient_occlusion', 'setup_volumetric_fog',
972
- 'build_lighting'
972
+ 'build_lighting', 'list_light_types'
973
973
  ],
974
974
  description: 'Action'
975
975
  },
@@ -301,5 +301,10 @@ export declare class DebugVisualizationTools {
301
301
  error: string;
302
302
  message?: undefined;
303
303
  }>;
304
+ listDebugShapes(): Promise<import("../automation/types.js").AutomationBridgeResponseMessage | {
305
+ success: boolean;
306
+ error: string;
307
+ message: string;
308
+ }>;
304
309
  }
305
310
  //# sourceMappingURL=debug.d.ts.map
@@ -449,5 +449,12 @@ export class DebugVisualizationTools {
449
449
  return { success: false, error: `Failed to clear debug shapes: ${err}` };
450
450
  }
451
451
  }
452
+ async listDebugShapes() {
453
+ if (this.automationBridge) {
454
+ const response = await this.automationBridge.sendAutomationRequest('list_debug_shapes', {});
455
+ return response;
456
+ }
457
+ return { success: false, error: 'AUTOMATION_BRIDGE_REQUIRED', message: 'Listing debug shapes requires Automation Bridge' };
458
+ }
452
459
  }
453
460
  //# sourceMappingURL=debug.js.map