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.
- package/CHANGELOG.md +195 -0
- package/README.md +9 -6
- package/dist/automation/bridge.d.ts +1 -0
- package/dist/automation/bridge.js +62 -4
- package/dist/automation/types.d.ts +1 -0
- package/dist/config/class-aliases.d.ts +5 -0
- package/dist/config/class-aliases.js +30 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/graphql/server.d.ts +0 -1
- package/dist/graphql/server.js +15 -16
- package/dist/index.js +1 -1
- package/dist/services/metrics-server.d.ts +2 -1
- package/dist/services/metrics-server.js +29 -4
- package/dist/tools/consolidated-tool-definitions.js +3 -3
- package/dist/tools/debug.d.ts +5 -0
- package/dist/tools/debug.js +7 -0
- package/dist/tools/handlers/actor-handlers.js +4 -27
- package/dist/tools/handlers/asset-handlers.js +13 -1
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -1
- package/dist/tools/handlers/common-handlers.d.ts +11 -11
- package/dist/tools/handlers/common-handlers.js +6 -4
- package/dist/tools/handlers/editor-handlers.d.ts +2 -1
- package/dist/tools/handlers/editor-handlers.js +6 -6
- package/dist/tools/handlers/effect-handlers.js +3 -0
- package/dist/tools/handlers/graph-handlers.d.ts +2 -1
- package/dist/tools/handlers/graph-handlers.js +1 -1
- package/dist/tools/handlers/input-handlers.d.ts +5 -1
- package/dist/tools/handlers/level-handlers.d.ts +2 -1
- package/dist/tools/handlers/level-handlers.js +3 -3
- package/dist/tools/handlers/lighting-handlers.d.ts +2 -1
- package/dist/tools/handlers/lighting-handlers.js +3 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +2 -1
- package/dist/tools/handlers/pipeline-handlers.js +64 -10
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/system-handlers.d.ts +1 -1
- package/dist/tools/input.d.ts +5 -1
- package/dist/tools/input.js +37 -1
- package/dist/tools/lighting.d.ts +1 -0
- package/dist/tools/lighting.js +7 -0
- package/dist/tools/physics.d.ts +1 -1
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +7 -0
- package/dist/types/handler-types.d.ts +343 -0
- package/dist/types/handler-types.js +2 -0
- package/dist/unreal-bridge.d.ts +1 -1
- package/dist/unreal-bridge.js +8 -6
- package/dist/utils/command-validator.d.ts +1 -0
- package/dist/utils/command-validator.js +11 -1
- package/dist/utils/error-handler.js +3 -1
- package/dist/utils/response-validator.js +2 -2
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.js +3 -6
- package/dist/utils/unreal-command-queue.js +1 -1
- package/dist/utils/validation.js +6 -2
- package/docs/handler-mapping.md +6 -1
- package/package.json +2 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +25 -1
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +40 -58
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +27 -46
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +16 -1
- package/server.json +2 -2
- package/src/automation/bridge.ts +80 -10
- package/src/automation/types.ts +1 -0
- package/src/config/class-aliases.ts +65 -0
- package/src/constants.ts +10 -0
- package/src/graphql/server.ts +23 -23
- package/src/index.ts +1 -1
- package/src/services/metrics-server.ts +40 -6
- package/src/tools/consolidated-tool-definitions.ts +3 -3
- package/src/tools/debug.ts +8 -0
- package/src/tools/handlers/actor-handlers.ts +5 -31
- package/src/tools/handlers/asset-handlers.ts +19 -1
- package/src/tools/handlers/blueprint-handlers.ts +1 -1
- package/src/tools/handlers/common-handlers.ts +32 -11
- package/src/tools/handlers/editor-handlers.ts +8 -7
- package/src/tools/handlers/effect-handlers.ts +4 -0
- package/src/tools/handlers/graph-handlers.ts +7 -6
- package/src/tools/handlers/level-handlers.ts +5 -4
- package/src/tools/handlers/lighting-handlers.ts +5 -1
- package/src/tools/handlers/pipeline-handlers.ts +83 -16
- package/src/tools/input.ts +60 -1
- package/src/tools/lighting.ts +11 -0
- package/src/tools/physics.ts +1 -1
- package/src/tools/sequence.ts +11 -0
- package/src/types/handler-types.ts +442 -0
- package/src/unreal-bridge.ts +8 -6
- package/src/utils/command-validator.ts +23 -1
- package/src/utils/error-handler.ts +4 -1
- package/src/utils/response-validator.ts +7 -9
- package/src/utils/safe-json.ts +20 -15
- package/src/utils/unreal-command-queue.ts +3 -1
- package/src/utils/validation.test.ts +3 -3
- package/src/utils/validation.ts +36 -26
- package/tests/test-console-command.mjs +1 -1
- package/tests/test-runner.mjs +63 -3
- package/tests/run-unreal-tool-tests.mjs +0 -948
- 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
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
@@ -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
|
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
package/dist/graphql/server.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/graphql/server.js
CHANGED
|
@@ -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.
|
|
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):
|
|
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://${
|
|
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
|
},
|
package/dist/tools/debug.d.ts
CHANGED
|
@@ -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
|
package/dist/tools/debug.js
CHANGED
|
@@ -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
|