Skip to main content
Glama
auth-app.jsโ€ข11.4 kB
#!/usr/bin/env node import http from 'http'; import url from 'url'; import { spawn } from 'child_process'; import { WhoopApiClient } from './dist/whoop-api.js'; const PORT = 3000; // Load environment variables import dotenv from 'dotenv'; dotenv.config(); const WHOOP_CONFIG = { clientId: process.env.WHOOP_CLIENT_ID || 'your_client_id_here', clientSecret: process.env.WHOOP_CLIENT_SECRET || 'your_client_secret_here', redirectUri: process.env.WHOOP_REDIRECT_URI || 'http://localhost:3000/callback' }; const whoopClient = new WhoopApiClient(WHOOP_CONFIG); // Store the authorization code for exchange let authCode = null; let accessToken = null; let refreshToken = null; // Generate a secure random state parameter function generateState() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } // Store the state for verification let currentState = null; // Function to open browser function openBrowser(url) { const platform = process.platform; let command; switch (platform) { case 'darwin': command = 'open'; break; case 'win32': command = 'start'; break; default: command = 'xdg-open'; } const child = spawn(command, [url], { stdio: 'ignore' }); child.on('error', (err) => { console.log('โŒ Could not open browser automatically'); console.log(`๐Ÿ”— Please visit this URL manually: ${url}`); }); } // Function to exchange code for token async function exchangeCodeForToken(code) { try { console.log('๐Ÿ”„ Exchanging authorization code for access token...'); const tokenResponse = await whoopClient.exchangeCodeForToken(code); console.log('๐Ÿ“‹ Raw token response:', JSON.stringify(tokenResponse, null, 2)); accessToken = tokenResponse.access_token; refreshToken = tokenResponse.refresh_token; console.log('โœ… Authentication successful!'); if (accessToken) { console.log('๐Ÿ“‹ Access Token:', accessToken.substring(0, 20) + '...'); } else { console.log('โŒ No access token received'); } if (refreshToken) { console.log('๐Ÿ”„ Refresh Token:', refreshToken.substring(0, 20) + '...'); } else { console.log('โŒ No refresh token received'); } console.log('โฐ Expires in:', tokenResponse.expires_in, 'seconds'); return tokenResponse; } catch (error) { console.error('โŒ Failed to exchange code for token:', error.message); console.error('โŒ Full error:', error); throw error; } } // Function to test the access token async function testAccessToken() { if (!accessToken) { console.log('โŒ No access token available'); return; } try { console.log('๐Ÿงช Testing access token...'); whoopClient.setAccessToken(accessToken); const userProfile = await whoopClient.getUserProfile(); console.log('โœ… Access token is valid!'); console.log('๐Ÿ‘ค User:', userProfile.first_name, userProfile.last_name); console.log('๐Ÿ“ง Email:', userProfile.email); return userProfile; } catch (error) { console.log('โŒ Access token test failed:', error.message); throw error; } } // Function to save tokens to file async function saveTokens() { const fs = await import('fs'); const tokenData = { accessToken, refreshToken, timestamp: new Date().toISOString() }; try { fs.default.writeFileSync('whoop-tokens.json', JSON.stringify(tokenData, null, 2)); console.log('๐Ÿ’พ Tokens saved to whoop-tokens.json'); } catch (error) { console.error('โŒ Failed to save tokens:', error.message); } } // Create HTTP server const server = http.createServer(async (req, res) => { const parsedUrl = url.parse(req.url, true); if (parsedUrl.pathname === '/') { // Main page - initiate OAuth flow currentState = generateState(); const authUrl = whoopClient.getAuthorizationUrl() + `&state=${currentState}`; res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head> <title>WHOOP Authentication</title> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; } .container { background: #f9f9f9; padding: 30px; border-radius: 10px; } .button { background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block; margin: 10px 0; } .status { padding: 10px; border-radius: 5px; margin: 10px 0; } .success { background: #d4edda; color: #155724; } .error { background: #f8d7da; color: #721c24; } .info { background: #d1ecf1; color: #0c5460; } </style> </head> <body> <div class="container"> <h1>๐Ÿ” WHOOP Authentication</h1> <p>Click the button below to authenticate with WHOOP and access your fitness data.</p> <a href="${authUrl}" class="button">๐Ÿ”— Connect to WHOOP</a> <div class="status info"> <strong>Status:</strong> Ready to authenticate </div> <p><small>This will open WHOOP's authorization page in your browser.</small></p> </div> </body> </html> `); // Automatically open the browser console.log('๐ŸŒ Opening WHOOP authorization page...'); openBrowser(authUrl); } else if (parsedUrl.pathname === '/callback') { const code = parsedUrl.query.code; const error = parsedUrl.query.error; const returnedState = parsedUrl.query.state; // Verify state parameter for security if (returnedState !== currentState) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head><title>WHOOP OAuth Error</title></head> <body> <div class="container"> <h1>โŒ Security Error</h1> <div class="status error"> <strong>Error:</strong> State parameter mismatch<br> <strong>Description:</strong> This could be a security issue. Please try again. </div> <p>Please try the authentication process again.</p> </div> </body> </html> `); console.error('โŒ State parameter mismatch - possible security issue'); return; } if (error) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head><title>WHOOP OAuth Error</title></head> <body> <div class="container"> <h1>โŒ OAuth Error</h1> <div class="status error"> <strong>Error:</strong> ${error}<br> <strong>Description:</strong> ${parsedUrl.query.error_description || 'No description'} </div> <p>Please try again or check your WHOOP credentials.</p> </div> </body> </html> `); console.error('โŒ OAuth error:', error); } else if (code) { authCode = code; try { // Exchange code for token const tokenResponse = await exchangeCodeForToken(code); // Test the token const userProfile = await testAccessToken(); // Save tokens await saveTokens(); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head> <title>WHOOP Authentication Success</title> <style> body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; } .container { background: #f9f9f9; padding: 30px; border-radius: 10px; } .status { padding: 10px; border-radius: 5px; margin: 10px 0; } .success { background: #d4edda; color: #155724; } .token { background: #f8f9fa; padding: 10px; border-radius: 5px; font-family: monospace; word-break: break-all; font-size: 12px; } </style> </head> <body> <div class="container"> <h1>โœ… Authentication Successful!</h1> <div class="status success"> <strong>Welcome, ${userProfile.first_name} ${userProfile.last_name}!</strong> </div> <h3>๐Ÿ”‘ Access Token:</h3> <div class="token">${accessToken}</div> <h3>๐Ÿ”„ Refresh Token:</h3> <div class="token">${refreshToken || 'No refresh token provided'}</div> <p><strong>๐ŸŽ‰ You can now use the WHOOP MCP server with Claude!</strong></p> <p>Your tokens have been saved to <code>whoop-tokens.json</code> and are ready to use.</p> <p><small>You can close this window. The authentication is complete.</small></p> </div> </body> </html> `); console.log('\n๐ŸŽ‰ Authentication completed successfully!'); console.log('๐Ÿ“‹ You can now use the WHOOP MCP server with Claude.'); console.log('๐Ÿ’พ Tokens saved to whoop-tokens.json'); // Optionally shut down the server after successful auth setTimeout(() => { console.log('\n๐Ÿ‘‹ Shutting down authentication server...'); server.close(() => { console.log('โœ… Authentication server stopped'); process.exit(0); }); }, 5000); } catch (error) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head><title>WHOOP Authentication Error</title></head> <body> <div class="container"> <h1>โŒ Authentication Failed</h1> <div class="status error"> <strong>Error:</strong> ${error.message} </div> <p>Please try again or check your WHOOP credentials.</p> </div> </body> </html> `); console.error('โŒ Authentication failed:', error.message); } } else { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` <html> <head><title>WHOOP OAuth</title></head> <body> <div class="container"> <h1>WHOOP OAuth Callback</h1> <p>No authorization code or error received.</p> <p>Please try the authentication process again.</p> </div> </body> </html> `); } } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); } }); // Start the server server.listen(PORT, () => { console.log('๐Ÿš€ WHOOP Authentication Server'); console.log('๐Ÿ“ Running on http://localhost:3000'); console.log('๐Ÿ”— Visit the URL above to start authentication'); console.log('๐Ÿ“‹ The browser will open automatically...'); console.log(''); }); // Handle graceful shutdown process.on('SIGINT', () => { console.log('\n๐Ÿ‘‹ Shutting down authentication server...'); server.close(() => { console.log('โœ… Authentication server stopped'); process.exit(0); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nissand/whoop-mcp-server-claude'

If you have feedback or need assistance with the MCP directory API, please join our Discord server