unreal-engine-mcp-server 0.5.1 → 0.5.2
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/.github/workflows/publish-mcp.yml +1 -4
- package/.github/workflows/release-drafter.yml +2 -1
- package/CHANGELOG.md +38 -0
- package/dist/automation/bridge.d.ts +1 -2
- package/dist/automation/bridge.js +24 -23
- package/dist/automation/connection-manager.d.ts +1 -0
- package/dist/automation/connection-manager.js +10 -0
- package/dist/automation/message-handler.js +5 -4
- package/dist/automation/request-tracker.d.ts +4 -0
- package/dist/automation/request-tracker.js +11 -3
- package/dist/tools/actors.d.ts +19 -1
- package/dist/tools/actors.js +15 -5
- package/dist/tools/assets.js +1 -1
- package/dist/tools/blueprint.d.ts +12 -0
- package/dist/tools/blueprint.js +43 -14
- package/dist/tools/consolidated-tool-definitions.js +2 -1
- package/dist/tools/editor.js +3 -2
- package/dist/tools/handlers/actor-handlers.d.ts +1 -1
- package/dist/tools/handlers/actor-handlers.js +14 -8
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/sequence-handlers.js +24 -13
- package/dist/tools/introspection.d.ts +1 -1
- package/dist/tools/introspection.js +1 -1
- package/dist/tools/level.js +3 -3
- package/dist/tools/lighting.d.ts +54 -7
- package/dist/tools/lighting.js +4 -4
- package/dist/tools/materials.d.ts +1 -1
- package/dist/types/tool-types.d.ts +2 -0
- package/dist/unreal-bridge.js +4 -4
- package/dist/utils/command-validator.js +6 -5
- package/dist/utils/error-handler.d.ts +24 -2
- package/dist/utils/error-handler.js +58 -23
- package/dist/utils/normalize.d.ts +7 -4
- package/dist/utils/normalize.js +12 -10
- package/dist/utils/response-validator.js +88 -73
- package/dist/utils/unreal-command-queue.d.ts +2 -0
- package/dist/utils/unreal-command-queue.js +8 -1
- package/docs/handler-mapping.md +4 -2
- package/package.json +1 -1
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
- package/server.json +3 -3
- package/src/automation/bridge.ts +27 -25
- package/src/automation/connection-manager.ts +18 -0
- package/src/automation/message-handler.ts +33 -8
- package/src/automation/request-tracker.ts +39 -7
- package/src/server/tool-registry.ts +3 -3
- package/src/tools/actors.ts +44 -19
- package/src/tools/assets.ts +3 -3
- package/src/tools/blueprint.ts +115 -49
- package/src/tools/consolidated-tool-definitions.ts +2 -1
- package/src/tools/editor.ts +4 -3
- package/src/tools/handlers/actor-handlers.ts +14 -9
- package/src/tools/handlers/sequence-handlers.ts +86 -63
- package/src/tools/introspection.ts +7 -7
- package/src/tools/level.ts +6 -6
- package/src/tools/lighting.ts +19 -19
- package/src/tools/materials.ts +1 -1
- package/src/tools/sequence.ts +1 -1
- package/src/tools/ui.ts +1 -1
- package/src/types/tool-types.ts +4 -0
- package/src/unreal-bridge.ts +71 -26
- package/src/utils/command-validator.ts +46 -5
- package/src/utils/error-handler.ts +128 -45
- package/src/utils/normalize.ts +38 -16
- package/src/utils/response-validator.ts +103 -87
- package/src/utils/unreal-command-queue.ts +13 -1
|
@@ -61,17 +61,14 @@ jobs:
|
|
|
61
61
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
62
62
|
|
|
63
63
|
- name: Install MCP Publisher
|
|
64
|
-
if: ${{ success() }}
|
|
65
64
|
run: |
|
|
66
|
-
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.
|
|
65
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.4.0/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
|
67
66
|
chmod +x mcp-publisher
|
|
68
67
|
|
|
69
68
|
- name: Login to MCP Registry
|
|
70
|
-
if: ${{ success() }}
|
|
71
69
|
run: ./mcp-publisher login github-oidc
|
|
72
70
|
|
|
73
71
|
- name: Publish to MCP Registry
|
|
74
|
-
if: ${{ success() }}
|
|
75
72
|
run: ./mcp-publisher publish
|
|
76
73
|
|
|
77
74
|
- name: Verify publication
|
|
@@ -15,8 +15,9 @@ jobs:
|
|
|
15
15
|
contents: write
|
|
16
16
|
pull-requests: write
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 15
|
|
18
19
|
steps:
|
|
19
|
-
- uses: release-drafter/release-drafter@v6
|
|
20
|
+
- uses: release-drafter/release-drafter@v6
|
|
20
21
|
with:
|
|
21
22
|
config-name: release-drafter-config.yml
|
|
22
23
|
env:
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## 🏷️ [0.5.2] - 2025-12-18
|
|
11
|
+
|
|
12
|
+
> [!IMPORTANT]
|
|
13
|
+
> ### 🔄 Breaking Changes
|
|
14
|
+
> - **Standardized Tools & Type Safety** - All tool handlers now use consistent interfaces with improved type safety. Some internal API signatures have changed. (`079e3c2`)
|
|
15
|
+
|
|
16
|
+
### ✨ Added
|
|
17
|
+
|
|
18
|
+
<details>
|
|
19
|
+
<summary><b>🛠️ Blueprint Enhancements</b> (<code>e710751</code>)</summary>
|
|
20
|
+
|
|
21
|
+
| Feature | Description |
|
|
22
|
+
|---------|-------------|
|
|
23
|
+
| **Dynamic Node Creation** | Support for creating nodes dynamically in Blueprint graphs |
|
|
24
|
+
| **Struct Property Support** | Added ability to set and get struct properties on Blueprint components |
|
|
25
|
+
|
|
26
|
+
</details>
|
|
27
|
+
|
|
28
|
+
### 🔄 Changed
|
|
29
|
+
|
|
30
|
+
<details>
|
|
31
|
+
<summary><b>🎯 Standardized Tool Interfaces</b> (<a href="https://github.com/ChiR24/Unreal_mcp/pull/28">#28</a>)</summary>
|
|
32
|
+
|
|
33
|
+
| Component | Change |
|
|
34
|
+
|-----------|--------|
|
|
35
|
+
| Tool Handlers | Optimized bridge communication and standardized response handling |
|
|
36
|
+
| Type Safety | Hardened type definitions across all tool interfaces |
|
|
37
|
+
| Bridge Optimization | Improved performance and reliability of automation bridge |
|
|
38
|
+
|
|
39
|
+
</details>
|
|
40
|
+
|
|
41
|
+
### 🔧 CI/CD
|
|
42
|
+
|
|
43
|
+
- 🔗 **MCP Publisher** - Fixed download URL format in workflow steps (`0d452e7`)
|
|
44
|
+
- 🧹 **Workflow Cleanup** - Removed unnecessary success conditions from MCP workflow steps (`82bd575`)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
10
48
|
## 🏷️ [0.5.1] - 2025-12-17
|
|
11
49
|
|
|
12
50
|
> [!WARNING]
|
|
@@ -24,9 +24,9 @@ export declare class AutomationBridge extends EventEmitter {
|
|
|
24
24
|
private lastHandshakeFailure?;
|
|
25
25
|
private lastDisconnect?;
|
|
26
26
|
private lastError?;
|
|
27
|
-
private requestQueue;
|
|
28
27
|
private queuedRequestItems;
|
|
29
28
|
private connectionPromise?;
|
|
29
|
+
private connectionLock;
|
|
30
30
|
constructor(options?: AutomationBridgeOptions);
|
|
31
31
|
on<K extends keyof AutomationBridgeEvents>(event: K, listener: AutomationBridgeEvents[K]): this;
|
|
32
32
|
once<K extends keyof AutomationBridgeEvents>(event: K, listener: AutomationBridgeEvents[K]): this;
|
|
@@ -44,7 +44,6 @@ export declare class AutomationBridge extends EventEmitter {
|
|
|
44
44
|
private processRequestQueue;
|
|
45
45
|
send(payload: AutomationBridgeMessage): boolean;
|
|
46
46
|
private broadcast;
|
|
47
|
-
private flushQueue;
|
|
48
47
|
private emitAutomation;
|
|
49
48
|
}
|
|
50
49
|
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -42,9 +42,9 @@ export class AutomationBridge extends EventEmitter {
|
|
|
42
42
|
lastHandshakeFailure;
|
|
43
43
|
lastDisconnect;
|
|
44
44
|
lastError;
|
|
45
|
-
requestQueue = [];
|
|
46
45
|
queuedRequestItems = [];
|
|
47
46
|
connectionPromise;
|
|
47
|
+
connectionLock = false;
|
|
48
48
|
constructor(options = {}) {
|
|
49
49
|
super();
|
|
50
50
|
this.host = options.host ?? process.env.MCP_AUTOMATION_WS_HOST ?? DEFAULT_AUTOMATION_HOST;
|
|
@@ -162,12 +162,12 @@ export class AutomationBridge extends EventEmitter {
|
|
|
162
162
|
this.lastHandshakeMetadata = metadata;
|
|
163
163
|
this.lastHandshakeFailure = undefined;
|
|
164
164
|
this.connectionManager.updateLastMessageTime();
|
|
165
|
-
const
|
|
165
|
+
const socketWithInternal = socket;
|
|
166
|
+
const underlying = socketWithInternal._socket || socketWithInternal.socket;
|
|
166
167
|
const remoteAddr = underlying?.remoteAddress ?? undefined;
|
|
167
168
|
const remotePort = underlying?.remotePort ?? undefined;
|
|
168
169
|
this.connectionManager.registerSocket(socket, this.clientPort, metadata, remoteAddr, remotePort);
|
|
169
170
|
this.connectionManager.startHeartbeat();
|
|
170
|
-
this.flushQueue();
|
|
171
171
|
this.emitAutomation('connected', {
|
|
172
172
|
socket,
|
|
173
173
|
metadata,
|
|
@@ -273,7 +273,7 @@ export class AutomationBridge extends EventEmitter {
|
|
|
273
273
|
? { message: this.lastError.message, at: this.lastError.at.toISOString() }
|
|
274
274
|
: null,
|
|
275
275
|
lastMessageAt: this.connectionManager.getLastMessageTime()?.toISOString() ?? null,
|
|
276
|
-
lastRequestSentAt: null,
|
|
276
|
+
lastRequestSentAt: this.requestTracker.getLastRequestSentAt()?.toISOString() ?? null,
|
|
277
277
|
pendingRequests: this.requestTracker.getPendingCount(),
|
|
278
278
|
pendingRequestDetails: this.requestTracker.getPendingDetails(),
|
|
279
279
|
connections: connectionInfos,
|
|
@@ -282,15 +282,16 @@ export class AutomationBridge extends EventEmitter {
|
|
|
282
282
|
serverName: this.serverName,
|
|
283
283
|
serverVersion: this.serverVersion,
|
|
284
284
|
maxConcurrentConnections: this.maxConcurrentConnections,
|
|
285
|
-
maxPendingRequests:
|
|
286
|
-
heartbeatIntervalMs:
|
|
285
|
+
maxPendingRequests: this.requestTracker.getMaxPendingRequests(),
|
|
286
|
+
heartbeatIntervalMs: this.connectionManager.getHeartbeatIntervalMs()
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
async sendAutomationRequest(action, payload = {}, options = {}) {
|
|
290
290
|
if (!this.isConnected()) {
|
|
291
291
|
if (this.enabled) {
|
|
292
292
|
this.log.info('Automation bridge not connected, attempting lazy connection...');
|
|
293
|
-
if (!this.connectionPromise) {
|
|
293
|
+
if (!this.connectionPromise && !this.connectionLock) {
|
|
294
|
+
this.connectionLock = true;
|
|
294
295
|
this.connectionPromise = new Promise((resolve, reject) => {
|
|
295
296
|
const onConnect = () => {
|
|
296
297
|
cleanup();
|
|
@@ -308,8 +309,8 @@ export class AutomationBridge extends EventEmitter {
|
|
|
308
309
|
this.off('connected', onConnect);
|
|
309
310
|
this.off('error', onError);
|
|
310
311
|
this.off('handshakeFailed', onHandshakeFail);
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
this.connectionLock = false;
|
|
313
|
+
this.connectionPromise = undefined;
|
|
313
314
|
};
|
|
314
315
|
this.once('connected', onConnect);
|
|
315
316
|
this.once('error', onError);
|
|
@@ -324,10 +325,17 @@ export class AutomationBridge extends EventEmitter {
|
|
|
324
325
|
}
|
|
325
326
|
try {
|
|
326
327
|
const connectTimeout = 5000;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
328
|
+
let timeoutId;
|
|
329
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
330
|
+
timeoutId = setTimeout(() => reject(new Error('Lazy connection timeout')), connectTimeout);
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
await Promise.race([this.connectionPromise, timeoutPromise]);
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
if (timeoutId)
|
|
337
|
+
clearTimeout(timeoutId);
|
|
338
|
+
}
|
|
331
339
|
}
|
|
332
340
|
catch (err) {
|
|
333
341
|
this.log.error('Lazy connection failed', err);
|
|
@@ -341,7 +349,7 @@ export class AutomationBridge extends EventEmitter {
|
|
|
341
349
|
if (!this.isConnected()) {
|
|
342
350
|
throw new Error('Automation bridge not connected');
|
|
343
351
|
}
|
|
344
|
-
if (this.requestTracker.getPendingCount() >= this.requestTracker.
|
|
352
|
+
if (this.requestTracker.getPendingCount() >= this.requestTracker.getMaxPendingRequests()) {
|
|
345
353
|
return new Promise((resolve, reject) => {
|
|
346
354
|
this.queuedRequestItems.push({
|
|
347
355
|
resolve,
|
|
@@ -378,6 +386,7 @@ export class AutomationBridge extends EventEmitter {
|
|
|
378
386
|
this.processRequestQueue();
|
|
379
387
|
}).catch(() => { });
|
|
380
388
|
if (this.send(message)) {
|
|
389
|
+
this.requestTracker.updateLastRequestSentAt();
|
|
381
390
|
return resultPromise;
|
|
382
391
|
}
|
|
383
392
|
else {
|
|
@@ -389,7 +398,7 @@ export class AutomationBridge extends EventEmitter {
|
|
|
389
398
|
if (this.queuedRequestItems.length === 0)
|
|
390
399
|
return;
|
|
391
400
|
while (this.queuedRequestItems.length > 0 &&
|
|
392
|
-
this.requestTracker.getPendingCount() < this.requestTracker.
|
|
401
|
+
this.requestTracker.getPendingCount() < this.requestTracker.getMaxPendingRequests()) {
|
|
393
402
|
const item = this.queuedRequestItems.shift();
|
|
394
403
|
if (item) {
|
|
395
404
|
this.sendRequestInternal(item.action, item.payload, item.options)
|
|
@@ -437,14 +446,6 @@ export class AutomationBridge extends EventEmitter {
|
|
|
437
446
|
}
|
|
438
447
|
return sentCount > 0;
|
|
439
448
|
}
|
|
440
|
-
flushQueue() {
|
|
441
|
-
if (this.requestQueue.length === 0)
|
|
442
|
-
return;
|
|
443
|
-
this.log.info(`Flushing ${this.requestQueue.length} queued automation requests`);
|
|
444
|
-
const queue = [...this.requestQueue];
|
|
445
|
-
this.requestQueue = [];
|
|
446
|
-
queue.forEach(fn => fn());
|
|
447
|
-
}
|
|
448
449
|
emitAutomation(event, ...args) {
|
|
449
450
|
this.emit(event, ...args);
|
|
450
451
|
}
|
|
@@ -9,6 +9,7 @@ export declare class ConnectionManager extends EventEmitter {
|
|
|
9
9
|
private lastMessageAt?;
|
|
10
10
|
private log;
|
|
11
11
|
constructor(heartbeatIntervalMs: number);
|
|
12
|
+
getHeartbeatIntervalMs(): number;
|
|
12
13
|
registerSocket(socket: WebSocket, port: number, metadata?: Record<string, unknown>, remoteAddress?: string, remotePort?: number): void;
|
|
13
14
|
removeSocket(socket: WebSocket): SocketInfo | undefined;
|
|
14
15
|
getActiveSockets(): Map<WebSocket, SocketInfo>;
|
|
@@ -13,6 +13,9 @@ export class ConnectionManager extends EventEmitter {
|
|
|
13
13
|
super();
|
|
14
14
|
this.heartbeatIntervalMs = heartbeatIntervalMs;
|
|
15
15
|
}
|
|
16
|
+
getHeartbeatIntervalMs() {
|
|
17
|
+
return this.heartbeatIntervalMs;
|
|
18
|
+
}
|
|
16
19
|
registerSocket(socket, port, metadata, remoteAddress, remotePort) {
|
|
17
20
|
const connectionId = randomUUID();
|
|
18
21
|
const sessionId = metadata && typeof metadata.sessionId === 'string' ? metadata.sessionId : undefined;
|
|
@@ -32,6 +35,13 @@ export class ConnectionManager extends EventEmitter {
|
|
|
32
35
|
socket.on('pong', () => {
|
|
33
36
|
this.lastMessageAt = new Date();
|
|
34
37
|
});
|
|
38
|
+
socket.once('close', () => {
|
|
39
|
+
this.removeSocket(socket);
|
|
40
|
+
});
|
|
41
|
+
socket.once('error', (error) => {
|
|
42
|
+
this.log.error('Socket error in ConnectionManager', error);
|
|
43
|
+
this.removeSocket(socket);
|
|
44
|
+
});
|
|
35
45
|
}
|
|
36
46
|
removeSocket(socket) {
|
|
37
47
|
const info = this.activeSockets.get(socket);
|
|
@@ -92,7 +92,7 @@ export class MessageHandler {
|
|
|
92
92
|
const synthetic = {
|
|
93
93
|
type: 'automation_response',
|
|
94
94
|
requestId: reqId,
|
|
95
|
-
success: evtSuccess !== undefined ? evtSuccess :
|
|
95
|
+
success: evtSuccess !== undefined ? evtSuccess : baseSuccess,
|
|
96
96
|
message: typeof evt.result?.message === 'string' ? evt.result.message : (typeof evt.message === 'string' ? evt.message : FStringSafe(evt.event)),
|
|
97
97
|
error: typeof evt.result?.error === 'string' ? evt.result.error : undefined,
|
|
98
98
|
result: evt.result ?? evt.payload ?? undefined
|
|
@@ -113,8 +113,9 @@ export class MessageHandler {
|
|
|
113
113
|
const expected = (expectedAction || '').toString().toLowerCase();
|
|
114
114
|
const echoed = (() => {
|
|
115
115
|
const r = response;
|
|
116
|
-
const
|
|
117
|
-
|
|
116
|
+
const resultObj = response.result;
|
|
117
|
+
const candidate = (typeof r.action === 'string' && r.action) || (typeof resultObj?.action === 'string' && resultObj.action);
|
|
118
|
+
return candidate || undefined;
|
|
118
119
|
})();
|
|
119
120
|
if (expected && echoed && typeof echoed === 'string') {
|
|
120
121
|
const got = echoed.toLowerCase();
|
|
@@ -141,7 +142,7 @@ export class MessageHandler {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
catch (e) {
|
|
144
|
-
this.log.debug('enforceActionMatch check skipped', e);
|
|
145
|
+
this.log.debug('enforceActionMatch check skipped', e instanceof Error ? e.message : String(e));
|
|
145
146
|
}
|
|
146
147
|
return response;
|
|
147
148
|
}
|
|
@@ -3,7 +3,11 @@ export declare class RequestTracker {
|
|
|
3
3
|
private maxPendingRequests;
|
|
4
4
|
private pendingRequests;
|
|
5
5
|
private coalescedRequests;
|
|
6
|
+
private lastRequestSentAt?;
|
|
6
7
|
constructor(maxPendingRequests: number);
|
|
8
|
+
getMaxPendingRequests(): number;
|
|
9
|
+
getLastRequestSentAt(): Date | undefined;
|
|
10
|
+
updateLastRequestSentAt(): void;
|
|
7
11
|
createRequest(action: string, payload: Record<string, unknown>, timeoutMs: number): {
|
|
8
12
|
requestId: string;
|
|
9
13
|
promise: Promise<AutomationBridgeResponseMessage>;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
-
const WAIT_FOR_EVENT_ACTIONS = new Set([]);
|
|
3
2
|
export class RequestTracker {
|
|
4
3
|
maxPendingRequests;
|
|
5
4
|
pendingRequests = new Map();
|
|
6
5
|
coalescedRequests = new Map();
|
|
6
|
+
lastRequestSentAt;
|
|
7
7
|
constructor(maxPendingRequests) {
|
|
8
8
|
this.maxPendingRequests = maxPendingRequests;
|
|
9
9
|
}
|
|
10
|
+
getMaxPendingRequests() {
|
|
11
|
+
return this.maxPendingRequests;
|
|
12
|
+
}
|
|
13
|
+
getLastRequestSentAt() {
|
|
14
|
+
return this.lastRequestSentAt;
|
|
15
|
+
}
|
|
16
|
+
updateLastRequestSentAt() {
|
|
17
|
+
this.lastRequestSentAt = new Date();
|
|
18
|
+
}
|
|
10
19
|
createRequest(action, payload, timeoutMs) {
|
|
11
20
|
if (this.pendingRequests.size >= this.maxPendingRequests) {
|
|
12
21
|
throw new Error(`Max pending requests limit reached (${this.maxPendingRequests})`);
|
|
13
22
|
}
|
|
14
23
|
const requestId = randomUUID();
|
|
15
|
-
const waitForEvent = WAIT_FOR_EVENT_ACTIONS.has(action);
|
|
16
24
|
const promise = new Promise((resolve, reject) => {
|
|
17
25
|
const timeout = setTimeout(() => {
|
|
18
26
|
if (this.pendingRequests.has(requestId)) {
|
|
@@ -27,7 +35,7 @@ export class RequestTracker {
|
|
|
27
35
|
action,
|
|
28
36
|
payload,
|
|
29
37
|
requestedAt: new Date(),
|
|
30
|
-
waitForEvent,
|
|
38
|
+
waitForEvent: false,
|
|
31
39
|
eventTimeoutMs: timeoutMs
|
|
32
40
|
});
|
|
33
41
|
});
|
package/dist/tools/actors.d.ts
CHANGED
|
@@ -64,7 +64,25 @@ export declare class ActorTools extends BaseTool implements IActorTools {
|
|
|
64
64
|
z: number;
|
|
65
65
|
};
|
|
66
66
|
}): Promise<StandardActionResponse<any>>;
|
|
67
|
-
getTransform(actorName: string): Promise<
|
|
67
|
+
getTransform(actorName: string): Promise<{
|
|
68
|
+
success: boolean;
|
|
69
|
+
error: string | {
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
code?: string;
|
|
72
|
+
message: string;
|
|
73
|
+
};
|
|
74
|
+
message?: undefined;
|
|
75
|
+
location?: undefined;
|
|
76
|
+
rotation?: undefined;
|
|
77
|
+
scale?: undefined;
|
|
78
|
+
} | {
|
|
79
|
+
success: boolean;
|
|
80
|
+
message: string;
|
|
81
|
+
location: any;
|
|
82
|
+
rotation: any;
|
|
83
|
+
scale: any;
|
|
84
|
+
error?: undefined;
|
|
85
|
+
}>;
|
|
68
86
|
setVisibility(params: {
|
|
69
87
|
actorName: string;
|
|
70
88
|
visible: boolean;
|
package/dist/tools/actors.js
CHANGED
|
@@ -40,7 +40,8 @@ export class ActorTools extends BaseTool {
|
|
|
40
40
|
}, timeoutMs ? { timeoutMs } : undefined);
|
|
41
41
|
if (!response || !response.success) {
|
|
42
42
|
const error = response?.error;
|
|
43
|
-
const
|
|
43
|
+
const errorObj = typeof error === 'object' && error !== null ? error : null;
|
|
44
|
+
const errorMsg = typeof error === 'string' ? error : errorObj?.message || response?.message || 'Failed to spawn actor';
|
|
44
45
|
throw new Error(errorMsg);
|
|
45
46
|
}
|
|
46
47
|
const data = response.data || {};
|
|
@@ -226,10 +227,19 @@ export class ActorTools extends BaseTool {
|
|
|
226
227
|
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
227
228
|
throw new Error('Invalid actorName');
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return response;
|
|
232
|
-
}
|
|
230
|
+
const response = await this.sendRequest('get_transform', { actorName }, 'control_actor');
|
|
231
|
+
if (!response.success) {
|
|
232
|
+
return { success: false, error: response.error || `Failed to get transform for actor ${actorName}` };
|
|
233
|
+
}
|
|
234
|
+
const rawData = response.data ?? response.result ?? response;
|
|
235
|
+
const data = rawData?.data ?? rawData;
|
|
236
|
+
return {
|
|
237
|
+
success: true,
|
|
238
|
+
message: 'Transform retrieved',
|
|
239
|
+
location: data.location ?? data.Location,
|
|
240
|
+
rotation: data.rotation ?? data.Rotation,
|
|
241
|
+
scale: data.scale ?? data.Scale
|
|
242
|
+
};
|
|
233
243
|
}
|
|
234
244
|
async setVisibility(params) {
|
|
235
245
|
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
package/dist/tools/assets.js
CHANGED
|
@@ -222,7 +222,7 @@ export class AssetTools extends BaseTool {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
catch (e) {
|
|
225
|
-
return { success: false, error: `Analysis failed: ${e.message} ` };
|
|
225
|
+
return { success: false, error: `Analysis failed: ${e instanceof Error ? e.message : String(e)} ` };
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
async createThumbnail(params) {
|
|
@@ -233,6 +233,18 @@ export declare class BlueprintTools extends BaseTool implements IBlueprintTools
|
|
|
233
233
|
graphName?: string;
|
|
234
234
|
timeoutMs?: number;
|
|
235
235
|
}): Promise<StandardActionResponse>;
|
|
236
|
+
listNodeTypes(params?: {
|
|
237
|
+
blueprintPath?: string;
|
|
238
|
+
timeoutMs?: number;
|
|
239
|
+
}): Promise<StandardActionResponse>;
|
|
240
|
+
setPinDefaultValue(params: {
|
|
241
|
+
blueprintPath: string;
|
|
242
|
+
nodeId: string;
|
|
243
|
+
pinName: string;
|
|
244
|
+
value: string;
|
|
245
|
+
graphName?: string;
|
|
246
|
+
timeoutMs?: number;
|
|
247
|
+
}): Promise<StandardActionResponse>;
|
|
236
248
|
connectPins(params: {
|
|
237
249
|
blueprintName: string;
|
|
238
250
|
sourceNodeGuid: string;
|
package/dist/tools/blueprint.js
CHANGED
|
@@ -12,17 +12,20 @@ export class BlueprintTools extends BaseTool {
|
|
|
12
12
|
try {
|
|
13
13
|
const response = await this.sendAutomationRequest(action, payload, { timeoutMs: finalTimeout, waitForEvent: !!options?.waitForEvent, waitForEventTimeoutMs: options?.waitForEventTimeoutMs });
|
|
14
14
|
const success = response && response.success !== false;
|
|
15
|
-
const result = response.result ?? response;
|
|
15
|
+
const result = (response.result ?? response);
|
|
16
16
|
return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId };
|
|
17
17
|
}
|
|
18
18
|
catch (err) {
|
|
19
|
-
|
|
19
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20
|
+
return { success: false, error: errMsg, message: errMsg };
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
isUnknownActionResponse(res) {
|
|
23
24
|
if (!res)
|
|
24
25
|
return false;
|
|
25
|
-
const
|
|
26
|
+
const errStr = typeof res.error === 'string' ? res.error : '';
|
|
27
|
+
const msgStr = typeof res.message === 'string' ? res.message : '';
|
|
28
|
+
const txt = (errStr || msgStr).toLowerCase();
|
|
26
29
|
return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
|
|
27
30
|
}
|
|
28
31
|
buildCandidates(rawName) {
|
|
@@ -114,7 +117,7 @@ export class BlueprintTools extends BaseTool {
|
|
|
114
117
|
if (typeof params.save === 'boolean')
|
|
115
118
|
payload.save = params.save;
|
|
116
119
|
const res = await this.sendAction('blueprint_modify_scs', payload, { timeoutMs: params.timeoutMs, waitForEvent: !!params.waitForCompletion, waitForEventTimeoutMs: params.waitForCompletionTimeoutMs });
|
|
117
|
-
if (res && res.result && typeof res.result === 'object' && res.result
|
|
120
|
+
if (res && res.result && typeof res.result === 'object' && res.result?.error === 'SCS_UNAVAILABLE') {
|
|
118
121
|
this.pluginBlueprintActionsAvailable = false;
|
|
119
122
|
return { success: false, error: 'SCS_UNAVAILABLE', message: 'Plugin does not support construction script modification (blueprint_modify_scs)' };
|
|
120
123
|
}
|
|
@@ -441,8 +444,8 @@ export class BlueprintTools extends BaseTool {
|
|
|
441
444
|
}
|
|
442
445
|
const pluginResp = await this.sendAction('add_scs_component', payload, { timeoutMs: params.timeoutMs });
|
|
443
446
|
if (pluginResp && pluginResp.success === false) {
|
|
444
|
-
if (pluginResp
|
|
445
|
-
this.log.warn?.(`addSCSComponent reported warning: ${pluginResp
|
|
447
|
+
if (pluginResp?.message) {
|
|
448
|
+
this.log.warn?.(`addSCSComponent reported warning: ${pluginResp?.message}`);
|
|
446
449
|
}
|
|
447
450
|
}
|
|
448
451
|
if (pluginResp && pluginResp.success)
|
|
@@ -468,8 +471,8 @@ export class BlueprintTools extends BaseTool {
|
|
|
468
471
|
try {
|
|
469
472
|
const pluginResp = await this.sendAction('remove_scs_component', { blueprint_path: blueprintPath, component_name: componentName }, { timeoutMs: params.timeoutMs });
|
|
470
473
|
if (pluginResp && pluginResp.success === false) {
|
|
471
|
-
if (pluginResp
|
|
472
|
-
this.log.warn?.(`removeSCSComponent reported warning: ${pluginResp
|
|
474
|
+
if (pluginResp?.message) {
|
|
475
|
+
this.log.warn?.(`removeSCSComponent reported warning: ${pluginResp?.message}`);
|
|
473
476
|
}
|
|
474
477
|
}
|
|
475
478
|
if (pluginResp && pluginResp.success)
|
|
@@ -499,8 +502,8 @@ export class BlueprintTools extends BaseTool {
|
|
|
499
502
|
new_parent: params.newParent || ''
|
|
500
503
|
}, { timeoutMs: params.timeoutMs });
|
|
501
504
|
if (pluginResp && pluginResp.success === false) {
|
|
502
|
-
if (pluginResp
|
|
503
|
-
this.log.warn?.(`reparentSCSComponent reported warning: ${pluginResp
|
|
505
|
+
if (pluginResp?.message) {
|
|
506
|
+
this.log.warn?.(`reparentSCSComponent reported warning: ${pluginResp?.message}`);
|
|
504
507
|
}
|
|
505
508
|
}
|
|
506
509
|
if (pluginResp && pluginResp.success)
|
|
@@ -536,8 +539,8 @@ export class BlueprintTools extends BaseTool {
|
|
|
536
539
|
payload.scale = params.scale;
|
|
537
540
|
const pluginResp = await this.sendAction('set_scs_component_transform', payload, { timeoutMs: params.timeoutMs });
|
|
538
541
|
if (pluginResp && pluginResp.success === false) {
|
|
539
|
-
if (pluginResp
|
|
540
|
-
this.log.warn?.(`setSCSComponentTransform reported warning: ${pluginResp
|
|
542
|
+
if (pluginResp?.message) {
|
|
543
|
+
this.log.warn?.(`setSCSComponentTransform reported warning: ${pluginResp?.message}`);
|
|
541
544
|
}
|
|
542
545
|
}
|
|
543
546
|
if (pluginResp && pluginResp.success)
|
|
@@ -573,8 +576,8 @@ export class BlueprintTools extends BaseTool {
|
|
|
573
576
|
property_value: propertyValueJson
|
|
574
577
|
}, { timeoutMs: params.timeoutMs });
|
|
575
578
|
if (pluginResp && pluginResp.success === false) {
|
|
576
|
-
if (pluginResp
|
|
577
|
-
this.log.warn?.(`setSCSComponentProperty reported warning: ${pluginResp
|
|
579
|
+
if (pluginResp?.message) {
|
|
580
|
+
this.log.warn?.(`setSCSComponentProperty reported warning: ${pluginResp?.message}`);
|
|
578
581
|
}
|
|
579
582
|
}
|
|
580
583
|
if (pluginResp && pluginResp.success)
|
|
@@ -722,6 +725,32 @@ export class BlueprintTools extends BaseTool {
|
|
|
722
725
|
}, { timeoutMs: params.timeoutMs });
|
|
723
726
|
return res;
|
|
724
727
|
}
|
|
728
|
+
async listNodeTypes(params = {}) {
|
|
729
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
730
|
+
subAction: 'list_node_types',
|
|
731
|
+
blueprintPath: params.blueprintPath || '/Game/Temp',
|
|
732
|
+
graphName: 'EventGraph'
|
|
733
|
+
}, { timeoutMs: params.timeoutMs });
|
|
734
|
+
return res;
|
|
735
|
+
}
|
|
736
|
+
async setPinDefaultValue(params) {
|
|
737
|
+
const blueprintPath = coerceString(params.blueprintPath);
|
|
738
|
+
if (!blueprintPath)
|
|
739
|
+
return { success: false, error: 'INVALID_BLUEPRINT_PATH', message: 'Blueprint path is required' };
|
|
740
|
+
if (!params.nodeId)
|
|
741
|
+
return { success: false, error: 'INVALID_NODE_ID', message: 'Node ID is required' };
|
|
742
|
+
if (!params.pinName)
|
|
743
|
+
return { success: false, error: 'INVALID_PIN_NAME', message: 'Pin name is required' };
|
|
744
|
+
const res = await this.sendAction('manage_blueprint_graph', {
|
|
745
|
+
subAction: 'set_pin_default_value',
|
|
746
|
+
blueprintPath: blueprintPath,
|
|
747
|
+
graphName: params.graphName || 'EventGraph',
|
|
748
|
+
nodeId: params.nodeId,
|
|
749
|
+
pinName: params.pinName,
|
|
750
|
+
value: params.value
|
|
751
|
+
}, { timeoutMs: params.timeoutMs });
|
|
752
|
+
return res;
|
|
753
|
+
}
|
|
725
754
|
async connectPins(params) {
|
|
726
755
|
const candidates = this.buildCandidates(params.blueprintName);
|
|
727
756
|
const primary = candidates[0];
|
|
@@ -917,7 +917,8 @@ Supported actions: create_node, delete_node, connect_pins, break_pin_links, set_
|
|
|
917
917
|
type: 'string',
|
|
918
918
|
enum: [
|
|
919
919
|
'create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property',
|
|
920
|
-
'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'
|
|
920
|
+
'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details',
|
|
921
|
+
'list_node_types', 'set_pin_default_value'
|
|
921
922
|
],
|
|
922
923
|
description: 'Action'
|
|
923
924
|
},
|
package/dist/tools/editor.js
CHANGED
|
@@ -34,8 +34,9 @@ export class EditorTools extends BaseTool {
|
|
|
34
34
|
return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
|
|
35
35
|
}
|
|
36
36
|
catch (err) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
if (errMsg && /time.*out/i.test(errMsg)) {
|
|
39
|
+
return { success: false, error: `Timeout waiting for PIE to start: ${errMsg}` };
|
|
39
40
|
}
|
|
40
41
|
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
41
42
|
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ITools } from '../../types/tool-interfaces.js';
|
|
2
|
-
export declare function handleActorTools(action: string, args: any, tools: ITools): Promise<any
|
|
2
|
+
export declare function handleActorTools(action: string, args: any, tools: ITools): Promise<import("../../types/tool-interfaces.js").StandardActionResponse<any>>;
|
|
3
3
|
//# sourceMappingURL=actor-handlers.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cleanObject } from '../../utils/safe-json.js';
|
|
2
1
|
import { executeAutomationRequest } from './common-handlers.js';
|
|
3
2
|
import { normalizeArgs } from './argument-helper.js';
|
|
3
|
+
import { ResponseFactory } from '../../utils/response-factory.js';
|
|
4
4
|
const handlers = {
|
|
5
5
|
spawn: async (args, tools) => {
|
|
6
6
|
const classAliases = {
|
|
@@ -28,11 +28,11 @@ const handlers = {
|
|
|
28
28
|
]);
|
|
29
29
|
const timeoutMs = typeof params.timeoutMs === 'number' ? params.timeoutMs : undefined;
|
|
30
30
|
if (typeof timeoutMs === 'number' && timeoutMs > 0 && timeoutMs < 200) {
|
|
31
|
-
return
|
|
31
|
+
return {
|
|
32
32
|
success: false,
|
|
33
33
|
error: `Timeout too small for spawn operation: ${timeoutMs}ms`,
|
|
34
34
|
message: 'Timeout too small for spawn operation'
|
|
35
|
-
}
|
|
35
|
+
};
|
|
36
36
|
}
|
|
37
37
|
const originalClass = args.classPath || args.class || args.type || args.actorClass;
|
|
38
38
|
const componentToAdd = (originalClass === 'SplineActor' || originalClass === 'Spline')
|
|
@@ -217,11 +217,17 @@ const handlers = {
|
|
|
217
217
|
}
|
|
218
218
|
};
|
|
219
219
|
export async function handleActorTools(action, args, tools) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
220
|
+
try {
|
|
221
|
+
const handler = handlers[action];
|
|
222
|
+
if (handler) {
|
|
223
|
+
const res = await handler(args, tools);
|
|
224
|
+
return ResponseFactory.success(res);
|
|
225
|
+
}
|
|
226
|
+
const res = await executeAutomationRequest(tools, 'control_actor', args);
|
|
227
|
+
return ResponseFactory.success(res);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return ResponseFactory.error(error);
|
|
224
231
|
}
|
|
225
|
-
return executeAutomationRequest(tools, 'control_actor', args);
|
|
226
232
|
}
|
|
227
233
|
//# sourceMappingURL=actor-handlers.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ITools } from '../../types/tool-interfaces.js';
|
|
2
|
-
export declare function handleSequenceTools(action: string, args:
|
|
2
|
+
export declare function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools): Promise<any>;
|
|
3
3
|
//# sourceMappingURL=sequence-handlers.d.ts.map
|