1- import WebSocket , { WebSocketServer } from 'ws' ;
1+ import { WebSocketServer , WebSocket } from 'ws' ;
22export class WebSocketHandler {
3- server ;
4- clients ;
3+ wsServer ;
54 port ;
65 unityConnection = null ;
76 editorState = {
@@ -17,95 +16,57 @@ export class WebSocketHandler {
1716 lastHeartbeat = 0 ;
1817 connectionEstablished = false ;
1918 pendingRequests = { } ;
20- constructor ( port = parseInt ( process . env . MCP_WEBSOCKET_PORT || '8080' ) ) {
19+ constructor ( port = 5010 ) {
2120 this . port = port ;
22- this . clients = new Set ( ) ;
23- // Create WebSocket server
24- this . server = new WebSocketServer ( { port } ) ;
25- // Setup event handlers
26- this . server . on ( 'connection' , this . handleConnection . bind ( this ) ) ;
27- this . server . on ( 'error' , this . handleError . bind ( this ) ) ;
28- console . error ( `[WebSocket] Server initialized on port ${ port } ` ) ;
21+ // Initialize WebSocket Server
22+ this . wsServer = new WebSocketServer ( { port } ) ;
23+ this . setupWebSocketServer ( ) ;
2924 }
30- handleConnection ( ws ) {
31- console . error ( '[WebSocket] Client connected' ) ;
32- this . clients . add ( ws ) ;
33- ws . on ( 'message' , ( message ) => {
34- try {
35- const messageStr = message . toString ( ) ;
36- console . error ( `[WebSocket] Received message: ${ messageStr } ` ) ;
37- // Parse message
38- const data = JSON . parse ( messageStr ) ;
39- // Handle different message types here
40- this . handleMessage ( data , ws ) ;
41- }
42- catch ( err ) {
43- console . error ( '[WebSocket] Error processing message:' , err ) ;
44- }
45- } ) ;
46- ws . on ( 'close' , ( ) => {
47- console . error ( '[WebSocket] Client disconnected' ) ;
48- this . clients . delete ( ws ) ;
25+ setupWebSocketServer ( ) {
26+ console . error ( `[Unity MCP] WebSocket server starting on port ${ this . port } ` ) ;
27+ this . wsServer . on ( 'listening' , ( ) => {
28+ console . error ( '[Unity MCP] WebSocket server is listening for connections' ) ;
4929 } ) ;
50- ws . on ( 'error' , ( error ) => {
51- console . error ( '[WebSocket] Client error:' , error ) ;
52- this . clients . delete ( ws ) ;
30+ this . wsServer . on ( 'error' , ( error ) => {
31+ console . error ( '[Unity MCP] WebSocket server error:' , error ) ;
5332 } ) ;
54- // Send welcome message
55- ws . send ( JSON . stringify ( {
56- type : 'connected' ,
57- message : 'Connected to Unity MCP WebSocket server'
58- } ) ) ;
59- }
60- handleMessage ( data , client ) {
61- // Handle Unity-specific messages here
62- console . error ( `[WebSocket] Handling message of type: ${ data . type } ` ) ;
63- // Add specific message handling as needed
64- }
65- handleError ( error ) {
66- console . error ( '[WebSocket] Server error:' , error ) ;
67- }
68- async sendMessage ( message ) {
69- const messageStr = typeof message === 'string' ? message : JSON . stringify ( message ) ;
70- const promises = Array . from ( this . clients ) . map ( ( client ) => {
71- return new Promise ( ( resolve , reject ) => {
72- if ( client . readyState === WebSocket . OPEN ) {
73- client . send ( messageStr , ( err ) => {
74- if ( err ) {
75- reject ( err ) ;
76- }
77- else {
78- resolve ( ) ;
79- }
80- } ) ;
33+ this . wsServer . on ( 'connection' , ( ws ) => {
34+ console . error ( '[Unity MCP] Unity Editor connected' ) ;
35+ this . unityConnection = ws ;
36+ this . connectionEstablished = true ;
37+ this . lastHeartbeat = Date . now ( ) ;
38+ // Send a simple handshake message to verify connection
39+ this . sendHandshake ( ) ;
40+ ws . on ( 'message' , ( data ) => {
41+ try {
42+ // Update heartbeat on any message
43+ this . lastHeartbeat = Date . now ( ) ;
44+ const message = JSON . parse ( data . toString ( ) ) ;
45+ console . error ( '[Unity MCP] Received message type:' , message . type ) ;
46+ this . handleUnityMessage ( message ) ;
8147 }
82- else {
83- resolve ( ) ; // Client not ready, skip it
48+ catch ( error ) {
49+ console . error ( '[Unity MCP] Error handling message:' , error ) ;
8450 }
8551 } ) ;
86- } ) ;
87- await Promise . all ( promises ) ;
88- }
89- async close ( ) {
90- // Close all client connections
91- const closePromises = Array . from ( this . clients ) . map ( ( client ) => {
92- return new Promise ( ( resolve ) => {
93- client . terminate ( ) ;
94- resolve ( ) ;
52+ ws . on ( 'error' , ( error ) => {
53+ console . error ( '[Unity MCP] WebSocket error:' , error ) ;
54+ this . connectionEstablished = false ;
9555 } ) ;
96- } ) ;
97- await Promise . all ( closePromises ) ;
98- this . clients . clear ( ) ;
99- // Close the server
100- return new Promise ( ( resolve , reject ) => {
101- this . server . close ( ( err ) => {
102- if ( err ) {
103- reject ( err ) ;
56+ ws . on ( 'close' , ( ) => {
57+ console . error ( '[Unity MCP] Unity Editor disconnected' ) ;
58+ this . unityConnection = null ;
59+ this . connectionEstablished = false ;
60+ } ) ;
61+ // Keep the automatic heartbeat for internal connection validation
62+ const pingInterval = setInterval ( ( ) => {
63+ if ( ws . readyState === WebSocket . OPEN ) {
64+ this . sendPing ( ) ;
10465 }
10566 else {
106- resolve ( ) ;
67+ clearInterval ( pingInterval ) ;
10768 }
108- } ) ;
69+ } , 30000 ) ; // Send heartbeat every 30 seconds
10970 } ) ;
11071 }
11172 sendHandshake ( ) {
@@ -122,7 +83,7 @@ export class WebSocketHandler {
12283 console . error ( '[Unity MCP] Error sending handshake:' , error ) ;
12384 }
12485 }
125- // Rename from sendHeartbeat to sendPing for consistency with protocol
86+ // Renamed from sendHeartbeat to sendPing for consistency with protocol
12687 sendPing ( ) {
12788 try {
12889 if ( this . unityConnection && this . unityConnection . readyState === WebSocket . OPEN ) {
@@ -175,7 +136,8 @@ export class WebSocketHandler {
175136 }
176137 break ;
177138 default :
178- console . error ( '[Unity MCP] Unknown message type:' , message . type ) ;
139+ console . error ( '[Unity MCP] Unknown message type:' ) ;
140+ break ;
179141 }
180142 }
181143 addLogEntry ( logEntry ) {
@@ -193,10 +155,12 @@ export class WebSocketHandler {
193155 // Start timing the command execution
194156 this . commandStartTime = Date . now ( ) ;
195157 // Send the command to Unity
196- this . unityConnection . send ( JSON . stringify ( {
197- type : 'executeEditorCommand' ,
198- data : { code }
199- } ) ) ;
158+ if ( this . unityConnection ) {
159+ this . unityConnection . send ( JSON . stringify ( {
160+ type : 'executeEditorCommand' ,
161+ data : { code }
162+ } ) ) ;
163+ }
200164 // Wait for result with timeout
201165 return await Promise . race ( [
202166 new Promise ( ( resolve , reject ) => {
@@ -271,7 +235,7 @@ export class WebSocketHandler {
271235 return true ;
272236 }
273237 requestEditorState ( ) {
274- if ( ! this . isConnected ( ) ) {
238+ if ( ! this . isConnected ( ) || ! this . unityConnection ) {
275239 return ;
276240 }
277241 try {
@@ -286,7 +250,7 @@ export class WebSocketHandler {
286250 }
287251 }
288252 async requestSceneInfo ( detailLevel ) {
289- if ( ! this . isConnected ( ) ) {
253+ if ( ! this . isConnected ( ) || ! this . unityConnection ) {
290254 throw new Error ( 'Unity Editor is not connected' ) ;
291255 }
292256 const requestId = crypto . randomUUID ( ) ;
@@ -316,7 +280,7 @@ export class WebSocketHandler {
316280 return responsePromise ;
317281 }
318282 async requestGameObjectsInfo ( instanceIDs , detailLevel ) {
319- if ( ! this . isConnected ( ) ) {
283+ if ( ! this . isConnected ( ) || ! this . unityConnection ) {
320284 throw new Error ( 'Unity Editor is not connected' ) ;
321285 }
322286 const requestId = crypto . randomUUID ( ) ;
@@ -346,4 +310,33 @@ export class WebSocketHandler {
346310 } ) ) ;
347311 return responsePromise ;
348312 }
313+ // Support for file system tools by adding a method to send generic messages
314+ async sendMessage ( message ) {
315+ if ( this . unityConnection && this . unityConnection . readyState === WebSocket . OPEN ) {
316+ const messageStr = typeof message === 'string' ? message : JSON . stringify ( message ) ;
317+ return new Promise ( ( resolve , reject ) => {
318+ this . unityConnection . send ( messageStr , ( err ) => {
319+ if ( err ) {
320+ reject ( err ) ;
321+ }
322+ else {
323+ resolve ( ) ;
324+ }
325+ } ) ;
326+ } ) ;
327+ }
328+ return Promise . resolve ( ) ;
329+ }
330+ async close ( ) {
331+ if ( this . unityConnection ) {
332+ this . unityConnection . close ( ) ;
333+ this . unityConnection = null ;
334+ }
335+ return new Promise ( ( resolve ) => {
336+ this . wsServer . close ( ( ) => {
337+ console . error ( '[Unity MCP] WebSocket server closed' ) ;
338+ resolve ( ) ;
339+ } ) ;
340+ } ) ;
341+ }
349342}
0 commit comments