#
tokens: 22691/50000 2/42 files (page 2/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 2. Use http://codebase.md/fyimail/whatsapp-mcp2?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       └── project-rules.mdc
├── .dockerignore
├── .eslintrc.js
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .puppeteer_ws
├── bin
│   └── wweb-mcp.js
├── bin.js
├── Dockerfile
├── eslint.config.js
├── fly.toml
├── jest.config.js
├── LICENSE
├── nodemon.json
├── package-lock.json
├── package.json
├── README.md
├── render.yaml
├── render.yml
├── server.js
├── src
│   ├── api.ts
│   ├── logger.ts
│   ├── main.ts
│   ├── mcp-server.ts
│   ├── middleware
│   │   ├── error-handler.ts
│   │   ├── index.ts
│   │   └── logger.ts
│   ├── minimal-server.ts
│   ├── server.js
│   ├── types.ts
│   ├── whatsapp-api-client.ts
│   ├── whatsapp-client.ts
│   ├── whatsapp-integration.js
│   └── whatsapp-service.ts
├── test
│   ├── setup.ts
│   └── unit
│       ├── api.test.ts
│       ├── mcp-server.test.ts
│       ├── utils.test.ts
│       ├── whatsapp-client.test.ts
│       └── whatsapp-service.test.ts
├── test-local.sh
├── tsconfig.json
├── tsconfig.prod.json
├── tsconfig.test.json
└── whatsapp-integration.zip
```

# Files

--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | // HTTP server with WhatsApp integration
  2 | const http = require('http');
  3 | const url = require('url');
  4 | 
  5 | // Import WhatsApp integration (but don't wait for it)
  6 | const whatsapp = require('./whatsapp-integration');
  7 | 
  8 | // Direct reference to the WhatsApp client for MCP-compatible endpoints
  9 | let whatsappClient = null;
 10 | 
 11 | // Set the WhatsApp client reference when it's ready
 12 | whatsapp.onClientReady((client) => {
 13 |   console.log('[Server] WhatsApp client reference received');
 14 |   whatsappClient = client;
 15 | });
 16 | 
 17 | // Start logging immediately
 18 | console.log(`[STARTUP] Starting HTTP server with WhatsApp integration`);
 19 | console.log(`[STARTUP] Node version: ${process.version}`);
 20 | console.log(`[STARTUP] Platform: ${process.platform}`);
 21 | console.log(`[STARTUP] PORT: ${process.env.PORT || 3000}`);
 22 | 
 23 | // Start WhatsApp initialization in the background WITHOUT awaiting
 24 | // This is critical - we don't block server startup
 25 | setTimeout(() => {
 26 |   console.log('[STARTUP] Starting WhatsApp client initialization in the background');
 27 |   whatsapp.initializeWhatsAppClient().catch(err => {
 28 |     console.error('[STARTUP] Error initializing WhatsApp client:', err);
 29 |     // Non-blocking - server continues running even if WhatsApp fails
 30 |   });
 31 | }, 2000); // Short delay to ensure server is fully up first
 32 | 
 33 | // Create server with no dependencies
 34 | const server = http.createServer((req, res) => {
 35 |   const url = req.url;
 36 |   console.log(`[${new Date().toISOString()}] ${req.method} ${url}`);
 37 | 
 38 |   // Health check endpoint - handle both with and without trailing space
 39 |   if (url === '/health' || url === '/health ' || url === '/health%20') {
 40 |     res.writeHead(200, { 'Content-Type': 'application/json' });
 41 |     res.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString() }));
 42 |     return;
 43 |   }
 44 | 
 45 |   // Root endpoint
 46 |   if (url === '/' || url === '/%20') {
 47 |     res.writeHead(200, { 'Content-Type': 'text/html' });
 48 |     res.end(`
 49 |       <html>
 50 |         <head><title>WhatsApp API Server</title></head>
 51 |         <body>
 52 |           <h1>WhatsApp API Server</h1>
 53 |           <p>Server is running successfully</p>
 54 |           <p>Server time: ${new Date().toISOString()}</p>
 55 |           <p>Node version: ${process.version}</p>
 56 |           <p>Available endpoints:</p>
 57 |           <ul>
 58 |             <li><a href="/health">Health Check</a></li>
 59 |             <li><a href="/status">WhatsApp Status</a></li>
 60 |             <li><a href="/qr">WhatsApp QR Code</a> (when available)</li>
 61 |           </ul>
 62 |         </body>
 63 |       </html>
 64 |     `);
 65 |     return;
 66 |   }
 67 |   
 68 |   // WhatsApp Status endpoint
 69 |   if (url === '/status' || url === '/status%20') {
 70 |     const status = whatsapp.getStatus();
 71 |     res.writeHead(200, { 'Content-Type': 'application/json' });
 72 |     res.end(JSON.stringify({
 73 |       status: status.status,
 74 |       error: status.error,
 75 |       timestamp: new Date().toISOString()
 76 |     }));
 77 |     return;
 78 |   }
 79 |   
 80 |   // WhatsApp QR Code endpoint
 81 |   if (url === '/qr' || url === '/qr%20') {
 82 |     try {
 83 |       // Async function so we need to handle it carefully
 84 |       whatsapp.getQRCode().then(qrCode => {
 85 |         if (!qrCode) {
 86 |           res.writeHead(404, { 'Content-Type': 'application/json' });
 87 |           res.end(JSON.stringify({ error: 'QR code not available', status: whatsapp.getStatus().status }));
 88 |           return;
 89 |         }
 90 |         
 91 |         res.writeHead(200, { 'Content-Type': 'text/html' });
 92 |         res.end(`
 93 |           <html>
 94 |             <head><title>WhatsApp QR Code</title></head>
 95 |             <body>
 96 |               <h1>WhatsApp QR Code</h1>
 97 |               <p>Scan with your WhatsApp mobile app:</p>
 98 |               <img src="${qrCode}" alt="WhatsApp QR Code" style="max-width: 300px;"/>
 99 |               <p>Status: ${whatsapp.getStatus().status}</p>
100 |               <p><a href="/qr">Refresh</a> | <a href="/status">Check Status</a></p>
101 |             </body>
102 |           </html>
103 |         `);
104 |       }).catch(err => {
105 |         console.error('[Server] Error generating QR code:', err);
106 |         res.writeHead(500, { 'Content-Type': 'application/json' });
107 |         res.end(JSON.stringify({ error: 'Failed to generate QR code', details: err.message }));
108 |       });
109 |     } catch (err) {
110 |       res.writeHead(500, { 'Content-Type': 'application/json' });
111 |       res.end(JSON.stringify({ error: 'QR code generation error', details: err.message }));
112 |     }
113 |     return;
114 |   }
115 | 
116 |   // API Key endpoint - simple way to get the current API key
117 |   if (url === '/wa-api' || url === '/wa-api/') {
118 |     const status = whatsapp.getStatus();
119 |     if (status.status === 'ready' && status.apiKey) {
120 |       res.writeHead(200, { 'Content-Type': 'text/html' });
121 |       res.end(`
122 |         <html>
123 |           <head><title>WhatsApp API Key</title></head>
124 |           <body>
125 |             <h1>WhatsApp API Key</h1>
126 |             <p>Current status: <strong>${status.status}</strong></p>
127 |             <p>API Key: <code>${status.apiKey}</code></p>
128 |             <p>MCP command:</p>
129 |             <pre>wweb-mcp -m mcp -s local -c api -t command --api-base-url https://whatsapp-integration-u4q0.onrender.com/api --api-key ${status.apiKey}</pre>
130 |           </body>
131 |         </html>
132 |       `);
133 |     } else {
134 |       res.writeHead(200, { 'Content-Type': 'text/html' });
135 |       res.end(`
136 |         <html>
137 |           <head><title>WhatsApp API Key</title></head>
138 |           <body>
139 |             <h1>WhatsApp API Key</h1>
140 |             <p>Current status: <strong>${status.status}</strong></p>
141 |             <p>API Key not available yet. WhatsApp must be in 'ready' state first.</p>
142 |             <p><a href="/api">Refresh</a> | <a href="/status">Check Status</a> | <a href="/qr">Scan QR Code</a></p>
143 |           </body>
144 |         </html>
145 |       `);
146 |     }
147 |     return;
148 |   }
149 | 
150 |   // MCP Tool specific endpoint - status check with API key (required by wweb-mcp)
151 |   if (url === '/api/status' || url.startsWith('/api/status?')) {
152 |     const status = whatsapp.getStatus();
153 |     const clientApiKey = status.apiKey;
154 |     
155 |     // Only validate API key if client is ready and has an API key
156 |     if (status.status === 'ready' && clientApiKey) {
157 |       // Extract API key from request (if any)
158 |       const urlParams = new URL('http://dummy.com' + req.url).searchParams;
159 |       const requestApiKey = urlParams.get('api_key') || urlParams.get('apiKey');
160 |       const headerApiKey = req.headers['x-api-key'] || req.headers['authorization'];
161 |       const providedApiKey = requestApiKey || (headerApiKey && headerApiKey.replace('Bearer ', ''));
162 |       
163 |       // Validate API key if provided
164 |       if (providedApiKey && providedApiKey !== clientApiKey) {
165 |         console.log(`[${new Date().toISOString()}] Invalid API key for /api/status endpoint`);
166 |         res.writeHead(401, { 'Content-Type': 'application/json' });
167 |         res.end(JSON.stringify({ success: false, error: 'Invalid API key' }));
168 |         return;
169 |       }
170 |     }
171 |     
172 |     console.log(`[${new Date().toISOString()}] MCP status check: ${status.status}`);
173 |     res.writeHead(200, { 
174 |       'Content-Type': 'application/json',
175 |       'Access-Control-Allow-Origin': '*'
176 |     });
177 |     res.end(JSON.stringify({
178 |       success: true,
179 |       connected: status.status === 'ready',
180 |       status: status.status,
181 |       error: status.error,
182 |       timestamp: new Date().toISOString()
183 |     }));
184 |     return;
185 |   }
186 | 
187 |   // Debug endpoint for WhatsApp client state
188 |   if (url === '/api/debug') {
189 |     const status = whatsapp.getStatus();
190 |     const clientInfo = {
191 |       status: status.status,
192 |       connected: status.connected,
193 |       authenticated: status.authenticated || false,
194 |       clientExists: !!whatsappClient,
195 |       clientInfo: whatsappClient ? {
196 |         info: whatsappClient.info ? Object.keys(whatsappClient.info) : null,
197 |         hasChats: typeof whatsappClient.getChats === 'function',
198 |         hasContacts: typeof whatsappClient.getContacts === 'function'
199 |       } : null
200 |     };
201 |     
202 |     res.writeHead(200, { 
203 |       'Content-Type': 'application/json',
204 |       'Access-Control-Allow-Origin': '*'
205 |     });
206 |     res.end(JSON.stringify(clientInfo));
207 |     return;
208 |   }
209 | 
210 |   // MCP Tool endpoint - get all chats (required by wweb-mcp)
211 |   if (url === '/api/chats' || url.startsWith('/api/chats?')) {
212 |     const status = whatsapp.getStatus();
213 |     const clientApiKey = status.apiKey;
214 |     
215 |     // Only validate API key if client is ready and has an API key
216 |     if (status.status === 'ready' && clientApiKey) {
217 |       // Extract API key from request (if any)
218 |       const urlParams = new URL('http://dummy.com' + req.url).searchParams;
219 |       const requestApiKey = urlParams.get('api_key') || urlParams.get('apiKey');
220 |       const headerApiKey = req.headers['x-api-key'] || req.headers['authorization'];
221 |       const providedApiKey = requestApiKey || (headerApiKey && headerApiKey.replace('Bearer ', ''));
222 |       
223 |       // Validate API key if provided
224 |       if (providedApiKey && providedApiKey !== clientApiKey) {
225 |         console.log(`[${new Date().toISOString()}] Invalid API key for /api/chats endpoint`);
226 |         res.writeHead(401, { 'Content-Type': 'application/json' });
227 |         res.end(JSON.stringify({ success: false, error: 'Invalid API key' }));
228 |         return;
229 |       }
230 |     }
231 |     
232 |     // Handle case where WhatsApp is not ready
233 |     if (status.status !== 'ready') {
234 |       console.log(`[${new Date().toISOString()}] /api/chats called but WhatsApp is not ready. Status: ${status.status}`);
235 |       res.writeHead(503, { 'Content-Type': 'application/json' });
236 |       res.end(JSON.stringify({
237 |         success: false,
238 |         error: `WhatsApp not ready. Current status: ${status.status}`,
239 |         status: status.status
240 |       }));
241 |       return;
242 |     }
243 |     
244 |     // Forward the request to the wweb-mcp library
245 |     console.log(`[${new Date().toISOString()}] MCP get_chats request forwarded to WhatsApp client`);
246 |     
247 |     // Check if WhatsApp client reference is valid
248 |     if (!whatsappClient) {
249 |       console.error(`[${new Date().toISOString()}] WhatsApp client reference is null or undefined`);
250 |       res.writeHead(500, { 'Content-Type': 'application/json' });
251 |       res.end(JSON.stringify({
252 |         success: false,
253 |         error: 'WhatsApp client not properly initialized'
254 |       }));
255 |       return;
256 |     }
257 |     
258 |     // Using whatsapp-web.js getChats() function with timeout
259 |     try {
260 |       // Create a timeout promise that rejects after 15 seconds
261 |       const timeoutPromise = new Promise((_, reject) => {
262 |         setTimeout(() => reject(new Error('Request timed out after 15 seconds')), 15000);
263 |       });
264 |       
265 |       // Debug the client's info
266 |       console.log(`[${new Date().toISOString()}] WhatsApp client info:`, {
267 |         id: whatsappClient.info ? whatsappClient.info.wid : 'unknown',
268 |         platform: whatsappClient.info ? whatsappClient.info.platform : 'unknown',
269 |         phone: whatsappClient.info ? whatsappClient.info.phone : 'unknown'
270 |       });
271 |       
272 |       // Enhanced implementation of getChats that's more reliable in containerized environments
273 |       const getChatsCustom = async () => {
274 |         console.log(`[${new Date().toISOString()}] Using enhanced getChats implementation...`);
275 |         
276 |         // First try the standard getChats method
277 |         try {
278 |           console.log(`[${new Date().toISOString()}] Attempting primary getChats method...`);
279 |           const primaryChats = await whatsappClient.getChats();
280 |           if (primaryChats && primaryChats.length > 0) {
281 |             console.log(`[${new Date().toISOString()}] Successfully retrieved ${primaryChats.length} chats using primary method`);
282 |             return primaryChats;
283 |           }
284 |         } catch (err) {
285 |           console.warn(`[${new Date().toISOString()}] Primary getChats method failed:`, err.message);
286 |         }
287 |         
288 |         // Next try to access the internal _chats collection which might be more stable
289 |         if (whatsappClient._chats && whatsappClient._chats.length > 0) {
290 |           console.log(`[${new Date().toISOString()}] Found ${whatsappClient._chats.length} chats in internal collection`);
291 |           return whatsappClient._chats;
292 |         }
293 |         
294 |         // Next try the store which is another way to access chats
295 |         if (whatsappClient.store && typeof whatsappClient.store.getChats === 'function') {
296 |           console.log(`[${new Date().toISOString()}] Attempting to get chats from store...`);
297 |           try {
298 |             const storeChats = await whatsappClient.store.getChats();
299 |             if (storeChats && storeChats.length > 0) {
300 |               console.log(`[${new Date().toISOString()}] Found ${storeChats.length} chats in store`);
301 |               return storeChats;
302 |             }
303 |           } catch (err) {
304 |             console.error(`[${new Date().toISOString()}] Error getting chats from store:`, err);
305 |           }
306 |         }
307 |         
308 |         // Try to get chats using direct access to WA-JS methods (more advanced approach)
309 |         try {
310 |           console.log(`[${new Date().toISOString()}] Attempting to use WA-JS direct access...`);
311 |           const wajs = whatsappClient.pupPage ? await whatsappClient.pupPage.evaluate(() => { 
312 |             return window.WWebJS.getChats().map(c => ({ 
313 |               id: c.id, 
314 |               name: c.name, 
315 |               timestamp: c.t, 
316 |               isGroup: c.isGroup,
317 |               unreadCount: c.unreadCount || 0
318 |             })); 
319 |           }) : null;
320 |           
321 |           if (wajs && wajs.length > 0) {
322 |             console.log(`[${new Date().toISOString()}] Found ${wajs.length} chats using WA-JS direct access`);
323 |             return wajs.map(chat => ({
324 |               id: { _serialized: chat.id },
325 |               name: chat.name || '',
326 |               isGroup: chat.isGroup,
327 |               timestamp: chat.timestamp,
328 |               unreadCount: chat.unreadCount
329 |             }));
330 |           }
331 |         } catch (err) {
332 |           console.error(`[${new Date().toISOString()}] Error using WA-JS direct access:`, err);
333 |         }
334 |         
335 |         // As a fallback, provide at least one mock chat for MCP compatibility
336 |         console.log(`[${new Date().toISOString()}] All methods failed. Falling back to mock chat data`);
337 |         return [{
338 |           id: { _serialized: 'mock-chat-id-1' },
339 |           name: 'Mock Chat (Fallback)',
340 |           isGroup: false,
341 |           timestamp: Date.now() / 1000,
342 |           unreadCount: 0
343 |         }];
344 |       };
345 |       
346 |       // Race between the custom chat implementation and the timeout
347 |       Promise.race([
348 |         getChatsCustom(),
349 |         timeoutPromise
350 |       ]).then(chats => {
351 |         console.log(`[${new Date().toISOString()}] Successfully retrieved ${chats.length} chats`);
352 |         // Transform the chats to the format expected by the MCP tool
353 |         const formattedChats = chats.map(chat => ({
354 |           id: chat.id._serialized,
355 |           name: chat.name || '',
356 |           isGroup: chat.isGroup,
357 |           timestamp: chat.timestamp ? new Date(chat.timestamp * 1000).toISOString() : null,
358 |           unreadCount: chat.unreadCount || 0
359 |         }));
360 |         
361 |         res.writeHead(200, { 
362 |           'Content-Type': 'application/json',
363 |           'Access-Control-Allow-Origin': '*'
364 |         });
365 |         res.end(JSON.stringify({
366 |           success: true,
367 |           chats: formattedChats
368 |         }));
369 |       }).catch(err => {
370 |         console.error(`[${new Date().toISOString()}] Error getting chats:`, err);
371 |         res.writeHead(500, { 'Content-Type': 'application/json' });
372 |         res.end(JSON.stringify({
373 |           success: false,
374 |           error: err.message
375 |         }));
376 |       });
377 |     } catch (err) {
378 |       console.error(`[${new Date().toISOString()}] Exception getting chats:`, err);
379 |       res.writeHead(500, { 'Content-Type': 'application/json' });
380 |       res.end(JSON.stringify({
381 |         success: false,
382 |         error: err.message
383 |       }));
384 |     }
385 |     return;
386 |   }
387 |   
388 |   // MCP Tool endpoint - get messages from a specific chat
389 |   if (url.startsWith('/api/messages/')) {
390 |     const status = whatsapp.getStatus();
391 |     const clientApiKey = status.apiKey;
392 |     
393 |     // Only validate API key if client is ready and has an API key
394 |     if (status.status === 'ready' && clientApiKey) {
395 |       // Extract API key from request (if any)
396 |       const urlParams = new URL('http://dummy.com' + req.url).searchParams;
397 |       const requestApiKey = urlParams.get('api_key') || urlParams.get('apiKey');
398 |       const headerApiKey = req.headers['x-api-key'] || req.headers['authorization'];
399 |       const providedApiKey = requestApiKey || (headerApiKey && headerApiKey.replace('Bearer ', ''));
400 |       
401 |       // Validate API key if provided
402 |       if (providedApiKey && providedApiKey !== clientApiKey) {
403 |         console.log(`[${new Date().toISOString()}] Invalid API key for /api/messages endpoint`);
404 |         res.writeHead(401, { 'Content-Type': 'application/json' });
405 |         res.end(JSON.stringify({ success: false, error: 'Invalid API key' }));
406 |         return;
407 |       }
408 |     }
409 |     
410 |     // Handle case where WhatsApp is not ready
411 |     if (status.status !== 'ready') {
412 |       console.log(`[${new Date().toISOString()}] /api/messages called but WhatsApp is not ready. Status: ${status.status}`);
413 |       res.writeHead(503, { 'Content-Type': 'application/json' });
414 |       res.end(JSON.stringify({
415 |         success: false,
416 |         error: `WhatsApp not ready. Current status: ${status.status}`,
417 |         status: status.status
418 |       }));
419 |       return;
420 |     }
421 |     
422 |     // Extract chat ID from URL
423 |     const pathParts = url.split('?')[0].split('/');
424 |     const chatId = pathParts[3]; // /api/messages/{chatId}
425 |     
426 |     if (!chatId) {
427 |       res.writeHead(400, { 'Content-Type': 'application/json' });
428 |       res.end(JSON.stringify({
429 |         success: false,
430 |         error: 'Missing chat ID in URL'
431 |       }));
432 |       return;
433 |     }
434 | 
435 |     // Get the limit from query params
436 |     const urlParams = new URL('http://dummy.com' + req.url).searchParams;
437 |     const limit = parseInt(urlParams.get('limit') || '20', 10);
438 |     
439 |     // Get messages for this chat
440 |     console.log(`[${new Date().toISOString()}] MCP get_messages request for chat ${chatId}`);
441 |     try {
442 |       // Format chat ID correctly for whatsapp-web.js
443 |       const formattedChatId = chatId.includes('@') ? chatId : `${chatId}@c.us`;
444 |       
445 |       // First get the chat object
446 |       whatsappClient.getChatById(formattedChatId).then(chat => {
447 |         // Then fetch messages
448 |         chat.fetchMessages({ limit }).then(messages => {
449 |           // Format the messages as required by the MCP tool
450 |           const formattedMessages = messages.map(msg => ({
451 |             id: msg.id._serialized,
452 |             body: msg.body || '',
453 |             timestamp: msg.timestamp ? new Date(msg.timestamp * 1000).toISOString() : null,
454 |             from: msg.from || '',
455 |             fromMe: msg.fromMe || false,
456 |             type: msg.type || 'chat'
457 |           }));
458 |           
459 |           res.writeHead(200, { 
460 |             'Content-Type': 'application/json',
461 |             'Access-Control-Allow-Origin': '*'
462 |           });
463 |           res.end(JSON.stringify({
464 |             success: true,
465 |             messages: formattedMessages
466 |           }));
467 |         }).catch(err => {
468 |           console.error(`[${new Date().toISOString()}] Error fetching messages:`, err);
469 |           res.writeHead(500, { 'Content-Type': 'application/json' });
470 |           res.end(JSON.stringify({
471 |             success: false,
472 |             error: err.message
473 |           }));
474 |         });
475 |       }).catch(err => {
476 |         console.error(`[${new Date().toISOString()}] Error getting chat by ID:`, err);
477 |         res.writeHead(404, { 'Content-Type': 'application/json' });
478 |         res.end(JSON.stringify({
479 |           success: false,
480 |           error: `Chat not found: ${err.message}`
481 |         }));
482 |       });
483 |     } catch (err) {
484 |       console.error(`[${new Date().toISOString()}] Exception getting messages:`, err);
485 |       res.writeHead(500, { 'Content-Type': 'application/json' });
486 |       res.end(JSON.stringify({
487 |         success: false,
488 |         error: err.message
489 |       }));
490 |     }
491 |     return;
492 |   }
493 |   
494 |   // Support OPTIONS requests for CORS
495 |   if (req.method === 'OPTIONS') {
496 |     res.writeHead(200, {
497 |       'Access-Control-Allow-Origin': '*',
498 |       'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
499 |       'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key'
500 |     });
501 |     res.end();
502 |     return;
503 |   }
504 | 
505 |   // Add a test message endpoint to validate sending works
506 |   if (url === '/api/test-message') {
507 |     const status = whatsapp.getStatus();
508 |     
509 |     // Check if WhatsApp is ready
510 |     if (status.status !== 'ready') {
511 |       console.log(`[${new Date().toISOString()}] /api/test-message called but WhatsApp is not ready. Status: ${status.status}`);
512 |       res.writeHead(503, { 'Content-Type': 'application/json' });
513 |       res.end(JSON.stringify({
514 |         success: false,
515 |         error: `WhatsApp not ready. Current status: ${status.status}`,
516 |         status: status.status
517 |       }));
518 |       return;
519 |     }
520 | 
521 |     // Send a test message to the specified number
522 |     const testNumber = '16505578984';
523 |     const testMessage = `Test message from WhatsApp API at ${new Date().toISOString()}`;
524 |     
525 |     console.log(`[${new Date().toISOString()}] Sending test message to ${testNumber}`);
526 |     
527 |     whatsapp.sendMessage(testNumber, testMessage)
528 |       .then(result => {
529 |         console.log(`[${new Date().toISOString()}] Test message sent successfully to ${testNumber}`);
530 |         res.writeHead(200, { 'Content-Type': 'application/json' });
531 |         res.end(JSON.stringify({
532 |           success: true,
533 |           message: 'Test message sent successfully',
534 |           to: testNumber,
535 |           messageId: result.id ? result.id._serialized : 'sent',
536 |           content: testMessage
537 |         }));
538 |       })
539 |       .catch(err => {
540 |         console.error(`[${new Date().toISOString()}] Error sending test message:`, err);
541 |         res.writeHead(500, { 'Content-Type': 'application/json' });
542 |         res.end(JSON.stringify({
543 |           success: false,
544 |           error: err.message
545 |         }));
546 |       });
547 |     
548 |     return;
549 |   }
550 | 
551 |   // Handle API message send endpoint POST /api/send
552 |   if (url === '/api/send' && req.method === 'POST') {
553 |     const status = whatsapp.getStatus();
554 |     const clientApiKey = status.apiKey;
555 |     
556 |     // Only validate API key if client is ready and has an API key
557 |     if (status.status === 'ready' && clientApiKey) {
558 |       // Extract API key from request (if any)
559 |       const headerApiKey = req.headers['x-api-key'] || req.headers['authorization'];
560 |       const providedApiKey = headerApiKey && headerApiKey.replace('Bearer ', '');
561 |       
562 |       // Validate API key if provided
563 |       if (!providedApiKey || providedApiKey !== clientApiKey) {
564 |         console.log(`[${new Date().toISOString()}] Invalid or missing API key for /api/send endpoint`);
565 |         res.writeHead(401, { 'Content-Type': 'application/json' });
566 |         res.end(JSON.stringify({ success: false, error: 'Invalid or missing API key' }));
567 |         return;
568 |       }
569 |     } else {
570 |       // Handle case where WhatsApp is not ready
571 |       console.log(`[${new Date().toISOString()}] /api/send called but WhatsApp is not ready. Status: ${status.status}`);
572 |       res.writeHead(503, { 'Content-Type': 'application/json' });
573 |       res.end(JSON.stringify({
574 |         success: false,
575 |         error: `WhatsApp not ready. Current status: ${status.status}`,
576 |         status: status.status
577 |       }));
578 |       return;
579 |     }
580 | 
581 |     // Get request body
582 |     let body = '';
583 |     req.on('data', chunk => {
584 |       body += chunk.toString();
585 |     });
586 | 
587 |     req.on('end', async () => {
588 |       try {
589 |         const data = JSON.parse(body);
590 |         const { to, message } = data;
591 |         
592 |         if (!to || !message) {
593 |           res.writeHead(400, { 'Content-Type': 'application/json' });
594 |           res.end(JSON.stringify({
595 |             success: false,
596 |             error: 'Missing required fields: to, message'
597 |           }));
598 |           return;
599 |         }
600 | 
601 |         console.log(`[${new Date().toISOString()}] Sending message to ${to}`);
602 |         
603 |         try {
604 |           const result = await whatsapp.sendMessage(to, message);
605 |           console.log(`[${new Date().toISOString()}] Message sent successfully to ${to}`);
606 |           
607 |           res.writeHead(200, { 
608 |             'Content-Type': 'application/json',
609 |             'Access-Control-Allow-Origin': '*'
610 |           });
611 |           res.end(JSON.stringify({
612 |             success: true,
613 |             messageId: result.id ? result.id._serialized : 'sent',
614 |             to: result.to || to
615 |           }));
616 |         } catch (err) {
617 |           console.error(`[${new Date().toISOString()}] Error sending message:`, err);
618 |           res.writeHead(500, { 'Content-Type': 'application/json' });
619 |           res.end(JSON.stringify({
620 |             success: false,
621 |             error: err.message
622 |           }));
623 |         }
624 |       } catch (err) {
625 |         console.error(`[${new Date().toISOString()}] Error parsing JSON:`, err);
626 |         res.writeHead(400, { 'Content-Type': 'application/json' });
627 |         res.end(JSON.stringify({
628 |           success: false,
629 |           error: 'Invalid JSON in request body'
630 |         }));
631 |       }
632 |     });
633 |     return;
634 |   }
635 |   
636 |   // Handle preflight CORS requests
637 |   if (req.method === 'OPTIONS') {
638 |     res.writeHead(200, {
639 |       'Access-Control-Allow-Origin': '*',
640 |       'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
641 |       'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key',
642 |       'Access-Control-Max-Age': '86400'
643 |     });
644 |     res.end();
645 |     return;
646 |   }
647 | 
648 |   // Add endpoint to get the most recent message from a chat
649 |   if (url === '/api/recent-message' || url.startsWith('/api/recent-message?')) {
650 |     const status = whatsapp.getStatus();
651 |     const clientApiKey = status.apiKey;
652 |     
653 |     // Only validate API key if client is ready and has an API key
654 |     if (status.status === 'ready' && clientApiKey) {
655 |       // Extract API key from request (if any)
656 |       const urlParams = new URL('http://dummy.com' + req.url).searchParams;
657 |       const requestApiKey = urlParams.get('api_key') || urlParams.get('apiKey');
658 |       const headerApiKey = req.headers['x-api-key'] || req.headers['authorization'];
659 |       const providedApiKey = requestApiKey || (headerApiKey && headerApiKey.replace('Bearer ', ''));
660 |       
661 |       // Validate API key if provided
662 |       if (providedApiKey && providedApiKey !== clientApiKey) {
663 |         console.log(`[${new Date().toISOString()}] Invalid API key for /api/recent-message endpoint`);
664 |         res.writeHead(401, { 'Content-Type': 'application/json' });
665 |         res.end(JSON.stringify({ success: false, error: 'Invalid API key' }));
666 |         return;
667 |       }
668 |     }
669 |     
670 |     // Handle case where WhatsApp is not ready
671 |     if (status.status !== 'ready') {
672 |       console.log(`[${new Date().toISOString()}] /api/recent-message called but WhatsApp is not ready. Status: ${status.status}`);
673 |       res.writeHead(503, { 'Content-Type': 'application/json' });
674 |       res.end(JSON.stringify({
675 |         success: false,
676 |         error: `WhatsApp not ready. Current status: ${status.status}`,
677 |         status: status.status
678 |       }));
679 |       return;
680 |     }
681 |     
682 |     console.log(`[${new Date().toISOString()}] Getting most recent chat messages...`);
683 |     
684 |     // Check if WhatsApp client reference is valid
685 |     if (!whatsappClient) {
686 |       console.error(`[${new Date().toISOString()}] WhatsApp client reference is null or undefined`);
687 |       res.writeHead(500, { 'Content-Type': 'application/json' });
688 |       res.end(JSON.stringify({
689 |         success: false,
690 |         error: 'WhatsApp client not properly initialized'
691 |       }));
692 |       return;
693 |     }
694 |     
695 |     // Using enhanced method to get chats first
696 |     // Wrap the whole logic in an async IIFE (Immediately Invoked Function Expression)
697 |     (async () => {
698 |       try {
699 |         // Create a timeout promise
700 |         const timeoutPromise = new Promise((_, reject) => {
701 |           setTimeout(() => reject(new Error('Request timed out after 15 seconds')), 15000);
702 |         });
703 |         
704 |         // Enhanced method to get recent message
705 |         const getRecentMessage = async () => {
706 |           try {
707 |             // First get chats using our enhanced method
708 |             const getChatsCustom = async () => {
709 |               // Try the standard getChats method first
710 |               try {
711 |                 const primaryChats = await whatsappClient.getChats();
712 |                 if (primaryChats && primaryChats.length > 0) {
713 |                   return primaryChats;
714 |                 }
715 |               } catch (err) {
716 |                 console.warn(`[${new Date().toISOString()}] Standard getChats failed:`, err.message);
717 |               }
718 |               
719 |               // Try direct access to _chats
720 |               if (whatsappClient._chats && whatsappClient._chats.length > 0) {
721 |                 return whatsappClient._chats;
722 |               }
723 |               
724 |               // Try using store
725 |               if (whatsappClient.store && typeof whatsappClient.store.getChats === 'function') {
726 |                 try {
727 |                   const storeChats = await whatsappClient.store.getChats();
728 |                   if (storeChats && storeChats.length > 0) {
729 |                     return storeChats;
730 |                   }
731 |                 } catch (err) {}
732 |               }
733 |               
734 |               // Try WA-JS direct access
735 |               try {
736 |                 const wajs = whatsappClient.pupPage ? await whatsappClient.pupPage.evaluate(() => { 
737 |                   return window.WWebJS.getChats().map(c => ({ 
738 |                     id: c.id, 
739 |                     name: c.name, 
740 |                     timestamp: c.t, 
741 |                     isGroup: c.isGroup
742 |                   })); 
743 |                 }) : null;
744 |                 
745 |                 if (wajs && wajs.length > 0) {
746 |                   return wajs.map(chat => ({
747 |                     id: { _serialized: chat.id },
748 |                     name: chat.name || '',
749 |                     isGroup: chat.isGroup,
750 |                     timestamp: chat.timestamp
751 |                   }));
752 |                 }
753 |               } catch (err) {}
754 |               
755 |               // Fallback
756 |               return [];
757 |             };
758 |             
759 |             // Get chats using our enhanced method
760 |             const chats = await getChatsCustom();
761 |             console.log(`[${new Date().toISOString()}] Retrieved ${chats.length} chats`);
762 |             
763 |             if (chats.length === 0) {
764 |               return { noChats: true };
765 |             }
766 |             
767 |             // Sort chats by timestamp if available
768 |             const sortedChats = chats.sort((a, b) => {
769 |               const timeA = a.timestamp || 0;
770 |               const timeB = b.timestamp || 0;
771 |               return timeB - timeA; // Newest first
772 |             });
773 |             
774 |             // Get most recent chat
775 |             const recentChat = sortedChats[0];
776 |             if (!recentChat || !recentChat.id || !recentChat.id._serialized) {
777 |               return { noValidChat: true, chats: sortedChats.length };
778 |             }
779 |             
780 |             // Get messages from this chat
781 |             console.log(`[${new Date().toISOString()}] Getting messages from chat: ${recentChat.id._serialized}`);
782 |             try {
783 |               // Format chat ID correctly for whatsapp-web.js
784 |               const formattedChatId = recentChat.id._serialized;
785 |               const chat = await whatsappClient.getChatById(formattedChatId);
786 |               const messages = await chat.fetchMessages({ limit: 1 });
787 |               
788 |               if (messages && messages.length > 0) {
789 |                 return { 
790 |                   success: true,
791 |                   chat: {
792 |                     id: recentChat.id._serialized,
793 |                     name: recentChat.name || '',
794 |                     isGroup: recentChat.isGroup || false
795 |                   },
796 |                   message: {
797 |                     id: messages[0].id._serialized,
798 |                     body: messages[0].body || '',
799 |                     timestamp: messages[0].timestamp ? new Date(messages[0].timestamp * 1000).toISOString() : null,
800 |                     from: messages[0].from || '',
801 |                     fromMe: messages[0].fromMe || false
802 |                   }
803 |                 };
804 |               } else {
805 |                 return { noMessages: true, chatId: formattedChatId };
806 |               }
807 |             } catch (err) {
808 |               console.error(`[${new Date().toISOString()}] Error getting chat by ID:`, err);
809 |               return { chatError: err.message, chatId: recentChat.id._serialized };
810 |             }
811 |           } catch (err) {
812 |             console.error(`[${new Date().toISOString()}] Error in getRecentMessage:`, err);
813 |             return { error: err.message };
814 |           }
815 |         };
816 |         
817 |         // Race between the fetching and the timeout
818 |         const result = await Promise.race([
819 |           getRecentMessage(),
820 |           timeoutPromise
821 |         ]);
822 |         
823 |         console.log(`[${new Date().toISOString()}] Recent message request result:`, JSON.stringify(result));
824 |         
825 |         res.writeHead(200, { 
826 |           'Content-Type': 'application/json',
827 |           'Access-Control-Allow-Origin': '*'
828 |         });
829 |         res.end(JSON.stringify(result));
830 |       } catch (err) {
831 |         console.error(`[${new Date().toISOString()}] Exception getting recent message:`, err);
832 |         res.writeHead(500, { 'Content-Type': 'application/json' });
833 |         res.end(JSON.stringify({
834 |           success: false,
835 |           error: err.message
836 |         }));
837 |       }
838 |     })();
839 |     return;
840 |   }
841 | 
842 |   // 404 for everything else
843 |   res.writeHead(404, { 'Content-Type': 'text/plain' });
844 |   res.end('Not Found');
845 | });
846 | 
847 | // Listen on all interfaces
848 | const PORT = process.env.PORT || 3000;
849 | server.listen(PORT, '0.0.0.0', () => {
850 |   console.log(`[${new Date().toISOString()}] Server listening on port ${PORT}`);
851 | });
852 | 
853 | // Handle termination gracefully
854 | process.on('SIGINT', () => {
855 |   console.log(`[${new Date().toISOString()}] Server shutting down`);
856 |   process.exit(0);
857 | });
858 | 
859 | // Handle uncaught exceptions
860 | process.on('uncaughtException', error => {
861 |   console.error(`[${new Date().toISOString()}] Uncaught exception: ${error.message}`);
862 |   console.error(error.stack);
863 |   // Keep server running despite errors
864 | });
865 | 
```

--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------

```typescript
   1 | import express, { NextFunction, Request, Response } from 'express';
   2 | import { createMcpServer, McpConfig } from './mcp-server';
   3 | import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
   4 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
   5 | import { createWhatsAppClient, WhatsAppConfig } from './whatsapp-client';
   6 | import yargs from 'yargs';
   7 | import { hideBin } from 'yargs/helpers';
   8 | import logger, { configureForCommandMode } from './logger';
   9 | import { requestLogger, errorHandler } from './middleware';
  10 | import { routerFactory } from './api';
  11 | import { Client } from 'whatsapp-web.js';
  12 | import fs from 'fs';
  13 | import path from 'path';
  14 | import crypto from 'crypto';
  15 | 
  16 | const isDockerContainer = process.env.DOCKER_CONTAINER === 'true';
  17 | 
  18 | function parseCommandLineArgs(): ReturnType<typeof yargs.parseSync> {
  19 |   return yargs(hideBin(process.argv))
  20 |     .option('mode', {
  21 |       alias: 'm',
  22 |       description: 'Run mode: mcp or whatsapp-api',
  23 |       type: 'string',
  24 |       choices: ['mcp', 'whatsapp-api'],
  25 |       default: 'mcp',
  26 |     })
  27 |     .option('mcp-mode', {
  28 |       alias: 'c',
  29 |       description:
  30 |         'MCP connection mode: standalone (direct WhatsApp client) or api (connect to WhatsApp API)',
  31 |       type: 'string',
  32 |       choices: ['standalone', 'api'],
  33 |       default: 'standalone',
  34 |     })
  35 |     .option('transport', {
  36 |       alias: 't',
  37 |       description: 'MCP transport mode: sse or command',
  38 |       type: 'string',
  39 |       choices: ['sse', 'command'],
  40 |       default: 'sse',
  41 |     })
  42 |     .option('sse-port', {
  43 |       alias: 'p',
  44 |       description: 'Port for SSE server',
  45 |       type: 'number',
  46 |       default: 3002,
  47 |     })
  48 |     .option('api-port', {
  49 |       description: 'Port for WhatsApp API server',
  50 |       type: 'number',
  51 |       default: 3002,
  52 |     })
  53 |     .option('auth-data-path', {
  54 |       alias: 'a',
  55 |       description: 'Path to store authentication data',
  56 |       type: 'string',
  57 |       default: '.wwebjs_auth',
  58 |     })
  59 |     .option('auth-strategy', {
  60 |       alias: 's',
  61 |       description: 'Authentication strategy: local or none',
  62 |       type: 'string',
  63 |       choices: ['local', 'none'],
  64 |       default: 'local',
  65 |     })
  66 |     .option('api-key', {
  67 |       alias: 'k',
  68 |       description: 'API key for WhatsApp Web REST API when using api mode',
  69 |       type: 'string',
  70 |       default: '',
  71 |     })
  72 |     .option('log-level', {
  73 |       alias: 'l',
  74 |       description: 'Log level: error, warn, info, http, debug',
  75 |       type: 'string',
  76 |       choices: ['error', 'warn', 'info', 'http', 'debug'],
  77 |       default: 'info',
  78 |     })
  79 |     .help()
  80 |     .alias('help', 'h')
  81 |     .parseSync();
  82 | }
  83 | 
  84 | function configureLogger(argv: ReturnType<typeof parseCommandLineArgs>): void {
  85 |   logger.level = argv['log-level'] as string;
  86 | 
  87 |   // Configure logger to use stderr for all levels when in MCP command mode
  88 |   if (argv.mode === 'mcp' && argv.transport === 'command') {
  89 |     configureForCommandMode();
  90 |   }
  91 | }
  92 | 
  93 | function createConfigurations(argv: ReturnType<typeof parseCommandLineArgs>): {
  94 |   whatsAppConfig: WhatsAppConfig;
  95 |   mcpConfig: McpConfig;
  96 | } {
  97 |   const whatsAppConfig: WhatsAppConfig = {
  98 |     authDir: argv['auth-data-path'] as string,
  99 |     authStrategy: argv['auth-strategy'] as 'local' | 'none',
 100 |     dockerContainer: isDockerContainer,
 101 |   };
 102 | 
 103 |   const mcpConfig: McpConfig = {
 104 |     useApiClient: argv['mcp-mode'] === 'api',
 105 |     apiKey: argv['api-key'] as string,
 106 |     whatsappConfig: whatsAppConfig,
 107 |   };
 108 | 
 109 |   return { whatsAppConfig, mcpConfig };
 110 | }
 111 | 
 112 | async function startMcpSseServer(
 113 |   server: ReturnType<typeof createMcpServer>,
 114 |   port: number,
 115 |   mode: string,
 116 | ): Promise<void> {
 117 |   const app = express();
 118 |   app.use(requestLogger);
 119 | 
 120 |   let transport: SSEServerTransport;
 121 | 
 122 |   app.get('/sse', async (_req: Request, res: Response) => {
 123 |     logger.info('Received SSE connection');
 124 |     transport = new SSEServerTransport('/message', res);
 125 |     await server.connect(transport);
 126 |   });
 127 | 
 128 |   app.post('/message', async (req: Request, res: Response) => {
 129 |     await transport?.handlePostMessage(req, res);
 130 |   });
 131 | 
 132 |   app.use(errorHandler);
 133 | 
 134 |   app.listen(port, '0.0.0.0', () => {
 135 |     logger.info(`MCP server is running on port ${port} in ${mode} mode`);
 136 |   });
 137 | }
 138 | 
 139 | async function startMcpCommandServer(
 140 |   server: ReturnType<typeof createMcpServer>,
 141 |   mode: string,
 142 | ): Promise<void> {
 143 |   try {
 144 |     const transport = new StdioServerTransport();
 145 |     await server.connect(transport);
 146 |     logger.info(`WhatsApp MCP server started successfully in ${mode} mode`);
 147 | 
 148 |     process.stdin.on('close', () => {
 149 |       logger.info('WhatsApp MCP Server closed');
 150 |       server.close();
 151 |     });
 152 |   } catch (error) {
 153 |     logger.error('Error connecting to MCP server', error);
 154 |   }
 155 | }
 156 | 
 157 | async function getWhatsAppApiKey(whatsAppConfig: WhatsAppConfig): Promise<string> {
 158 |   if (whatsAppConfig.authStrategy === 'none') {
 159 |     return crypto.randomBytes(32).toString('hex');
 160 |   }
 161 |   const authDataPath = whatsAppConfig.authDir;
 162 |   if (!authDataPath) {
 163 |     throw new Error('The auth-data-path is required when using whatsapp-api mode');
 164 |   }
 165 |   const apiKeyPath = path.join(authDataPath, 'api_key.txt');
 166 |   if (!fs.existsSync(apiKeyPath)) {
 167 |     const apiKey = crypto.randomBytes(32).toString('hex');
 168 |     fs.writeFileSync(apiKeyPath, apiKey);
 169 |     return apiKey;
 170 |   }
 171 |   return fs.readFileSync(apiKeyPath, 'utf8');
 172 | }
 173 | 
 174 | async function startWhatsAppApiServer(whatsAppConfig: WhatsAppConfig, port: number): Promise<void> {
 175 |   logger.info('[WA] Starting WhatsApp Web REST API...');
 176 | 
 177 |   // Create the Express app before initializing WhatsApp client
 178 |   const app = express();
 179 | 
 180 |   // Add error handling to all middleware
 181 |   app.use((req: Request, res: Response, next: NextFunction) => {
 182 |     try {
 183 |       requestLogger(req, res, next);
 184 |     } catch (error) {
 185 |       logger.error('[WA] Error in request logger middleware:', error);
 186 |       next();
 187 |     }
 188 |   });
 189 | 
 190 |   app.use(express.json());
 191 | 
 192 |   // CRITICAL: Track server start time - helps with troubleshooting
 193 |   const serverStartTime = new Date();
 194 | 
 195 |   // Set up minimal state management for diagnostics
 196 |   const state = {
 197 |     whatsappInitializing: false,
 198 |     whatsappInitStarted: false,
 199 |     whatsappError: null as Error | null,
 200 |     clientReady: false,
 201 |     latestQrCode: null as string | null,
 202 |     client: null as any, // Will hold the WhatsApp client instance once initialized
 203 |     environment: {
 204 |       node: process.version,
 205 |       platform: process.platform,
 206 |       port: port || process.env.PORT || 3000,
 207 |       pid: process.pid,
 208 |       uptime: () => Math.floor((new Date().getTime() - serverStartTime.getTime()) / 1000),
 209 |     },
 210 |   };
 211 | 
 212 |   // Log important startup information
 213 |   logger.info(
 214 |     `[WA] Server starting with Node ${state.environment.node} on ${state.environment.platform}`,
 215 |   );
 216 |   logger.info(`[WA] Process ID: ${state.environment.pid}`);
 217 |   logger.info(`[WA] Port: ${state.environment.port}`);
 218 |   logger.info(`[WA] Start time: ${serverStartTime.toISOString()}`);
 219 | 
 220 |   // EMERGENCY DIAGNOSTIC endpoint - absolutely minimal, will help diagnose deployment issues
 221 |   app.get('/', (_req: Request, res: Response) => {
 222 |     res.status(200).send(`
 223 |       <html>
 224 |         <head><title>WhatsApp API Service</title></head>
 225 |         <body>
 226 |           <h1>WhatsApp API Service</h1>
 227 |           <p>Server is running</p>
 228 |           <p>Uptime: ${state.environment.uptime()} seconds</p>
 229 |           <p>Started: ${serverStartTime.toISOString()}</p>
 230 |           <p>Node: ${state.environment.node}</p>
 231 |           <p>Platform: ${state.environment.platform}</p>
 232 |           <p>WhatsApp Status: ${
 233 |             state.whatsappInitStarted
 234 |               ? state.clientReady
 235 |                 ? 'Ready'
 236 |                 : state.whatsappError
 237 |                   ? 'Error'
 238 |                   : 'Initializing'
 239 |               : 'Not started'
 240 |           }</p>
 241 |           <ul>
 242 |             <li><a href="/health">Health Check</a></li>
 243 |             <li><a href="/memory-usage">Memory Usage</a></li>
 244 |             <li><a href="/container-env">Container Environment</a></li>
 245 |             <li><a href="/filesys">File System Check</a></li>
 246 |             <li><a href="/qr">QR Code</a> (if available)</li>
 247 |           </ul>
 248 |         </body>
 249 |       </html>
 250 |     `);
 251 |   });
 252 | 
 253 |   // Add health check endpoint that doesn't require authentication
 254 |   // CRITICAL: This must be minimal and not depend on any WhatsApp state
 255 |   app.get('/health', (_req: Request, res: Response) => {
 256 |     try {
 257 |       // Always return 200 for Render health check, even if WhatsApp is still initializing
 258 |       res.status(200).json({
 259 |         status: 'ok',
 260 |         server: 'running',
 261 |         uptime: state.environment.uptime(),
 262 |         startTime: serverStartTime.toISOString(),
 263 |         whatsappStarted: state.whatsappInitStarted,
 264 |         whatsapp: state.clientReady
 265 |           ? 'ready'
 266 |           : state.whatsappError
 267 |             ? 'error'
 268 |             : state.whatsappInitializing
 269 |               ? 'initializing'
 270 |               : 'not_started',
 271 |         timestamp: new Date().toISOString(),
 272 |       });
 273 |     } catch (error) {
 274 |       logger.error('[WA] Error in health check endpoint:', error);
 275 |       // Still return 200 to keep Render happy
 276 |       res.status(200).send('OK');
 277 |     }
 278 |   });
 279 | 
 280 |   // Add /wa-api endpoint for backwards compatibility with previous implementation
 281 |   app.get('/wa-api', (_req: Request, res: Response) => {
 282 |     try {
 283 |       // Get the API key from the same place as the official implementation
 284 |       const apiKeyPath = path.join(whatsAppConfig.authDir || '.wwebjs_auth', 'api_key.txt');
 285 | 
 286 |       if (fs.existsSync(apiKeyPath)) {
 287 |         const apiKey = fs.readFileSync(apiKeyPath, 'utf8');
 288 |         logger.info('[WA] API key retrieved for /wa-api endpoint');
 289 | 
 290 |         res.status(200).json({
 291 |           status: 'success',
 292 |           message: 'WhatsApp API key',
 293 |           apiKey: apiKey,
 294 |         });
 295 |       } else {
 296 |         logger.warn('[WA] API key file not found for /wa-api endpoint');
 297 |         res.status(404).json({
 298 |           status: 'error',
 299 |           message: 'API key not found. Service might still be initializing.',
 300 |         });
 301 |       }
 302 |     } catch (error) {
 303 |       logger.error('[WA] Error retrieving API key for /wa-api endpoint:', error);
 304 |       res.status(500).json({
 305 |         status: 'error',
 306 |         message: 'Failed to retrieve API key',
 307 |         error: error instanceof Error ? error.message : String(error),
 308 |       });
 309 |     }
 310 |   });
 311 | 
 312 |   // Add QR code endpoint with enhanced error handling
 313 |   app.get('/qr', (_req: Request, res: Response) => {
 314 |     try {
 315 |       // First try to get QR from file
 316 |       try {
 317 |         const qrPath = path.join('/var/data/whatsapp', 'last-qr.txt');
 318 |         if (fs.existsSync(qrPath)) {
 319 |           try {
 320 |             const qrCode = fs.readFileSync(qrPath, 'utf8');
 321 |             return res.send(`
 322 |               <html>
 323 |                 <head>
 324 |                   <title>WhatsApp QR Code</title>
 325 |                   <meta name="viewport" content="width=device-width, initial-scale=1">
 326 |                   <style>
 327 |                     body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
 328 |                     .qr-container { margin: 20px auto; }
 329 |                     pre { background: #f4f4f4; padding: 20px; display: inline-block; text-align: left; }
 330 |                     .status { color: #555; margin: 20px 0; }
 331 |                   </style>
 332 |                 </head>
 333 |                 <body>
 334 |                   <h1>WhatsApp QR Code</h1>
 335 |                   <p>Scan this QR code with your WhatsApp app to link your device</p>
 336 |                   <div class="qr-container">
 337 |                     <pre>${qrCode}</pre>
 338 |                   </div>
 339 |                   <p class="status">Server status: ${state.whatsappInitializing ? 'Initializing WhatsApp...' : state.clientReady ? 'WhatsApp Ready' : 'Waiting for authentication'}</p>
 340 |                   <p><small>Last updated: ${new Date().toISOString()}</small></p>
 341 |                   <p><a href="/">Back to Home</a></p>
 342 |                 </body>
 343 |               </html>
 344 |             `);
 345 |           } catch (readError) {
 346 |             logger.error('[WA] Error reading QR file:', readError);
 347 |             // Continue to fallback methods
 348 |           }
 349 |         }
 350 |       } catch (fileError) {
 351 |         logger.error('[WA] Error accessing QR file system:', fileError);
 352 |         // Continue to fallback methods
 353 |       }
 354 | 
 355 |       // Fallback to in-memory QR code
 356 |       if (state.latestQrCode) {
 357 |         try {
 358 |           res.type('text/plain');
 359 |           return res.send(state.latestQrCode);
 360 |         } catch (error) {
 361 |           logger.error('[WA] Error sending QR code as text:', error);
 362 |           // Continue to final fallback
 363 |         }
 364 |       }
 365 | 
 366 |       // Final fallback - just return status
 367 |       if (state.whatsappError) {
 368 |         return res
 369 |           .status(500)
 370 |           .send(`WhatsApp initialization error: ${state.whatsappError.message}`);
 371 |       } else if (state.whatsappInitializing) {
 372 |         return res
 373 |           .status(202)
 374 |           .send('WhatsApp client is still initializing. Please try again in a minute.');
 375 |       } else if (state.clientReady) {
 376 |         return res.status(200).send('WhatsApp client is already authenticated. No QR code needed.');
 377 |       } else if (!state.whatsappInitStarted) {
 378 |         return res
 379 |           .status(200)
 380 |           .send('WhatsApp initialization has not been started yet. Check server logs.');
 381 |       } else {
 382 |         return res.status(404).send('QR code not yet available. Please try again in a moment.');
 383 |       }
 384 |     } catch (error) {
 385 |       logger.error('[WA] Unhandled error in QR endpoint:', error);
 386 |       res.status(500).send('Internal server error processing QR request');
 387 |     }
 388 |   });
 389 | 
 390 |   // Add status endpoint with enhanced error handling
 391 |   app.get('/status', (_req: Request, res: Response) => {
 392 |     try {
 393 |       res.status(200).json({
 394 |         server: 'running',
 395 |         uptime: state.environment.uptime(),
 396 |         startTime: serverStartTime.toISOString(),
 397 |         whatsappStarted: state.whatsappInitStarted,
 398 |         whatsapp: state.clientReady
 399 |           ? 'ready'
 400 |           : state.whatsappError
 401 |             ? 'error'
 402 |             : state.whatsappInitializing
 403 |               ? 'initializing'
 404 |               : 'not_started',
 405 |         error: state.whatsappError ? state.whatsappError.message : null,
 406 |         timestamp: new Date().toISOString(),
 407 |       });
 408 |     } catch (error) {
 409 |       logger.error('[WA] Error in status endpoint:', error);
 410 |       res.status(500).send('Error getting status');
 411 |     }
 412 |   });
 413 | 
 414 |   // Add memory usage endpoint for troubleshooting
 415 |   app.get('/memory-usage', (_req: Request, res: Response) => {
 416 |     try {
 417 |       const formatMemoryUsage = (data: number) =>
 418 |         `${Math.round((data / 1024 / 1024) * 100) / 100} MB`;
 419 | 
 420 |       const memoryData = process.memoryUsage();
 421 | 
 422 |       const memoryUsage = {
 423 |         rss: formatMemoryUsage(memoryData.rss),
 424 |         heapTotal: formatMemoryUsage(memoryData.heapTotal),
 425 |         heapUsed: formatMemoryUsage(memoryData.heapUsed),
 426 |         external: formatMemoryUsage(memoryData.external),
 427 |         arrayBuffers: formatMemoryUsage(memoryData.arrayBuffers || 0),
 428 |         rawData: memoryData,
 429 |         timestamp: new Date().toISOString(),
 430 |       };
 431 | 
 432 |       logger.info('[WA] Memory usage report:', memoryUsage);
 433 |       res.status(200).json(memoryUsage);
 434 |     } catch (error) {
 435 |       logger.error('[WA] Error in memory-usage endpoint:', error);
 436 |       res.status(500).send('Error getting memory usage');
 437 |     }
 438 |   });
 439 | 
 440 |   // API endpoint to get all chats (leverages the MCP get_chats tool)
 441 |   app.get('/api/chats', async (_req: Request, res: Response) => {
 442 |     try {
 443 |       if (!state.clientReady) {
 444 |         return res.status(503).json({
 445 |           status: 'error',
 446 |           message: 'WhatsApp client not ready',
 447 |           whatsappStatus: state.clientReady ? 'ready' : state.whatsappError ? 'error' : 'initializing',
 448 |         });
 449 |       }
 450 | 
 451 |       const whatsappClient = state.client;
 452 |       if (!whatsappClient) {
 453 |         return res.status(500).json({
 454 |           status: 'error',
 455 |           message: 'WhatsApp client not available',
 456 |         });
 457 |       }
 458 | 
 459 |       logger.info('[WA] Getting all chats');
 460 |       const chats = await whatsappClient.getChats();
 461 |       
 462 |       // Format the chats in a more API-friendly format
 463 |       const formattedChats = chats.map(chat => ({
 464 |         id: chat.id._serialized,
 465 |         name: chat.name,
 466 |         isGroup: chat.isGroup,
 467 |         timestamp: chat.timestamp ? new Date(chat.timestamp * 1000).toISOString() : null,
 468 |         unreadCount: chat.unreadCount,
 469 |       }));
 470 | 
 471 |       res.status(200).json({
 472 |         status: 'success',
 473 |         chats: formattedChats,
 474 |       });
 475 |     } catch (error) {
 476 |       logger.error('[WA] Error getting chats:', error);
 477 |       res.status(500).json({
 478 |         status: 'error',
 479 |         message: 'Failed to get chats',
 480 |         error: error instanceof Error ? error.message : String(error),
 481 |       });
 482 |     }
 483 |   });
 484 | 
 485 |   // API endpoint to get messages from a specific chat
 486 |   app.get('/api/chats/:chatId/messages', async (req: Request, res: Response) => {
 487 |     try {
 488 |       const { chatId } = req.params;
 489 |       const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
 490 | 
 491 |       if (!state.clientReady) {
 492 |         return res.status(503).json({
 493 |           status: 'error',
 494 |           message: 'WhatsApp client not ready',
 495 |           whatsappStatus: state.clientReady ? 'ready' : state.whatsappError ? 'error' : 'initializing',
 496 |         });
 497 |       }
 498 | 
 499 |       const whatsappClient = state.client;
 500 |       if (!whatsappClient) {
 501 |         return res.status(500).json({
 502 |           status: 'error',
 503 |           message: 'WhatsApp client not available',
 504 |         });
 505 |       }
 506 | 
 507 |       logger.info(`[WA] Getting messages for chat ${chatId} (limit: ${limit})`);
 508 |       
 509 |       // Get the chat by ID
 510 |       const chat = await whatsappClient.getChatById(chatId);
 511 |       if (!chat) {
 512 |         return res.status(404).json({
 513 |           status: 'error',
 514 |           message: `Chat with ID ${chatId} not found`,
 515 |         });
 516 |       }
 517 | 
 518 |       // Fetch messages
 519 |       const messages = await chat.fetchMessages({ limit });
 520 |       
 521 |       // Format messages in a more API-friendly format
 522 |       const formattedMessages = messages.map(msg => ({
 523 |         id: msg.id._serialized,
 524 |         body: msg.body,
 525 |         type: msg.type,
 526 |         timestamp: msg.timestamp ? new Date(msg.timestamp * 1000).toISOString() : null,
 527 |         from: msg.from,
 528 |         fromMe: msg.fromMe,
 529 |         hasMedia: msg.hasMedia,
 530 |       }));
 531 | 
 532 |       res.status(200).json({
 533 |         status: 'success',
 534 |         chatId: chatId,
 535 |         messages: formattedMessages,
 536 |       });
 537 |     } catch (error) {
 538 |       logger.error(`[WA] Error getting messages for chat:`, error);
 539 |       res.status(500).json({
 540 |         status: 'error',
 541 |         message: 'Failed to get messages',
 542 |         error: error instanceof Error ? error.message : String(error),
 543 |       });
 544 |     }
 545 |   });
 546 | 
 547 |   // ===================================================
 548 |   // REST API ENDPOINTS THAT MAP TO ALL MCP TOOLS
 549 |   // ===================================================
 550 | 
 551 |   // Utility function to check if WhatsApp client is ready
 552 |   const ensureClientReady = (res: Response) => {
 553 |     if (!state.clientReady) {
 554 |       res.status(503).json({
 555 |         status: 'error',
 556 |         message: 'WhatsApp client not ready',
 557 |         whatsappStatus: state.clientReady ? 'ready' : state.whatsappError ? 'error' : 'initializing',
 558 |       });
 559 |       return false;
 560 |     }
 561 |     return true;
 562 |   };
 563 | 
 564 |   // 1. GET STATUS ENDPOINT - Maps to get_status tool
 565 |   app.get('/api/status', (_req: Request, res: Response) => {
 566 |     try {
 567 |       const whatsappStatus = state.clientReady 
 568 |         ? 'ready' 
 569 |         : state.whatsappError 
 570 |           ? 'error' 
 571 |           : state.whatsappInitializing 
 572 |             ? 'initializing' 
 573 |             : 'not_started';
 574 |       
 575 |       res.status(200).json({
 576 |         status: 'success',
 577 |         whatsappStatus: whatsappStatus,
 578 |         uptime: state.environment.uptime(),
 579 |         startTime: serverStartTime.toISOString(),
 580 |         error: state.whatsappError ? state.whatsappError.message : null,
 581 |       });
 582 |     } catch (error) {
 583 |       logger.error('[WA] Error in status endpoint:', error);
 584 |       res.status(500).json({
 585 |         status: 'error',
 586 |         message: 'Failed to get status',
 587 |         error: error instanceof Error ? error.message : String(error),
 588 |       });
 589 |     }
 590 |   });
 591 | 
 592 |   // 2. SEARCH CONTACTS ENDPOINT - Maps to search_contacts tool
 593 |   app.get('/api/contacts/search', async (req: Request, res: Response) => {
 594 |     try {
 595 |       if (!ensureClientReady(res)) return;
 596 | 
 597 |       const query = req.query.query as string;
 598 |       if (!query) {
 599 |         return res.status(400).json({
 600 |           status: 'error',
 601 |           message: 'Missing query parameter',
 602 |         });
 603 |       }
 604 | 
 605 |       const whatsappClient = state.client;
 606 |       const contacts = await whatsappClient.getContacts();
 607 |       const filtered = contacts.filter(contact => {
 608 |         const name = contact.name || contact.pushname || '';
 609 |         const number = contact.number || contact.id?.user || '';
 610 |         return name.toLowerCase().includes(query.toLowerCase()) || number.includes(query);
 611 |       }).map(contact => ({
 612 |         id: contact.id._serialized,
 613 |         name: contact.name || contact.pushname || 'Unknown',
 614 |         number: contact.number || contact.id?.user || 'Unknown',
 615 |         type: contact.isGroup ? 'group' : 'individual',
 616 |       }));
 617 | 
 618 |       res.status(200).json({
 619 |         status: 'success',
 620 |         query: query,
 621 |         contacts: filtered,
 622 |       });
 623 |     } catch (error) {
 624 |       logger.error('[WA] Error searching contacts:', error);
 625 |       res.status(500).json({
 626 |         status: 'error',
 627 |         message: 'Failed to search contacts',
 628 |         error: error instanceof Error ? error.message : String(error),
 629 |       });
 630 |     }
 631 |   });
 632 | 
 633 |   // 3. GET MESSAGES ENDPOINT - Maps to get_messages tool
 634 |   app.get('/api/chats/:chatId/messages', async (req: Request, res: Response) => {
 635 |     try {
 636 |       if (!ensureClientReady(res)) return;
 637 | 
 638 |       const { chatId } = req.params;
 639 |       const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
 640 | 
 641 |       const whatsappClient = state.client;
 642 |       
 643 |       // Get the chat by ID
 644 |       const chat = await whatsappClient.getChatById(chatId);
 645 |       if (!chat) {
 646 |         return res.status(404).json({
 647 |           status: 'error',
 648 |           message: `Chat with ID ${chatId} not found`,
 649 |         });
 650 |       }
 651 | 
 652 |       // Fetch messages
 653 |       const messages = await chat.fetchMessages({ limit });
 654 |       
 655 |       // Format messages in a more API-friendly format
 656 |       const formattedMessages = messages.map(msg => ({
 657 |         id: msg.id._serialized,
 658 |         body: msg.body,
 659 |         type: msg.type,
 660 |         timestamp: msg.timestamp ? new Date(msg.timestamp * 1000).toISOString() : null,
 661 |         from: msg.from,
 662 |         fromMe: msg.fromMe,
 663 |         hasMedia: msg.hasMedia,
 664 |       }));
 665 | 
 666 |       res.status(200).json({
 667 |         status: 'success',
 668 |         chatId: chatId,
 669 |         messages: formattedMessages,
 670 |       });
 671 |     } catch (error) {
 672 |       logger.error(`[WA] Error getting messages for chat:`, error);
 673 |       res.status(500).json({
 674 |         status: 'error',
 675 |         message: 'Failed to get messages',
 676 |         error: error instanceof Error ? error.message : String(error),
 677 |       });
 678 |     }
 679 |   });
 680 | 
 681 |   // 4. GET CHATS ENDPOINT - Maps to get_chats tool
 682 |   app.get('/api/chats', async (_req: Request, res: Response) => {
 683 |     try {
 684 |       if (!ensureClientReady(res)) return;
 685 | 
 686 |       const whatsappClient = state.client;
 687 |       const chats = await whatsappClient.getChats();
 688 |       
 689 |       // Format the chats in a more API-friendly format
 690 |       const formattedChats = chats.map(chat => ({
 691 |         id: chat.id._serialized,
 692 |         name: chat.name,
 693 |         isGroup: chat.isGroup,
 694 |         timestamp: chat.timestamp ? new Date(chat.timestamp * 1000).toISOString() : null,
 695 |         unreadCount: chat.unreadCount,
 696 |       }));
 697 | 
 698 |       res.status(200).json({
 699 |         status: 'success',
 700 |         chats: formattedChats,
 701 |       });
 702 |     } catch (error) {
 703 |       logger.error('[WA] Error getting chats:', error);
 704 |       res.status(500).json({
 705 |         status: 'error',
 706 |         message: 'Failed to get chats',
 707 |         error: error instanceof Error ? error.message : String(error),
 708 |       });
 709 |     }
 710 |   });
 711 | 
 712 |   // 5. SEND MESSAGE ENDPOINT - Maps to send_message tool
 713 |   app.post('/api/chats/:chatId/messages', async (req: Request, res: Response) => {
 714 |     try {
 715 |       if (!ensureClientReady(res)) return;
 716 | 
 717 |       const { chatId } = req.params;
 718 |       const { message } = req.body;
 719 | 
 720 |       if (!message) {
 721 |         return res.status(400).json({
 722 |           status: 'error',
 723 |           message: 'Missing message in request body',
 724 |         });
 725 |       }
 726 | 
 727 |       const whatsappClient = state.client;
 728 |       
 729 |       // Get the chat by ID
 730 |       const chat = await whatsappClient.getChatById(chatId);
 731 |       if (!chat) {
 732 |         return res.status(404).json({
 733 |           status: 'error',
 734 |           message: `Chat with ID ${chatId} not found`,
 735 |         });
 736 |       }
 737 | 
 738 |       // Send the message
 739 |       const sentMessage = await chat.sendMessage(message);
 740 |       
 741 |       res.status(200).json({
 742 |         status: 'success',
 743 |         chatId: chatId,
 744 |         messageId: sentMessage.id._serialized,
 745 |         timestamp: new Date().toISOString(),
 746 |       });
 747 |     } catch (error) {
 748 |       logger.error(`[WA] Error sending message to chat:`, error);
 749 |       res.status(500).json({
 750 |         status: 'error',
 751 |         message: 'Failed to send message',
 752 |         error: error instanceof Error ? error.message : String(error),
 753 |       });
 754 |     }
 755 |   });
 756 | 
 757 |   // 6. GET GROUPS ENDPOINT - Maps to groups resource
 758 |   app.get('/api/groups', async (_req: Request, res: Response) => {
 759 |     try {
 760 |       if (!ensureClientReady(res)) return;
 761 | 
 762 |       const whatsappClient = state.client;
 763 |       const chats = await whatsappClient.getChats();
 764 |       const groups = chats.filter(chat => chat.isGroup).map(group => ({
 765 |         id: group.id._serialized,
 766 |         name: group.name,
 767 |         participants: group.participants?.map(p => ({
 768 |           id: p.id._serialized,
 769 |           isAdmin: p.isAdmin || false,
 770 |         })) || [],
 771 |         timestamp: group.timestamp ? new Date(group.timestamp * 1000).toISOString() : null,
 772 |       }));
 773 | 
 774 |       res.status(200).json({
 775 |         status: 'success',
 776 |         groups: groups,
 777 |       });
 778 |     } catch (error) {
 779 |       logger.error('[WA] Error getting groups:', error);
 780 |       res.status(500).json({
 781 |         status: 'error',
 782 |         message: 'Failed to get groups',
 783 |         error: error instanceof Error ? error.message : String(error),
 784 |       });
 785 |     }
 786 |   });
 787 | 
 788 |   // 7. SEARCH GROUPS ENDPOINT - Maps to search_groups resource
 789 |   app.get('/api/groups/search', async (req: Request, res: Response) => {
 790 |     try {
 791 |       if (!ensureClientReady(res)) return;
 792 | 
 793 |       const query = req.query.query as string;
 794 |       if (!query) {
 795 |         return res.status(400).json({
 796 |           status: 'error',
 797 |           message: 'Missing query parameter',
 798 |         });
 799 |       }
 800 | 
 801 |       const whatsappClient = state.client;
 802 |       const chats = await whatsappClient.getChats();
 803 |       const groups = chats.filter(chat => {
 804 |         return chat.isGroup && chat.name.toLowerCase().includes(query.toLowerCase());
 805 |       }).map(group => ({
 806 |         id: group.id._serialized,
 807 |         name: group.name,
 808 |         participants: group.participants?.length || 0,
 809 |         timestamp: group.timestamp ? new Date(group.timestamp * 1000).toISOString() : null,
 810 |       }));
 811 | 
 812 |       res.status(200).json({
 813 |         status: 'success',
 814 |         query: query,
 815 |         groups: groups,
 816 |       });
 817 |     } catch (error) {
 818 |       logger.error('[WA] Error searching groups:', error);
 819 |       res.status(500).json({
 820 |         status: 'error',
 821 |         message: 'Failed to search groups',
 822 |         error: error instanceof Error ? error.message : String(error),
 823 |       });
 824 |     }
 825 |   });
 826 | 
 827 |   // 8. GET GROUP MESSAGES ENDPOINT - Maps to group_messages resource
 828 |   app.get('/api/groups/:groupId/messages', async (req: Request, res: Response) => {
 829 |     try {
 830 |       if (!ensureClientReady(res)) return;
 831 | 
 832 |       const { groupId } = req.params;
 833 |       const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
 834 | 
 835 |       const whatsappClient = state.client;
 836 |       
 837 |       // Get the group chat by ID
 838 |       const chat = await whatsappClient.getChatById(groupId);
 839 |       if (!chat || !chat.isGroup) {
 840 |         return res.status(404).json({
 841 |           status: 'error',
 842 |           message: `Group with ID ${groupId} not found`,
 843 |         });
 844 |       }
 845 | 
 846 |       // Fetch messages
 847 |       const messages = await chat.fetchMessages({ limit });
 848 |       
 849 |       // Format messages
 850 |       const formattedMessages = messages.map(msg => ({
 851 |         id: msg.id._serialized,
 852 |         body: msg.body,
 853 |         type: msg.type,
 854 |         timestamp: msg.timestamp ? new Date(msg.timestamp * 1000).toISOString() : null,
 855 |         author: msg.author || msg.from,
 856 |         fromMe: msg.fromMe,
 857 |         hasMedia: msg.hasMedia,
 858 |       }));
 859 | 
 860 |       res.status(200).json({
 861 |         status: 'success',
 862 |         groupId: groupId,
 863 |         messages: formattedMessages,
 864 |       });
 865 |     } catch (error) {
 866 |       logger.error(`[WA] Error getting messages for group:`, error);
 867 |       res.status(500).json({
 868 |         status: 'error',
 869 |         message: 'Failed to get group messages',
 870 |         error: error instanceof Error ? error.message : String(error),
 871 |       });
 872 |     }
 873 |   });
 874 | 
 875 |   // 9. CREATE GROUP ENDPOINT - Maps to create_group tool
 876 |   app.post('/api/groups', async (req: Request, res: Response) => {
 877 |     try {
 878 |       if (!ensureClientReady(res)) return;
 879 | 
 880 |       const { name, participants } = req.body;
 881 | 
 882 |       if (!name || !participants || !Array.isArray(participants)) {
 883 |         return res.status(400).json({
 884 |           status: 'error',
 885 |           message: 'Missing name or participants array in request body',
 886 |         });
 887 |       }
 888 | 
 889 |       const whatsappClient = state.client;
 890 |       const result = await whatsappClient.createGroup(name, participants);
 891 | 
 892 |       res.status(200).json({
 893 |         status: 'success',
 894 |         group: {
 895 |           id: result.gid._serialized,
 896 |           name: name,
 897 |           participants: participants,
 898 |         },
 899 |       });
 900 |     } catch (error) {
 901 |       logger.error('[WA] Error creating group:', error);
 902 |       res.status(500).json({
 903 |         status: 'error',
 904 |         message: 'Failed to create group',
 905 |         error: error instanceof Error ? error.message : String(error),
 906 |       });
 907 |     }
 908 |   });
 909 | 
 910 |   // 10. ADD PARTICIPANTS TO GROUP ENDPOINT - Maps to add_participants_to_group tool
 911 |   app.post('/api/groups/:groupId/participants', async (req: Request, res: Response) => {
 912 |     try {
 913 |       if (!ensureClientReady(res)) return;
 914 | 
 915 |       const { groupId } = req.params;
 916 |       const { participants } = req.body;
 917 | 
 918 |       if (!participants || !Array.isArray(participants)) {
 919 |         return res.status(400).json({
 920 |           status: 'error',
 921 |           message: 'Missing participants array in request body',
 922 |         });
 923 |       }
 924 | 
 925 |       const whatsappClient = state.client;
 926 |       
 927 |       // Get the group chat by ID
 928 |       const chat = await whatsappClient.getChatById(groupId);
 929 |       if (!chat || !chat.isGroup) {
 930 |         return res.status(404).json({
 931 |           status: 'error',
 932 |           message: `Group with ID ${groupId} not found`,
 933 |         });
 934 |       }
 935 | 
 936 |       // Add participants
 937 |       const result = await chat.addParticipants(participants);
 938 | 
 939 |       res.status(200).json({
 940 |         status: 'success',
 941 |         groupId: groupId,
 942 |         added: result,
 943 |       });
 944 |     } catch (error) {
 945 |       logger.error(`[WA] Error adding participants to group:`, error);
 946 |       res.status(500).json({
 947 |         status: 'error',
 948 |         message: 'Failed to add participants to group',
 949 |         error: error instanceof Error ? error.message : String(error),
 950 |       });
 951 |     }
 952 |   });
 953 | 
 954 |   // Add environment variables endpoint for troubleshooting
 955 |   app.get('/container-env', (_req: Request, res: Response) => {
 956 |     try {
 957 |       // Don't log or expose sensitive values
 958 |       const sanitizedEnv = Object.fromEntries(
 959 |         Object.entries(process.env)
 960 |           .filter(
 961 |             ([key]) =>
 962 |               !key.toLowerCase().includes('key') &&
 963 |               !key.toLowerCase().includes('token') &&
 964 |               !key.toLowerCase().includes('secret') &&
 965 |               !key.toLowerCase().includes('pass') &&
 966 |               !key.toLowerCase().includes('auth'),
 967 |           )
 968 |           .map(([key, value]) => [key, value]),
 969 |       );
 970 | 
 971 |       const envData = {
 972 |         nodeVersion: process.version,
 973 |         platform: process.platform,
 974 |         arch: process.arch,
 975 |         containerVars: {
 976 |           PORT: process.env.PORT,
 977 |           NODE_ENV: process.env.NODE_ENV,
 978 |           DOCKER_CONTAINER: process.env.DOCKER_CONTAINER,
 979 |           RENDER: process.env.RENDER,
 980 |         },
 981 |         // Include sanitized env for debugging only
 982 |         fullEnv: sanitizedEnv,
 983 |         timestamp: new Date().toISOString(),
 984 |       };
 985 | 
 986 |       logger.info('[WA] Container environment report');
 987 |       res.status(200).json(envData);
 988 |     } catch (error) {
 989 |       logger.error('[WA] Error in container-env endpoint:', error);
 990 |       res.status(500).send('Error getting container environment');
 991 |     }
 992 |   });
 993 | 
 994 |   // Add file system exploration endpoint for troubleshooting
 995 |   app.get('/filesys', (_req: Request, res: Response) => {
 996 |     try {
 997 |       const directoriesToCheck = [
 998 |         '/',
 999 |         '/app',
1000 |         '/app/data',
1001 |         '/app/data/whatsapp',
1002 |         '/var',
1003 |         '/var/data',
1004 |         '/var/data/whatsapp',
1005 |         '/tmp',
1006 |         '/tmp/puppeteer_data',
1007 |       ];
1008 | 
1009 |       const fsData = directoriesToCheck.map(dir => {
1010 |         try {
1011 |           const exists = fs.existsSync(dir);
1012 |           let files: string[] = [];
1013 |           let stats = null;
1014 | 
1015 |           if (exists) {
1016 |             try {
1017 |               stats = fs.statSync(dir);
1018 |               files = fs.readdirSync(dir).slice(0, 20); // Only get first 20 files
1019 |             } catch (e) {
1020 |               files = [`Error reading directory: ${e instanceof Error ? e.message : String(e)}`];
1021 |             }
1022 |           }
1023 | 
1024 |           return {
1025 |             directory: dir,
1026 |             exists,
1027 |             stats: stats
1028 |               ? {
1029 |                   isDirectory: stats.isDirectory(),
1030 |                   size: stats.size,
1031 |                   mode: stats.mode,
1032 |                   uid: stats.uid,
1033 |                   gid: stats.gid,
1034 |                 }
1035 |               : null,
1036 |             files,
1037 |           };
1038 |         } catch (e) {
1039 |           return {
1040 |             directory: dir,
1041 |             error: e instanceof Error ? e.message : String(e),
1042 |           };
1043 |         }
1044 |       });
1045 | 
1046 |       logger.info('[WA] File system exploration report');
1047 |       res.status(200).json(fsData);
1048 |     } catch (error) {
1049 |       logger.error('[WA] Error in filesys endpoint:', error);
1050 |       res.status(500).send('Error exploring file system');
1051 |     }
1052 |   });
1053 | 
1054 |   // Add start WhatsApp endpoint - separated from server start
1055 |   app.get('/start-whatsapp', (_req: Request, res: Response) => {
1056 |     // Only start once
1057 |     if (state.whatsappInitStarted) {
1058 |       return res.status(200).json({
1059 |         status: 'WhatsApp initialization already started',
1060 |         clientReady: state.clientReady,
1061 |         error: state.whatsappError ? state.whatsappError.message : null,
1062 |       });
1063 |     }
1064 | 
1065 |     // Start WhatsApp initialization
1066 |     state.whatsappInitStarted = true;
1067 |     state.whatsappInitializing = true;
1068 | 
1069 |     // Launch initialization in the background
1070 |     initializeWhatsAppClient(whatsAppConfig, state);
1071 | 
1072 |     return res.status(200).json({
1073 |       status: 'WhatsApp initialization started',
1074 |       message: 'Check /status for updates',
1075 |     });
1076 |   });
1077 | 
1078 |   // Start server IMMEDIATELY - BEFORE client initialization
1079 |   // This is CRITICAL to prevent Render deployment failures
1080 |   const serverPort = port || parseInt(process.env.PORT || '') || 3000;
1081 |   logger.info(`[WA] Starting HTTP server on port ${serverPort}`);
1082 | 
1083 |   const server = app.listen(serverPort, '0.0.0.0', () => {
1084 |     logger.info(`[WA] WhatsApp Web Client API server started on port ${serverPort}`);
1085 |   });
1086 | 
1087 |   // Set additional error handlers for process
1088 |   process.on('uncaughtException', error => {
1089 |     logger.error('[WA] Uncaught exception:', error);
1090 |     // Don't crash the server
1091 |   });
1092 | 
1093 |   process.on('unhandledRejection', reason => {
1094 |     logger.error('[WA] Unhandled rejection:', reason);
1095 |     // Don't crash the server
1096 |   });
1097 | 
1098 |   // Keep the process running
1099 |   process.on('SIGINT', async () => {
1100 |     logger.info('[WA] Shutting down WhatsApp Web Client API...');
1101 |     server.close();
1102 |     process.exit(0);
1103 |   });
1104 | }
1105 | 
1106 | // Separate function to initialize WhatsApp client
1107 | async function initializeWhatsAppClient(whatsAppConfig: WhatsAppConfig, state: any): Promise<void> {
1108 |   let client: Client | null = null;
1109 | 
1110 |   try {
1111 |     logger.info('[WA] Starting WhatsApp client initialization...');
1112 | 
1113 |     // Create the client
1114 |     client = createWhatsAppClient(whatsAppConfig);
1115 | 
1116 |     // Capture the QR code
1117 |     client.on('qr', qr => {
1118 |       logger.info('[WA] New QR code received');
1119 |       state.latestQrCode = qr;
1120 |       // QR code file saving is handled in whatsapp-client.ts
1121 | 
1122 |       // Also log QR code to console for terminal access
1123 |       try {
1124 |         // Use a smaller QR code with proper formatting
1125 |         logger.info('[WA] Scan this QR code with your WhatsApp app:');
1126 |         const qrcodeTerminal = require('qrcode-terminal');
1127 |         qrcodeTerminal.generate(qr, { small: true }, function (qrcode: string) {
1128 |           // Split the QR code by lines and log each line separately to preserve formatting
1129 |           const qrLines = qrcode.split('\n');
1130 |           qrLines.forEach((line: string) => {
1131 |             logger.info(`[WA-QR] ${line}`);
1132 |           });
1133 |         });
1134 |       } catch (error) {
1135 |         logger.error('[WA] Failed to generate terminal QR code', error);
1136 |       }
1137 |     });
1138 | 
1139 |     client.on('ready', () => {
1140 |       state.clientReady = true;
1141 |       state.whatsappInitializing = false;
1142 |       logger.info('[WA] Client is ready');
1143 |     });
1144 | 
1145 |     client.on('auth_failure', error => {
1146 |       state.whatsappError = new Error(`Authentication failed: ${error}`);
1147 |       logger.error('[WA] Authentication failed:', error);
1148 |     });
1149 | 
1150 |     client.on('disconnected', reason => {
1151 |       logger.warn('[WA] Client disconnected:', reason);
1152 |       state.clientReady = false;
1153 |     });
1154 | 
1155 |     await client.initialize();
1156 |   } catch (error) {
1157 |     state.whatsappInitializing = false;
1158 |     state.whatsappError = error as Error;
1159 |     logger.error('[WA] Error during client initialization:', error);
1160 |     // Don't throw here - we want the server to keep running even if WhatsApp fails
1161 |   }
1162 | }
1163 | 
1164 | async function startMcpServer(
1165 |   mcpConfig: McpConfig,
1166 |   transport: string,
1167 |   port: number,
1168 |   mode: string,
1169 | ): Promise<void> {
1170 |   let client: Client | null = null;
1171 |   if (mode === 'standalone') {
1172 |     logger.info('Starting WhatsApp Web Client...');
1173 |     client = createWhatsAppClient(mcpConfig.whatsappConfig);
1174 |     await client.initialize();
1175 |   }
1176 | 
1177 |   logger.info(`Starting MCP server in ${mode} mode...`);
1178 |   logger.debug('MCP Configuration:', mcpConfig);
1179 | 
1180 |   const server = createMcpServer(mcpConfig, client);
1181 | 
1182 |   if (transport === 'sse') {
1183 |     await startMcpSseServer(server, port, mode);
1184 |   } else if (transport === 'command') {
1185 |     await startMcpCommandServer(server, mode);
1186 |   }
1187 | }
1188 | 
1189 | async function main(): Promise<void> {
1190 |   try {
1191 |     const argv = parseCommandLineArgs();
1192 |     configureLogger(argv);
1193 | 
1194 |     const { whatsAppConfig, mcpConfig } = createConfigurations(argv);
1195 | 
1196 |     if (argv.mode === 'mcp') {
1197 |       await startMcpServer(
1198 |         mcpConfig,
1199 |         argv['transport'] as string,
1200 |         argv['sse-port'] as number,
1201 |         argv['mcp-mode'] as string,
1202 |       );
1203 |     } else if (argv.mode === 'whatsapp-api') {
1204 |       await startWhatsAppApiServer(whatsAppConfig, argv['api-port'] as number);
1205 |     }
1206 |   } catch (error) {
1207 |     logger.error('Error starting application:', error);
1208 |     process.exit(1);
1209 |   }
1210 | }
1211 | 
1212 | main();
1213 | 
```
Page 2/2FirstPrevNextLast