@akiojin/unity-mcp-server 2.14.14

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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/bin/unity-mcp-server +2 -0
  4. package/package.json +73 -0
  5. package/src/core/codeIndex.js +163 -0
  6. package/src/core/codeIndexDb.js +96 -0
  7. package/src/core/config.js +165 -0
  8. package/src/core/indexWatcher.js +52 -0
  9. package/src/core/projectInfo.js +111 -0
  10. package/src/core/server.js +294 -0
  11. package/src/core/unityConnection.js +426 -0
  12. package/src/handlers/analysis/AnalyzeSceneContentsToolHandler.js +35 -0
  13. package/src/handlers/analysis/FindByComponentToolHandler.js +20 -0
  14. package/src/handlers/analysis/GetAnimatorStateToolHandler.js +37 -0
  15. package/src/handlers/analysis/GetComponentValuesToolHandler.js +20 -0
  16. package/src/handlers/analysis/GetGameObjectDetailsToolHandler.js +35 -0
  17. package/src/handlers/analysis/GetInputActionsStateToolHandler.js +37 -0
  18. package/src/handlers/analysis/GetObjectReferencesToolHandler.js +20 -0
  19. package/src/handlers/asset/AssetDatabaseToolHandler.js +221 -0
  20. package/src/handlers/asset/AssetDependencyToolHandler.js +201 -0
  21. package/src/handlers/asset/AssetImportSettingsToolHandler.js +170 -0
  22. package/src/handlers/asset/CreateMaterialToolHandler.js +96 -0
  23. package/src/handlers/asset/CreatePrefabToolHandler.js +78 -0
  24. package/src/handlers/asset/ExitPrefabModeToolHandler.js +83 -0
  25. package/src/handlers/asset/InstantiatePrefabToolHandler.js +133 -0
  26. package/src/handlers/asset/ModifyMaterialToolHandler.js +76 -0
  27. package/src/handlers/asset/ModifyPrefabToolHandler.js +72 -0
  28. package/src/handlers/asset/OpenPrefabToolHandler.js +121 -0
  29. package/src/handlers/asset/SavePrefabToolHandler.js +106 -0
  30. package/src/handlers/base/BaseToolHandler.js +133 -0
  31. package/src/handlers/compilation/GetCompilationStateToolHandler.js +90 -0
  32. package/src/handlers/component/AddComponentToolHandler.js +126 -0
  33. package/src/handlers/component/GetComponentTypesToolHandler.js +100 -0
  34. package/src/handlers/component/ListComponentsToolHandler.js +85 -0
  35. package/src/handlers/component/ModifyComponentToolHandler.js +143 -0
  36. package/src/handlers/component/RemoveComponentToolHandler.js +108 -0
  37. package/src/handlers/console/ClearConsoleToolHandler.js +160 -0
  38. package/src/handlers/console/ReadConsoleToolHandler.js +276 -0
  39. package/src/handlers/editor/LayerManagementToolHandler.js +160 -0
  40. package/src/handlers/editor/SelectionToolHandler.js +141 -0
  41. package/src/handlers/editor/TagManagementToolHandler.js +129 -0
  42. package/src/handlers/editor/ToolManagementToolHandler.js +135 -0
  43. package/src/handlers/editor/WindowManagementToolHandler.js +125 -0
  44. package/src/handlers/gameobject/CreateGameObjectToolHandler.js +131 -0
  45. package/src/handlers/gameobject/DeleteGameObjectToolHandler.js +101 -0
  46. package/src/handlers/gameobject/FindGameObjectToolHandler.js +119 -0
  47. package/src/handlers/gameobject/GetHierarchyToolHandler.js +132 -0
  48. package/src/handlers/gameobject/ModifyGameObjectToolHandler.js +128 -0
  49. package/src/handlers/index.js +389 -0
  50. package/src/handlers/input/AddInputActionToolHandler.js +20 -0
  51. package/src/handlers/input/AddInputBindingToolHandler.js +20 -0
  52. package/src/handlers/input/CreateActionMapToolHandler.js +20 -0
  53. package/src/handlers/input/CreateCompositeBindingToolHandler.js +20 -0
  54. package/src/handlers/input/GamepadSimulationHandler.js +116 -0
  55. package/src/handlers/input/InputSystemHandler.js +80 -0
  56. package/src/handlers/input/KeyboardSimulationHandler.js +79 -0
  57. package/src/handlers/input/ManageControlSchemesToolHandler.js +20 -0
  58. package/src/handlers/input/MouseSimulationHandler.js +107 -0
  59. package/src/handlers/input/RemoveActionMapToolHandler.js +20 -0
  60. package/src/handlers/input/RemoveAllBindingsToolHandler.js +20 -0
  61. package/src/handlers/input/RemoveInputActionToolHandler.js +20 -0
  62. package/src/handlers/input/RemoveInputBindingToolHandler.js +20 -0
  63. package/src/handlers/input/TouchSimulationHandler.js +142 -0
  64. package/src/handlers/menu/ExecuteMenuItemToolHandler.js +304 -0
  65. package/src/handlers/package/PackageManagerToolHandler.js +248 -0
  66. package/src/handlers/package/RegistryConfigToolHandler.js +198 -0
  67. package/src/handlers/playmode/GetEditorStateToolHandler.js +81 -0
  68. package/src/handlers/playmode/PauseToolHandler.js +44 -0
  69. package/src/handlers/playmode/PlayToolHandler.js +91 -0
  70. package/src/handlers/playmode/StopToolHandler.js +77 -0
  71. package/src/handlers/playmode/WaitForEditorStateToolHandler.js +45 -0
  72. package/src/handlers/scene/CreateSceneToolHandler.js +91 -0
  73. package/src/handlers/scene/GetSceneInfoToolHandler.js +20 -0
  74. package/src/handlers/scene/ListScenesToolHandler.js +58 -0
  75. package/src/handlers/scene/LoadSceneToolHandler.js +92 -0
  76. package/src/handlers/scene/SaveSceneToolHandler.js +76 -0
  77. package/src/handlers/screenshot/AnalyzeScreenshotToolHandler.js +238 -0
  78. package/src/handlers/screenshot/CaptureScreenshotToolHandler.js +692 -0
  79. package/src/handlers/script/BuildCodeIndexToolHandler.js +163 -0
  80. package/src/handlers/script/ScriptCreateClassFileToolHandler.js +60 -0
  81. package/src/handlers/script/ScriptEditStructuredToolHandler.js +173 -0
  82. package/src/handlers/script/ScriptIndexStatusToolHandler.js +61 -0
  83. package/src/handlers/script/ScriptPackagesListToolHandler.js +103 -0
  84. package/src/handlers/script/ScriptReadToolHandler.js +106 -0
  85. package/src/handlers/script/ScriptRefactorRenameToolHandler.js +83 -0
  86. package/src/handlers/script/ScriptRefsFindToolHandler.js +144 -0
  87. package/src/handlers/script/ScriptRemoveSymbolToolHandler.js +79 -0
  88. package/src/handlers/script/ScriptSearchToolHandler.js +320 -0
  89. package/src/handlers/script/ScriptSymbolFindToolHandler.js +117 -0
  90. package/src/handlers/script/ScriptSymbolsGetToolHandler.js +96 -0
  91. package/src/handlers/settings/GetProjectSettingsToolHandler.js +161 -0
  92. package/src/handlers/settings/UpdateProjectSettingsToolHandler.js +272 -0
  93. package/src/handlers/system/GetCommandStatsToolHandler.js +25 -0
  94. package/src/handlers/system/PingToolHandler.js +53 -0
  95. package/src/handlers/system/RefreshAssetsToolHandler.js +45 -0
  96. package/src/handlers/ui/ClickUIElementToolHandler.js +110 -0
  97. package/src/handlers/ui/FindUIElementsToolHandler.js +63 -0
  98. package/src/handlers/ui/GetUIElementStateToolHandler.js +50 -0
  99. package/src/handlers/ui/SetUIElementValueToolHandler.js +49 -0
  100. package/src/handlers/ui/SimulateUIInputToolHandler.js +156 -0
  101. package/src/handlers/video/CaptureVideoForToolHandler.js +96 -0
  102. package/src/handlers/video/CaptureVideoStartToolHandler.js +38 -0
  103. package/src/handlers/video/CaptureVideoStatusToolHandler.js +30 -0
  104. package/src/handlers/video/CaptureVideoStopToolHandler.js +32 -0
  105. package/src/lsp/CSharpLspUtils.js +134 -0
  106. package/src/lsp/LspProcessManager.js +60 -0
  107. package/src/lsp/LspRpcClient.js +133 -0
  108. package/src/tools/analysis/analyzeSceneContents.js +100 -0
  109. package/src/tools/analysis/findByComponent.js +87 -0
  110. package/src/tools/analysis/getAnimatorState.js +326 -0
  111. package/src/tools/analysis/getComponentValues.js +182 -0
  112. package/src/tools/analysis/getGameObjectDetails.js +159 -0
  113. package/src/tools/analysis/getInputActionsState.js +329 -0
  114. package/src/tools/analysis/getObjectReferences.js +86 -0
  115. package/src/tools/input/inputActionsEditor.js +556 -0
  116. package/src/tools/scene/createScene.js +112 -0
  117. package/src/tools/scene/getSceneInfo.js +95 -0
  118. package/src/tools/scene/listScenes.js +82 -0
  119. package/src/tools/scene/loadScene.js +122 -0
  120. package/src/tools/scene/saveScene.js +91 -0
  121. package/src/tools/system/ping.js +72 -0
  122. package/src/tools/video/recordFor.js +31 -0
  123. package/src/tools/video/recordPlayMode.js +61 -0
  124. package/src/utils/csharpParse.js +88 -0
  125. package/src/utils/validators.js +90 -0
@@ -0,0 +1,426 @@
1
+ import net from 'net';
2
+ import { EventEmitter } from 'events';
3
+ import { config, logger } from './config.js';
4
+
5
+ /**
6
+ * Manages TCP connection to Unity Editor
7
+ */
8
+ export class UnityConnection extends EventEmitter {
9
+ constructor() {
10
+ super();
11
+ this.socket = null;
12
+ this.connected = false;
13
+ this.reconnectAttempts = 0;
14
+ this.reconnectTimer = null;
15
+ this.commandId = 0;
16
+ this.pendingCommands = new Map();
17
+ this.isDisconnecting = false;
18
+ this.messageBuffer = Buffer.alloc(0);
19
+ // Simple concurrency limiter and send queue to avoid flooding Unity
20
+ this.sendQueue = [];
21
+ this.inFlight = 0;
22
+ this.maxInFlight = 1; // process one command at a time by default
23
+ }
24
+
25
+ /**
26
+ * Connects to Unity Editor
27
+ * @returns {Promise<void>}
28
+ */
29
+ async connect() {
30
+ return new Promise((resolve, reject) => {
31
+ if (this.connected) {
32
+ resolve();
33
+ return;
34
+ }
35
+
36
+ // Skip connection in CI/test environments
37
+ if (process.env.NODE_ENV === 'test' || process.env.CI === 'true') {
38
+ logger.info('Skipping Unity connection in test/CI environment');
39
+ reject(new Error('Unity connection disabled in test environment'));
40
+ return;
41
+ }
42
+
43
+ logger.info(`Connecting to Unity at ${config.unity.host}:${config.unity.port}...`);
44
+
45
+ this.socket = new net.Socket();
46
+ let connectionTimeout = null;
47
+ let resolved = false;
48
+
49
+ // Helper to clean up the connection timeout
50
+ const clearConnectionTimeout = () => {
51
+ if (connectionTimeout) {
52
+ clearTimeout(connectionTimeout);
53
+ connectionTimeout = null;
54
+ }
55
+ };
56
+
57
+ // Set up event handlers
58
+ this.socket.on('connect', () => {
59
+ logger.info('Connected to Unity Editor');
60
+ this.connected = true;
61
+ this.reconnectAttempts = 0;
62
+ resolved = true;
63
+ clearConnectionTimeout();
64
+ this.emit('connected');
65
+ resolve();
66
+ });
67
+
68
+ this.socket.on('data', (data) => {
69
+ this.handleData(data);
70
+ });
71
+
72
+ this.socket.on('error', (error) => {
73
+ logger.error('Socket error:', error.message);
74
+ this.emit('error', error);
75
+
76
+ if (!this.connected && !resolved) {
77
+ resolved = true;
78
+ clearConnectionTimeout();
79
+ // Mark as disconnecting to prevent reconnection
80
+ this.isDisconnecting = true;
81
+ // Destroy the socket to clean up properly
82
+ this.socket.destroy();
83
+ this.isDisconnecting = false;
84
+ reject(error);
85
+ }
86
+ });
87
+
88
+ this.socket.on('close', () => {
89
+ // Clear the connection timeout when socket closes
90
+ clearConnectionTimeout();
91
+
92
+ // Check if we're already handling disconnection
93
+ if (this.isDisconnecting || !this.socket) {
94
+ return;
95
+ }
96
+
97
+ logger.info('Disconnected from Unity Editor');
98
+ this.connected = false;
99
+ const wasSocket = this.socket;
100
+ this.socket = null;
101
+
102
+ // Clear message buffer
103
+ this.messageBuffer = Buffer.alloc(0);
104
+
105
+ // Clear pending commands
106
+ for (const [id, pending] of this.pendingCommands) {
107
+ pending.reject(new Error('Connection closed'));
108
+ }
109
+ this.pendingCommands.clear();
110
+
111
+ // Emit disconnected event
112
+ this.emit('disconnected');
113
+
114
+ // Attempt reconnection only if not intentionally disconnecting
115
+ if (!this.isDisconnecting && process.env.DISABLE_AUTO_RECONNECT !== 'true') {
116
+ this.scheduleReconnect();
117
+ }
118
+ });
119
+
120
+ // Attempt connection
121
+ this.socket.connect(config.unity.port, config.unity.host);
122
+
123
+ // Set timeout for initial connection
124
+ connectionTimeout = setTimeout(() => {
125
+ if (!this.connected && !resolved && this.socket) {
126
+ resolved = true;
127
+ // Remove event listeners before destroying to prevent callbacks after timeout
128
+ this.socket.removeAllListeners();
129
+ this.socket.destroy();
130
+ reject(new Error('Connection timeout'));
131
+ }
132
+ }, config.unity.commandTimeout);
133
+ });
134
+ }
135
+
136
+ /**
137
+ * Disconnects from Unity Editor
138
+ */
139
+ disconnect() {
140
+ this.isDisconnecting = true;
141
+
142
+ if (this.reconnectTimer) {
143
+ clearTimeout(this.reconnectTimer);
144
+ this.reconnectTimer = null;
145
+ }
146
+
147
+ if (this.socket) {
148
+ try {
149
+ // Remove all listeners before destroying to prevent async callbacks
150
+ this.socket.removeAllListeners();
151
+ this.socket.destroy();
152
+ } catch (error) {
153
+ // Ignore errors during cleanup
154
+ }
155
+ this.socket = null;
156
+ }
157
+
158
+ this.connected = false;
159
+ this.isDisconnecting = false;
160
+ }
161
+
162
+ /**
163
+ * Schedules a reconnection attempt
164
+ */
165
+ scheduleReconnect() {
166
+ if (this.reconnectTimer) {
167
+ return;
168
+ }
169
+
170
+ const delay = Math.min(
171
+ config.unity.reconnectDelay * Math.pow(config.unity.reconnectBackoffMultiplier, this.reconnectAttempts),
172
+ config.unity.maxReconnectDelay
173
+ );
174
+
175
+ logger.info(`Scheduling reconnection in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);
176
+
177
+ this.reconnectTimer = setTimeout(() => {
178
+ this.reconnectTimer = null;
179
+ this.reconnectAttempts++;
180
+ this.connect().catch((error) => {
181
+ logger.error('Reconnection failed:', error.message);
182
+ });
183
+ }, delay);
184
+ }
185
+
186
+ /**
187
+ * Handles incoming data from Unity
188
+ * @param {Buffer} data
189
+ */
190
+ handleData(data) {
191
+ // Check if this is an unframed Unity debug log
192
+ if (data.length > 0 && !this.messageBuffer.length) {
193
+ const dataStr = data.toString('utf8');
194
+ if (dataStr.startsWith('[Unity Editor MCP]') || dataStr.startsWith('[Unity]')) {
195
+ logger.debug(`[Unity] Received unframed debug log: ${dataStr.trim()}`);
196
+ // Don't process unframed logs as messages
197
+ return;
198
+ }
199
+ }
200
+
201
+ // Append new data to buffer
202
+ this.messageBuffer = Buffer.concat([this.messageBuffer, data]);
203
+
204
+ // Process complete messages
205
+ while (this.messageBuffer.length >= 4) {
206
+ // Read message length (first 4 bytes, big-endian)
207
+ const messageLength = this.messageBuffer.readInt32BE(0);
208
+
209
+ // Validate message length
210
+ if (messageLength < 0 || messageLength > 1024 * 1024) { // Max 1MB messages
211
+ logger.error(`[Unity] Invalid message length: ${messageLength}`);
212
+
213
+ // Try to recover by looking for valid framed message
214
+ // Look for a reasonable length value (positive, less than 10KB for typical responses)
215
+ let recoveryIndex = -1;
216
+ for (let i = 4; i < Math.min(this.messageBuffer.length - 4, 100); i++) {
217
+ const testLength = this.messageBuffer.readInt32BE(i);
218
+ if (testLength > 0 && testLength < 10240) {
219
+ // Check if this could be a valid JSON message
220
+ if (i + 4 + testLength <= this.messageBuffer.length) {
221
+ const testData = this.messageBuffer.slice(i + 4, i + 4 + testLength).toString('utf8');
222
+ if (testData.trim().startsWith('{')) {
223
+ recoveryIndex = i;
224
+ break;
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ if (recoveryIndex > 0) {
231
+ logger.warn(`[Unity] Discarding ${recoveryIndex} bytes of invalid data`);
232
+ this.messageBuffer = this.messageBuffer.slice(recoveryIndex);
233
+ continue;
234
+ } else {
235
+ // Can't recover, clear buffer
236
+ logger.error('[Unity] Unable to recover from invalid frame, clearing buffer');
237
+ this.messageBuffer = Buffer.alloc(0);
238
+ break;
239
+ }
240
+ }
241
+
242
+ // Check if we have the complete message
243
+ if (this.messageBuffer.length >= 4 + messageLength) {
244
+ // Extract message
245
+ const messageData = this.messageBuffer.slice(4, 4 + messageLength);
246
+ this.messageBuffer = this.messageBuffer.slice(4 + messageLength);
247
+
248
+ // Process the message
249
+ try {
250
+ const message = messageData.toString('utf8');
251
+
252
+ // Skip non-JSON messages (like debug logs)
253
+ if (!message.trim().startsWith('{')) {
254
+ logger.warn(`[Unity] Skipping non-JSON message: ${message.substring(0, 50)}...`);
255
+ continue;
256
+ }
257
+
258
+ logger.debug(`[Unity] Received framed message (length=${message.length})`);
259
+
260
+ const response = JSON.parse(message);
261
+ logger.debug(`[Unity] Parsed response id=${response.id || 'n/a'} status=${response.status || (response.success === false ? 'error' : 'success')}`);
262
+
263
+ // Check if this is a response to a pending command
264
+ if (response.id && this.pendingCommands.has(response.id)) {
265
+ logger.info(`[Unity] Found pending command for ID ${response.id}`);
266
+ const pending = this.pendingCommands.get(response.id);
267
+ this.pendingCommands.delete(response.id);
268
+
269
+ // Handle both old and new response formats
270
+ if (response.status === 'success' || response.success === true) {
271
+ logger.info(`[Unity] Command ${response.id} succeeded`);
272
+
273
+ let result = response.result || response.data || {};
274
+
275
+ // If result is a string, try to parse it as JSON
276
+ if (typeof result === 'string') {
277
+ try {
278
+ result = JSON.parse(result);
279
+ logger.info(`[Unity] Parsed string result as JSON:`, result);
280
+ } catch (parseError) {
281
+ logger.warn(`[Unity] Failed to parse result as JSON: ${parseError.message}`);
282
+ // Keep the original string value
283
+ }
284
+ }
285
+
286
+ // Include version and editorState information if available
287
+ if (response.version) {
288
+ result._version = response.version;
289
+ }
290
+ if (response.editorState) {
291
+ result._editorState = response.editorState;
292
+ }
293
+
294
+ logger.info(`[Unity] Command ${response.id} resolved successfully`);
295
+ pending.resolve(result);
296
+ } else if (response.status === 'error' || response.success === false) {
297
+ logger.error(`[Unity] Command ${response.id} failed:`, response.error);
298
+ pending.reject(new Error(response.error || 'Command failed'));
299
+ } else {
300
+ // Unknown format
301
+ logger.warn(`[Unity] Command ${response.id} has unknown response format`);
302
+ pending.resolve(response);
303
+ }
304
+ } else {
305
+ // Handle unsolicited messages
306
+ logger.debug(`[Unity] Received unsolicited message id=${response.id || 'n/a'}`);
307
+ this.emit('message', response);
308
+ }
309
+ } catch (error) {
310
+ logger.error('[Unity] Failed to parse response:', error.message);
311
+ logger.debug(`[Unity] Raw message: ${messageData.toString().substring(0, 200)}...`);
312
+
313
+ // Check if this looks like a Unity log message
314
+ const messageStr = messageData.toString();
315
+ if (messageStr.includes('[Unity Editor MCP]')) {
316
+ logger.debug('[Unity] Received Unity log message instead of JSON response');
317
+ // Don't treat this as a critical error
318
+ }
319
+ }
320
+ } else {
321
+ // Not enough data yet, wait for more
322
+ break;
323
+ }
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Sends a command to Unity
329
+ * @param {string} type - Command type
330
+ * @param {object} params - Command parameters
331
+ * @returns {Promise<any>} - Response from Unity
332
+ */
333
+ async sendCommand(type, params = {}) {
334
+ logger.info(`[Unity] enqueue sendCommand: ${type}`, { connected: this.connected });
335
+
336
+ if (!this.connected) {
337
+ logger.error('[Unity] Cannot send command - not connected');
338
+ throw new Error('Not connected to Unity');
339
+ }
340
+
341
+ // Create an external promise that will resolve when Unity responds
342
+ return new Promise((outerResolve, outerReject) => {
343
+ const task = { type, params, outerResolve, outerReject };
344
+ this.sendQueue.push(task);
345
+ this._pumpQueue();
346
+ });
347
+ }
348
+
349
+ _pumpQueue() {
350
+ if (!this.connected) return;
351
+ if (this.inFlight >= this.maxInFlight) return;
352
+ const task = this.sendQueue.shift();
353
+ if (!task) return;
354
+
355
+ const id = String(++this.commandId);
356
+ const command = { id, type: task.type, params: task.params };
357
+ const json = JSON.stringify(command);
358
+ const messageBuffer = Buffer.from(json, 'utf8');
359
+ const lengthBuffer = Buffer.allocUnsafe(4);
360
+ lengthBuffer.writeInt32BE(messageBuffer.length, 0);
361
+ const framedMessage = Buffer.concat([lengthBuffer, messageBuffer]);
362
+
363
+ this.inFlight++;
364
+ logger.info(`[Unity] Dispatching command ${id}: ${task.type}`);
365
+
366
+ // Set up timeout only when actually dispatched
367
+ const timeout = setTimeout(() => {
368
+ logger.error(`[Unity] Command ${id} timed out after ${config.unity.commandTimeout}ms`);
369
+ this.pendingCommands.delete(id);
370
+ this.inFlight = Math.max(0, this.inFlight - 1);
371
+ task.outerReject(new Error('Command timeout'));
372
+ this._pumpQueue();
373
+ }, config.unity.commandTimeout);
374
+
375
+ // Store pending with wrappers to manage queue progression
376
+ this.pendingCommands.set(id, {
377
+ resolve: (data) => {
378
+ logger.info(`[Unity] Command ${id} resolved`);
379
+ clearTimeout(timeout);
380
+ try { task.outerResolve(data); } finally {
381
+ this.inFlight = Math.max(0, this.inFlight - 1);
382
+ this._pumpQueue();
383
+ }
384
+ },
385
+ reject: (error) => {
386
+ logger.error(`[Unity] Command ${id} rejected: ${error.message}`);
387
+ clearTimeout(timeout);
388
+ try { task.outerReject(error); } finally {
389
+ this.inFlight = Math.max(0, this.inFlight - 1);
390
+ this._pumpQueue();
391
+ }
392
+ }
393
+ });
394
+
395
+ // Send framed message
396
+ this.socket.write(framedMessage, (error) => {
397
+ if (error) {
398
+ logger.error(`[Unity] Failed to write command ${id}: ${error.message}`);
399
+ clearTimeout(timeout);
400
+ this.pendingCommands.delete(id);
401
+ this.inFlight = Math.max(0, this.inFlight - 1);
402
+ task.outerReject(error);
403
+ this._pumpQueue();
404
+ } else {
405
+ logger.debug(`[Unity] Command ${id} written; awaiting response`);
406
+ }
407
+ });
408
+ }
409
+
410
+ /**
411
+ * Sends a ping command to Unity
412
+ * @returns {Promise<any>}
413
+ */
414
+ async ping() {
415
+ // Use normal command sending for ping with proper framing
416
+ return this.sendCommand('ping', {});
417
+ }
418
+
419
+ /**
420
+ * Checks if connected to Unity
421
+ * @returns {boolean}
422
+ */
423
+ isConnected() {
424
+ return this.connected;
425
+ }
426
+ }
@@ -0,0 +1,35 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { analyzeSceneContentsToolDefinition, analyzeSceneContentsHandler } from '../../tools/analysis/analyzeSceneContents.js';
3
+
4
+ /**
5
+ * Handler for analyze_scene_contents tool
6
+ */
7
+ export class AnalyzeSceneContentsToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ analyzeSceneContentsToolDefinition.name,
11
+ analyzeSceneContentsToolDefinition.description,
12
+ analyzeSceneContentsToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ this.handler = analyzeSceneContentsHandler;
16
+ }
17
+
18
+ async execute(args) {
19
+ // Check connection
20
+ if (!this.unityConnection.isConnected()) {
21
+ throw new Error('Unity connection not available');
22
+ }
23
+
24
+ // Use the handler function
25
+ const result = await this.handler(this.unityConnection, args);
26
+
27
+ // If the handler returns an error response, throw it
28
+ if (result.isError) {
29
+ throw new Error(result.content[0].text);
30
+ }
31
+
32
+ // Return the content
33
+ return result;
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { findByComponentToolDefinition, findByComponentHandler } from '../../tools/analysis/findByComponent.js';
3
+
4
+ /**
5
+ * Handler for the find_by_component tool
6
+ */
7
+ export class FindByComponentToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ findByComponentToolDefinition.name,
11
+ findByComponentToolDefinition.description,
12
+ findByComponentToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ }
16
+
17
+ async execute(args) {
18
+ return findByComponentHandler(this.unityConnection, args);
19
+ }
20
+ }
@@ -0,0 +1,37 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import {
3
+ getAnimatorStateToolDefinition,
4
+ getAnimatorRuntimeInfoToolDefinition,
5
+ getAnimatorStateHandler,
6
+ getAnimatorRuntimeInfoHandler
7
+ } from '../../tools/analysis/getAnimatorState.js';
8
+
9
+ export class GetAnimatorStateToolHandler extends BaseToolHandler {
10
+ constructor(unityConnection) {
11
+ super(
12
+ getAnimatorStateToolDefinition.name,
13
+ getAnimatorStateToolDefinition.description,
14
+ getAnimatorStateToolDefinition.inputSchema
15
+ );
16
+ this.unityConnection = unityConnection;
17
+ }
18
+
19
+ async execute(args) {
20
+ return getAnimatorStateHandler(this.unityConnection, args);
21
+ }
22
+ }
23
+
24
+ export class GetAnimatorRuntimeInfoToolHandler extends BaseToolHandler {
25
+ constructor(unityConnection) {
26
+ super(
27
+ getAnimatorRuntimeInfoToolDefinition.name,
28
+ getAnimatorRuntimeInfoToolDefinition.description,
29
+ getAnimatorRuntimeInfoToolDefinition.inputSchema
30
+ );
31
+ this.unityConnection = unityConnection;
32
+ }
33
+
34
+ async execute(args) {
35
+ return getAnimatorRuntimeInfoHandler(this.unityConnection, args);
36
+ }
37
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { getComponentValuesToolDefinition, getComponentValuesHandler } from '../../tools/analysis/getComponentValues.js';
3
+
4
+ /**
5
+ * Handler for the get_component_values tool
6
+ */
7
+ export class GetComponentValuesToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ getComponentValuesToolDefinition.name,
11
+ getComponentValuesToolDefinition.description,
12
+ getComponentValuesToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ }
16
+
17
+ async execute(args) {
18
+ return getComponentValuesHandler(this.unityConnection, args);
19
+ }
20
+ }
@@ -0,0 +1,35 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { getGameObjectDetailsToolDefinition, getGameObjectDetailsHandler } from '../../tools/analysis/getGameObjectDetails.js';
3
+
4
+ /**
5
+ * Handler for get_gameobject_details tool
6
+ */
7
+ export class GetGameObjectDetailsToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ getGameObjectDetailsToolDefinition.name,
11
+ getGameObjectDetailsToolDefinition.description,
12
+ getGameObjectDetailsToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ this.handler = getGameObjectDetailsHandler;
16
+ }
17
+
18
+ async execute(args) {
19
+ // Check connection
20
+ if (!this.unityConnection.isConnected()) {
21
+ throw new Error('Unity connection not available');
22
+ }
23
+
24
+ // Use the handler function
25
+ const result = await this.handler(this.unityConnection, args);
26
+
27
+ // If the handler returns an error response, throw it
28
+ if (result.isError) {
29
+ throw new Error(result.content[0].text);
30
+ }
31
+
32
+ // Return the content
33
+ return result;
34
+ }
35
+ }
@@ -0,0 +1,37 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import {
3
+ getInputActionsStateToolDefinition,
4
+ analyzeInputActionsAssetToolDefinition,
5
+ getInputActionsStateHandler,
6
+ analyzeInputActionsAssetHandler
7
+ } from '../../tools/analysis/getInputActionsState.js';
8
+
9
+ export class GetInputActionsStateToolHandler extends BaseToolHandler {
10
+ constructor(unityConnection) {
11
+ super(
12
+ getInputActionsStateToolDefinition.name,
13
+ getInputActionsStateToolDefinition.description,
14
+ getInputActionsStateToolDefinition.inputSchema
15
+ );
16
+ this.unityConnection = unityConnection;
17
+ }
18
+
19
+ async execute(args) {
20
+ return getInputActionsStateHandler(this.unityConnection, args);
21
+ }
22
+ }
23
+
24
+ export class AnalyzeInputActionsAssetToolHandler extends BaseToolHandler {
25
+ constructor(unityConnection) {
26
+ super(
27
+ analyzeInputActionsAssetToolDefinition.name,
28
+ analyzeInputActionsAssetToolDefinition.description,
29
+ analyzeInputActionsAssetToolDefinition.inputSchema
30
+ );
31
+ this.unityConnection = unityConnection;
32
+ }
33
+
34
+ async execute(args) {
35
+ return analyzeInputActionsAssetHandler(this.unityConnection, args);
36
+ }
37
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseToolHandler } from '../base/BaseToolHandler.js';
2
+ import { getObjectReferencesToolDefinition, getObjectReferencesHandler } from '../../tools/analysis/getObjectReferences.js';
3
+
4
+ /**
5
+ * Handler for the get_object_references tool
6
+ */
7
+ export class GetObjectReferencesToolHandler extends BaseToolHandler {
8
+ constructor(unityConnection) {
9
+ super(
10
+ getObjectReferencesToolDefinition.name,
11
+ getObjectReferencesToolDefinition.description,
12
+ getObjectReferencesToolDefinition.inputSchema
13
+ );
14
+ this.unityConnection = unityConnection;
15
+ }
16
+
17
+ async execute(args) {
18
+ return getObjectReferencesHandler(this.unityConnection, args);
19
+ }
20
+ }