authenticate_garmin
Authenticate with Garmin Connect to enable workout creation and device synchronization through the Garmin Workouts MCP server.
Instructions
Authenticate with Garmin Connect (opens browser)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/mcp-server.ts:99-106 (registration)Registration of the 'authenticate_garmin' tool in the ListTools response, including name, description, and input schema (empty object).{ name: "authenticate_garmin", description: "Authenticate with Garmin Connect (opens browser)", inputSchema: { type: "object", properties: {}, }, },
- src/mcp-server.ts:222-256 (handler)MCP server tool handler for 'authenticate_garmin' that invokes GarminAuth.authenticate() and formats success/error responses.case "authenticate_garmin": { try { console.error("๐ Starting Garmin authentication..."); const authData = await garminAuth.authenticate(); if (authData) { const expiresAt = new Date(authData.expiresAt); return { content: [ { type: "text", text: `โ Successfully authenticated with Garmin Connect! You can now create workouts.\nToken expires: ${expiresAt.toLocaleString()}`, }, ], }; } else { return { content: [ { type: "text", text: "โ Authentication failed. Please ensure you can access Garmin Connect in your browser.", }, ], }; } } catch (error) { return { content: [ { type: "text", text: `โ Authentication error: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }
- src/garmin-auth.ts:44-152 (handler)Core GarminAuth.authenticate() method implementing the authentication logic: launches headless=false Puppeteer browser, navigates to Garmin Connect, waits for manual login, intercepts Authorization Bearer token, extracts cookies, parses JWT expiration, stores auth data.async authenticate(): Promise<AuthData | null> { console.error("๐ Starting Garmin authentication..."); const browser = await puppeteer.launch({ headless: false, args: [ "--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", "--disable-features=VizDisplayCompositor", ], }); let authData: AuthData | null = null; try { const page = await browser.newPage(); await page.setUserAgent( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ); await page.evaluateOnNewDocument(() => { delete (navigator as any).webdriver; (window as any).chrome = { runtime: {}, loadTimes: function () {}, csi: function () {}, app: {}, }; }); await page.setViewport({ width: 1366, height: 768 }); let authToken = ""; let cookies = ""; // Set up request interception to capture auth token await page.setRequestInterception(true); page.on("request", (request) => { const authHeader = request.headers()["authorization"]; if (authHeader && authHeader.startsWith("Bearer ") && !authToken) { authToken = authHeader; console.error("๐ฏ Captured auth token!"); } request.continue(); }); console.error("๐ Opening Garmin Connect login..."); console.error("๐ Please login manually in the browser"); // Go directly to workouts page (will redirect to login if needed) await page.goto("https://connect.garmin.com/modern/workouts", { waitUntil: "networkidle2", }); console.error("โณ Waiting for login completion..."); console.error("๐ก The page should redirect to login, then back to workouts"); // Wait for the workouts page to load properly (means we're logged in) await page.waitForFunction( () => { return ( window.location.href.includes("/modern/workouts") && !window.location.href.includes("sso.garmin.com") && document.querySelector('select[name="select-workout"]') !== null ); }, { timeout: 300000 } // 5 minutes for manual login ); // Extract cookies const pageCookies = await page.cookies(); cookies = pageCookies.map((c) => `${c.name}=${c.value}`).join("; "); // Make sure we have an auth token by triggering a request if (!authToken) { console.error("๐ Triggering request to capture auth token..."); await page.reload(); await new Promise((resolve) => setTimeout(resolve, 3000)); } if (authToken && cookies) { console.error("โ Authentication successful!"); // Parse token expiration const tokenPayload = this.parseJWT(authToken); authData = { authToken, cookies, expiresAt: tokenPayload.exp * 1000, // Convert to milliseconds issuedAt: tokenPayload.iat * 1000, }; // Store auth data this.storeAuth(authData); } else { console.error("โ Could not extract authentication data"); } } catch (error) { console.error("โ Authentication failed:", error); } finally { await browser.close(); } return authData; }
- src/garmin-auth.ts:6-11 (schema)Type definition for AuthData used in authentication storage and validation.interface AuthData { authToken: string; cookies: string; expiresAt: number; issuedAt: number; }
- src/mcp-server.ts:40-40 (helper)Instantiation of GarminAuth class used across tools for shared authentication state.const garminAuth = new GarminAuth();