Danisz commited on
Commit
1f0b59d
·
1 Parent(s): 34aec0d

refactor: update core files for async MCP server

Browse files
Files changed (4) hide show
  1. browser/handler.ts +33 -6
  2. package-lock.json +98 -0
  3. package.json +1 -0
  4. server/server.ts +176 -42
browser/handler.ts CHANGED
@@ -53,9 +53,9 @@ export class CodenamesGameHandler implements ICodenamesGameAPI {
53
  await nicknameInput.fill('TestPlayer');
54
  const joinButton = this.page.locator(Selectors.joinRoomButton);
55
  await joinButton.waitFor({ timeout: 10000 });
 
56
  await joinButton.click();
57
  }
58
- await this.page.locator(Selectors.teamBoardBlue).waitFor({ timeout: 15000 });
59
  }
60
 
61
  async leaveRoom(): Promise<void> {
@@ -70,12 +70,39 @@ export class CodenamesGameHandler implements ICodenamesGameAPI {
70
  }
71
 
72
  async joinAsSpymaster(team: Team): Promise<void> {
 
73
  const teamBoardSelector = team === Team.Red ? Selectors.teamBoardRed : Selectors.teamBoardBlue;
74
- const teamBoard = this.page.locator(teamBoardSelector);
75
- await teamBoard.waitFor({ timeout: 5000 });
76
- const joinSpymasterButton = team === Team.Red ? this.page.locator(Selectors.joinRedSpymasterButton) : this.page.locator(Selectors.joinBlueSpymasterButton);
77
- await joinSpymasterButton.waitFor({ timeout: 5000 });
78
- await joinSpymasterButton.click();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  await joinSpymasterButton.waitFor({ state: 'hidden', timeout: 5000 });
80
  }
81
 
 
53
  await nicknameInput.fill('TestPlayer');
54
  const joinButton = this.page.locator(Selectors.joinRoomButton);
55
  await joinButton.waitFor({ timeout: 10000 });
56
+ console.log(`>>> Clicking joinButton`);
57
  await joinButton.click();
58
  }
 
59
  }
60
 
61
  async leaveRoom(): Promise<void> {
 
70
  }
71
 
72
  async joinAsSpymaster(team: Team): Promise<void> {
73
+ console.log(`>>> Entering joinAsSpymaster for team: ${team === Team.Red ? 'Red' : 'Blue'}`);
74
  const teamBoardSelector = team === Team.Red ? Selectors.teamBoardRed : Selectors.teamBoardBlue;
75
+ const joinSpymasterButtonSelector = team === Team.Red ? Selectors.joinRedSpymasterButton : Selectors.joinBlueSpymasterButton;
76
+
77
+ try {
78
+ // Wait for the team board (redundant if button check passed, but safe)
79
+ await this.page.waitForSelector(teamBoardSelector, { state: 'visible', timeout: 5000 });
80
+ } catch (e) {
81
+ // This specific catch might be less likely now, but keep for safety
82
+ await this._handleScreenshot('screenshots/joinSpymaster_teamBoard_timeout_alt.png', `[joinAsSpymaster] Team board (${teamBoardSelector}) did not become visible even after button check`);
83
+ throw e;
84
+ }
85
+
86
+ // First, check if the join button is even visible. If not, assume role is taken/player already joined.
87
+ await new Promise(resolve => setTimeout(resolve, 1000));
88
+ const isJoinButtonVisible = await this.page.locator(joinSpymasterButtonSelector).isVisible({ timeout: 20000 }).catch(() => false);
89
+
90
+ if (!isJoinButtonVisible) {
91
+ // console.log(`Join Spymaster button (${joinSpymasterButtonSelector}) is not visible. Assuming already joined or role unavailable.`);
92
+ await this._handleScreenshot('screenshots/joinSpymaster_no_button.png', `[joinAsSpymaster] Join button (${joinSpymasterButtonSelector}) not visible`);
93
+ return; // Exit early
94
+ }
95
+
96
+ console.log(`Join Spymaster button (${joinSpymasterButtonSelector}) is visible. Attempting to join.`);
97
+ const joinSpymasterButton = this.page.locator(joinSpymasterButtonSelector);
98
+
99
+ try {
100
+ console.log(`>>> Clicking joinSpymasterButton`);
101
+ await joinSpymasterButton.click();
102
+ } catch (e) {
103
+ await this._handleScreenshot('screenshots/joinSpymaster_click_or_hidden_timeout_alt.png', `[joinAsSpymaster] Error clicking button (${joinSpymasterButtonSelector})`);
104
+ throw e;
105
+ }
106
  await joinSpymasterButton.waitFor({ state: 'hidden', timeout: 5000 });
107
  }
108
 
package-lock.json CHANGED
@@ -16,6 +16,7 @@
16
  "zod": "latest"
17
  },
18
  "devDependencies": {
 
19
  "@types/jest": "^29.5.14",
20
  "jest": "^29.7.0",
21
  "playwright": "^1.52.0",
@@ -988,6 +989,52 @@
988
  "@babel/types": "^7.20.7"
989
  }
990
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
991
  "node_modules/@types/graceful-fs": {
992
  "version": "4.1.9",
993
  "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -998,6 +1045,13 @@
998
  "@types/node": "*"
999
  }
1000
  },
 
 
 
 
 
 
 
1001
  "node_modules/@types/istanbul-lib-coverage": {
1002
  "version": "2.0.6",
1003
  "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -1036,6 +1090,13 @@
1036
  "pretty-format": "^29.0.0"
1037
  }
1038
  },
 
 
 
 
 
 
 
1039
  "node_modules/@types/node": {
1040
  "version": "22.14.1",
1041
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
@@ -1046,6 +1107,43 @@
1046
  "undici-types": "~6.21.0"
1047
  }
1048
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1049
  "node_modules/@types/stack-utils": {
1050
  "version": "2.0.3",
1051
  "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
 
16
  "zod": "latest"
17
  },
18
  "devDependencies": {
19
+ "@types/express": "^5.0.1",
20
  "@types/jest": "^29.5.14",
21
  "jest": "^29.7.0",
22
  "playwright": "^1.52.0",
 
989
  "@babel/types": "^7.20.7"
990
  }
991
  },
992
+ "node_modules/@types/body-parser": {
993
+ "version": "1.19.5",
994
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
995
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
996
+ "dev": true,
997
+ "license": "MIT",
998
+ "dependencies": {
999
+ "@types/connect": "*",
1000
+ "@types/node": "*"
1001
+ }
1002
+ },
1003
+ "node_modules/@types/connect": {
1004
+ "version": "3.4.38",
1005
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
1006
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
1007
+ "dev": true,
1008
+ "license": "MIT",
1009
+ "dependencies": {
1010
+ "@types/node": "*"
1011
+ }
1012
+ },
1013
+ "node_modules/@types/express": {
1014
+ "version": "5.0.1",
1015
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz",
1016
+ "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==",
1017
+ "dev": true,
1018
+ "license": "MIT",
1019
+ "dependencies": {
1020
+ "@types/body-parser": "*",
1021
+ "@types/express-serve-static-core": "^5.0.0",
1022
+ "@types/serve-static": "*"
1023
+ }
1024
+ },
1025
+ "node_modules/@types/express-serve-static-core": {
1026
+ "version": "5.0.6",
1027
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
1028
+ "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
1029
+ "dev": true,
1030
+ "license": "MIT",
1031
+ "dependencies": {
1032
+ "@types/node": "*",
1033
+ "@types/qs": "*",
1034
+ "@types/range-parser": "*",
1035
+ "@types/send": "*"
1036
+ }
1037
+ },
1038
  "node_modules/@types/graceful-fs": {
1039
  "version": "4.1.9",
1040
  "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
 
1045
  "@types/node": "*"
1046
  }
1047
  },
1048
+ "node_modules/@types/http-errors": {
1049
+ "version": "2.0.4",
1050
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
1051
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
1052
+ "dev": true,
1053
+ "license": "MIT"
1054
+ },
1055
  "node_modules/@types/istanbul-lib-coverage": {
1056
  "version": "2.0.6",
1057
  "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
 
1090
  "pretty-format": "^29.0.0"
1091
  }
1092
  },
1093
+ "node_modules/@types/mime": {
1094
+ "version": "1.3.5",
1095
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
1096
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
1097
+ "dev": true,
1098
+ "license": "MIT"
1099
+ },
1100
  "node_modules/@types/node": {
1101
  "version": "22.14.1",
1102
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
 
1107
  "undici-types": "~6.21.0"
1108
  }
1109
  },
1110
+ "node_modules/@types/qs": {
1111
+ "version": "6.9.18",
1112
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
1113
+ "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
1114
+ "dev": true,
1115
+ "license": "MIT"
1116
+ },
1117
+ "node_modules/@types/range-parser": {
1118
+ "version": "1.2.7",
1119
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
1120
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
1121
+ "dev": true,
1122
+ "license": "MIT"
1123
+ },
1124
+ "node_modules/@types/send": {
1125
+ "version": "0.17.4",
1126
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
1127
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
1128
+ "dev": true,
1129
+ "license": "MIT",
1130
+ "dependencies": {
1131
+ "@types/mime": "^1",
1132
+ "@types/node": "*"
1133
+ }
1134
+ },
1135
+ "node_modules/@types/serve-static": {
1136
+ "version": "1.15.7",
1137
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
1138
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
1139
+ "dev": true,
1140
+ "license": "MIT",
1141
+ "dependencies": {
1142
+ "@types/http-errors": "*",
1143
+ "@types/node": "*",
1144
+ "@types/send": "*"
1145
+ }
1146
+ },
1147
  "node_modules/@types/stack-utils": {
1148
  "version": "2.0.3",
1149
  "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
package.json CHANGED
@@ -14,6 +14,7 @@
14
  "zod": "latest"
15
  },
16
  "devDependencies": {
 
17
  "@types/jest": "^29.5.14",
18
  "jest": "^29.7.0",
19
  "playwright": "^1.52.0",
 
14
  "zod": "latest"
15
  },
16
  "devDependencies": {
17
+ "@types/express": "^5.0.1",
18
  "@types/jest": "^29.5.14",
19
  "jest": "^29.7.0",
20
  "playwright": "^1.52.0",
server/server.ts CHANGED
@@ -1,51 +1,185 @@
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 
3
  import { z } from "zod";
4
  import { BoardState, Team } from "../shared/types";
5
  import { CodenamesGameHandler } from "../browser/handler";
 
 
 
 
6
 
7
- async function main() {
8
- // Initialize browser handler
9
- const browserHandler = await CodenamesGameHandler.create();
10
- await browserHandler.joinRoom();
11
- await browserHandler.joinAsSpymaster(Team.Red);
12
-
13
- const server = new McpServer(
14
- { name: "codenames-mcp-server", version: "1.0.0" },
15
- { capabilities: {} }
16
- );
17
-
18
- server.tool(
19
- "getBoardState",
20
- {},
21
- async () => {
22
- const board: BoardState = await browserHandler.getBoardState();
23
- return {
24
- content: [
25
- { type: "text", text: JSON.stringify(board) }
26
- ],
27
- board
28
- };
29
- }
30
- );
31
-
32
- server.tool(
33
- "submitClue",
34
- { word: z.string(), count: z.number() },
35
- async ({ word, count }: { word: string; count: number }) => {
36
- await browserHandler.submitClue({ word, count });
37
- return {
38
- content: [
39
- { type: "text", text: `Clue submitted: ${word} (${count})` }
40
- ],
41
- success: true
42
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
- );
 
 
 
 
 
 
 
 
 
 
45
 
46
- // Start server with stdio transport
47
- const transport = new StdioServerTransport();
48
- server.connect(transport);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
- main();
 
 
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ // import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; // Corrected class name casing
4
  import { z } from "zod";
5
  import { BoardState, Team } from "../shared/types";
6
  import { CodenamesGameHandler } from "../browser/handler";
7
+ // import * as http from 'http'; // No longer using raw http server
8
+ // import * as url from 'url'; // No longer using raw url parsing
9
+ import express, { Request, Response } from 'express'; // Import express
10
+ // import { JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js"; // Import schema if needed for debug
11
 
12
+ // Keep track of the initialized handler globally or pass it differently
13
+ let browserHandler: CodenamesGameHandler | null = null;
14
+ let mcpServer: McpServer | null = null;
15
+ const transports: { [sessionId: string]: SSEServerTransport } = {};
16
+
17
+ async function initializeBrowserAndMcp() {
18
+ try {
19
+ console.log("Initializing browser handler...");
20
+ browserHandler = await CodenamesGameHandler.create();
21
+ await browserHandler.joinRoom();
22
+ await browserHandler.joinAsSpymaster(Team.Red);
23
+ console.log("Browser handler initialized and joined room/role.");
24
+
25
+ // Now define MCP server and tools *after* async setup is done
26
+ mcpServer = new McpServer(
27
+ { name: "codenames-mcp-server", version: "1.0.0" },
28
+ { capabilities: {} }
29
+ );
30
+
31
+ mcpServer.tool(
32
+ "getBoardState",
33
+ {},
34
+ async () => {
35
+ if (!browserHandler) throw new Error("Browser handler not initialized");
36
+ const board: BoardState = await browserHandler.getBoardState();
37
+ return {
38
+ content: [
39
+ { type: "text", text: JSON.stringify(board) }
40
+ ],
41
+ };
42
+ }
43
+ );
44
+
45
+ mcpServer.tool(
46
+ "submitClue",
47
+ { word: z.string(), count: z.number() },
48
+ async ({ word, count }: { word: string; count: number }) => {
49
+ if (!browserHandler) throw new Error("Browser handler not initialized");
50
+ await browserHandler.submitClue({ word, count });
51
+ return {
52
+ content: [
53
+ { type: "text", text: `Clue submitted: ${word} (${count})` }
54
+ ],
55
+ success: true
56
+ };
57
+ }
58
+ );
59
+
60
+ } catch (initError) {
61
+ console.error("Error during async browser/MCP initialization:", initError);
62
+ process.exit(1); // Exit if essential setup fails
63
  }
64
+ }
65
+
66
+ // Main execution flow
67
+ async function main() {
68
+ try {
69
+ // Perform async initialization first
70
+ await initializeBrowserAndMcp();
71
+
72
+ // --- Express App Setup ---
73
+ const app = express();
74
+ const port = 4000;
75
 
76
+ // --- SSE Connection Route (GET /sse) ---
77
+ app.get("/sse", async (req: Request, res: Response): Promise<void> => {
78
+ const logPrefixBase = `[SSE Connect ${req.ip}]`;
79
+ console.log(`${logPrefixBase} SSE connection request received`);
80
+
81
+ // Check if MCP server is ready
82
+ if (!mcpServer) {
83
+ console.error(`${logPrefixBase} Error: MCP Server not ready for SSE connection`);
84
+ res.status(503).send("Server not ready"); // Send the response
85
+ return; // Then return void
86
+ }
87
+
88
+ // Check for existing session ID (optional, for potential reconnection logic)
89
+ const existingSessionId = req.query.sessionId as string;
90
+ if (existingSessionId && transports[existingSessionId]) {
91
+ console.log(`${logPrefixBase} Reconnecting session: ${existingSessionId}`);
92
+ // Potentially handle reconnection logic here if needed
93
+ }
94
+
95
+ // Generate a unique session ID
96
+ const sessionId = crypto.randomUUID();
97
+ const logPrefix = `[${sessionId}]`;
98
+ console.log(`${logPrefix} Generated new session ID`);
99
+
100
+ try {
101
+ // Create a new SSE transport
102
+ const transport = new SSEServerTransport(`/messages`, res);
103
+ // Use the transport's own sessionId as the key for our map
104
+ const transportSessionId = transport.sessionId;
105
+ transports[transportSessionId] = transport;
106
+ console.log(`${logPrefix} SSE Transport created. Storing under session ID: ${transportSessionId}`); // Log the key being used
107
+
108
+ // Handle client disconnect
109
+ req.on('close', () => {
110
+ console.log(`[${transportSessionId}] Client disconnected`); // Log with the correct ID
111
+ transport.close(); // Close the transport on the server side
112
+ delete transports[transportSessionId]; // Clean up the transport reference using the correct key
113
+ console.log(`[${transportSessionId}] Transport closed and cleaned up.`); // Log with the correct ID
114
+ });
115
+
116
+ // Establish connection with MCP server
117
+ console.log(`[${transportSessionId}] Connecting transport to MCP server...`); // Log with the correct ID
118
+ await mcpServer.connect(transport);
119
+ console.log(`[${transportSessionId}] Transport connected to MCP server.`); // Log with the correct ID
120
+
121
+ } catch (error) {
122
+ // Cannot access transportSessionId here if transport creation failed
123
+ console.error(`${logPrefix} Error setting up SSE connection:`, error);
124
+ // We don't know the transportSessionId if creation failed, so cannot clean up transports[transportSessionId]
125
+ // If creation succeeded but connect failed, the disconnect handler should eventually clean it up.
126
+ if (!res.headersSent) {
127
+ res.status(500).send('Failed to establish SSE connection');
128
+ }
129
+ }
130
+ });
131
+
132
+ // --- Message Posting Route (POST /messages) ---
133
+ app.post(
134
+ "/messages",
135
+ express.json({ limit: "5mb" }),
136
+ async (req: Request, res: Response): Promise<void> => {
137
+ const sessionId = req.query.sessionId as string;
138
+ if (!sessionId) {
139
+ console.error("POST /messages error: Missing sessionId");
140
+ res.status(400).send("Missing sessionId"); return;
141
+ }
142
+ const logPrefix = `[${sessionId}]`;
143
+ console.log(`${logPrefix} POST /messages received`);
144
+ const transport = transports[sessionId];
145
+ const messageBody = req.body;
146
+ if (!transport) {
147
+ console.error(`${logPrefix} POST /messages error: No transport`);
148
+ res.status(404).send("No session"); return;
149
+ }
150
+ if (!messageBody) {
151
+ console.error(`${logPrefix} POST /messages error: No body`);
152
+ res.status(400).send("No body"); return;
153
+ }
154
+ console.log(`${logPrefix} --> Received body:`, JSON.stringify(messageBody, null, 2));
155
+ try {
156
+ console.log(`${logPrefix} DEBUG: Calling transport.handlePostMessage...`);
157
+ await transport.handlePostMessage(req, res, messageBody);
158
+ console.log(`${logPrefix} DEBUG: transport.handlePostMessage completed.`);
159
+ } catch (error) {
160
+ console.error(`${logPrefix} ERROR in POST /messages handler:`, error);
161
+ if (!res.headersSent) {
162
+ res.status(500).send("Server Error");
163
+ }
164
+ }
165
+ }
166
+ );
167
+
168
+ // --- Start Express Server ---
169
+ app.listen(port, '0.0.0.0', () => {
170
+ console.log(`Express server listening on port ${port}`);
171
+ console.log(`MCP SSE endpoint: http://localhost:${port}/sse`);
172
+ console.log(`MCP Messages endpoint: POST http://localhost:${port}/messages`);
173
+ });
174
+
175
+ // Add an indefinite wait to keep the main function/script alive
176
+ console.log("Server setup complete. Entering indefinite wait...");
177
+ await new Promise(() => {}); // This promise never resolves
178
+ } catch (initError) {
179
+ console.error("Error during server initialization:", initError);
180
+ process.exit(1);
181
+ }
182
  }
183
 
184
+ // Call the async main function
185
+ main(); // No catch needed here as errors are handled inside main or by Node