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.
Files changed (75) hide show
  1. package/.github/workflows/publish-mcp.yml +1 -4
  2. package/.github/workflows/release-drafter.yml +2 -1
  3. package/CHANGELOG.md +38 -0
  4. package/dist/automation/bridge.d.ts +1 -2
  5. package/dist/automation/bridge.js +24 -23
  6. package/dist/automation/connection-manager.d.ts +1 -0
  7. package/dist/automation/connection-manager.js +10 -0
  8. package/dist/automation/message-handler.js +5 -4
  9. package/dist/automation/request-tracker.d.ts +4 -0
  10. package/dist/automation/request-tracker.js +11 -3
  11. package/dist/tools/actors.d.ts +19 -1
  12. package/dist/tools/actors.js +15 -5
  13. package/dist/tools/assets.js +1 -1
  14. package/dist/tools/blueprint.d.ts +12 -0
  15. package/dist/tools/blueprint.js +43 -14
  16. package/dist/tools/consolidated-tool-definitions.js +2 -1
  17. package/dist/tools/editor.js +3 -2
  18. package/dist/tools/handlers/actor-handlers.d.ts +1 -1
  19. package/dist/tools/handlers/actor-handlers.js +14 -8
  20. package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
  21. package/dist/tools/handlers/sequence-handlers.js +24 -13
  22. package/dist/tools/introspection.d.ts +1 -1
  23. package/dist/tools/introspection.js +1 -1
  24. package/dist/tools/level.js +3 -3
  25. package/dist/tools/lighting.d.ts +54 -7
  26. package/dist/tools/lighting.js +4 -4
  27. package/dist/tools/materials.d.ts +1 -1
  28. package/dist/types/tool-types.d.ts +2 -0
  29. package/dist/unreal-bridge.js +4 -4
  30. package/dist/utils/command-validator.js +6 -5
  31. package/dist/utils/error-handler.d.ts +24 -2
  32. package/dist/utils/error-handler.js +58 -23
  33. package/dist/utils/normalize.d.ts +7 -4
  34. package/dist/utils/normalize.js +12 -10
  35. package/dist/utils/response-validator.js +88 -73
  36. package/dist/utils/unreal-command-queue.d.ts +2 -0
  37. package/dist/utils/unreal-command-queue.js +8 -1
  38. package/docs/handler-mapping.md +4 -2
  39. package/package.json +1 -1
  40. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +298 -33
  41. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +7 -8
  42. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +229 -319
  43. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +98 -0
  44. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +24 -0
  45. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +96 -0
  46. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +52 -5
  47. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +5 -268
  48. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +57 -2
  49. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +0 -1
  50. package/server.json +3 -3
  51. package/src/automation/bridge.ts +27 -25
  52. package/src/automation/connection-manager.ts +18 -0
  53. package/src/automation/message-handler.ts +33 -8
  54. package/src/automation/request-tracker.ts +39 -7
  55. package/src/server/tool-registry.ts +3 -3
  56. package/src/tools/actors.ts +44 -19
  57. package/src/tools/assets.ts +3 -3
  58. package/src/tools/blueprint.ts +115 -49
  59. package/src/tools/consolidated-tool-definitions.ts +2 -1
  60. package/src/tools/editor.ts +4 -3
  61. package/src/tools/handlers/actor-handlers.ts +14 -9
  62. package/src/tools/handlers/sequence-handlers.ts +86 -63
  63. package/src/tools/introspection.ts +7 -7
  64. package/src/tools/level.ts +6 -6
  65. package/src/tools/lighting.ts +19 -19
  66. package/src/tools/materials.ts +1 -1
  67. package/src/tools/sequence.ts +1 -1
  68. package/src/tools/ui.ts +1 -1
  69. package/src/types/tool-types.ts +4 -0
  70. package/src/unreal-bridge.ts +71 -26
  71. package/src/utils/command-validator.ts +46 -5
  72. package/src/utils/error-handler.ts +128 -45
  73. package/src/utils/normalize.ts +38 -16
  74. package/src/utils/response-validator.ts +103 -87
  75. 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.1.0/mcp-publisher_1.1.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
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.1.0 # v6.1.0
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 underlying = socket._socket || socket.socket;
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: 100,
286
- heartbeatIntervalMs: 30000
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
- if (this.connectionPromise)
312
- this.connectionPromise = undefined;
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
- await Promise.race([
328
- this.connectionPromise,
329
- new Promise((_, reject) => setTimeout(() => reject(new Error('Lazy connection timeout')), connectTimeout))
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.maxPendingRequests) {
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.maxPendingRequests) {
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 : (baseSuccess !== undefined ? baseSuccess : undefined),
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 candidate = (typeof r.action === 'string' && r.action) || (typeof r.result?.action === 'string' && r.result.action);
117
- return candidate;
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
  });
@@ -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<StandardActionResponse<any>>;
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;
@@ -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 errorMsg = typeof error === 'string' ? error : error?.message || response?.message || 'Failed to spawn actor';
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
- return this.sendRequest('get_transform', { actorName }, 'control_actor')
230
- .then(response => {
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() : '';
@@ -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;
@@ -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
- return { success: false, error: String(err), message: String(err) };
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 txt = String((res.error ?? res.message ?? '')).toLowerCase();
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.error === 'SCS_UNAVAILABLE') {
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.message) {
445
- this.log.warn?.(`addSCSComponent reported warning: ${pluginResp.message}`);
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.message) {
472
- this.log.warn?.(`removeSCSComponent reported warning: ${pluginResp.message}`);
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.message) {
503
- this.log.warn?.(`reparentSCSComponent reported warning: ${pluginResp.message}`);
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.message) {
540
- this.log.warn?.(`setSCSComponentTransform reported warning: ${pluginResp.message}`);
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.message) {
577
- this.log.warn?.(`setSCSComponentProperty reported warning: ${pluginResp.message}`);
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
  },
@@ -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
- if (err.message && /time.*out/i.test(err.message)) {
38
- return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
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 cleanObject({
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
- const handler = handlers[action];
221
- if (handler) {
222
- const res = await handler(args, tools);
223
- return cleanObject(res);
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: any, tools: ITools): Promise<any>;
2
+ export declare function handleSequenceTools(action: string, args: Record<string, unknown>, tools: ITools): Promise<any>;
3
3
  //# sourceMappingURL=sequence-handlers.d.ts.map