login-and-wait
Opens a webpage for manual login, waits for user authentication, and saves cookies for session continuity. Ideal for automating login processes in web applications.
Instructions
Opens a webpage in a visible browser window for manual login, waits for user to complete login, then saves cookies
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| successIndicator | No | Optional CSS selector or URL pattern that indicates successful login | |
| url | Yes | The URL of the login page | |
| useDefaultBrowser | No | Whether to use the system's default browser instead of Puppeteer's bundled Chromium | |
| waitMinutes | No | Maximum minutes to wait for login (default: 3) |
Implementation Reference
- src/index.ts:342-493 (handler)Main execution logic for the 'login-and-wait' tool: initializes visible browser, loads existing cookies, applies anti-detection, navigates to login URL, waits for completion via indicator/ navigation/ signal file/ timeout, saves cookies, keeps page open.async ({ url, waitMinutes, successIndicator, useDefaultBrowser }) => { let page: Page | null = null; try { // Initialize browser in non-headless mode with default browser option const browserInstance = await initBrowser(false, useDefaultBrowser); // Create or reuse persistent page if (!persistentPage || persistentPage.isClosed()) { persistentPage = await browserInstance.newPage(); } page = persistentPage; // Load existing cookies if available const existingCookies = await loadCookies(url); if (existingCookies.length > 0) { await page.setCookie(...existingCookies); } // Set user agent and anti-detection measures for login 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'); // Additional anti-detection measures for Google login await page.evaluateOnNewDocument(() => { // Remove webdriver property delete (window.navigator as any).webdriver; // Override the plugins property to add fake plugins Object.defineProperty(window.navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); // Override the languages property Object.defineProperty(window.navigator, 'languages', { get: () => ['en-US', 'en'] }); // Override permissions Object.defineProperty(window.navigator, 'permissions', { get: () => ({ query: () => Promise.resolve({ state: 'granted' }) }) }); }); // Navigate to the URL await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); const startTime = Date.now(); const maxWaitTime = waitMinutes * 60 * 1000; // Wait for login console.error(`Waiting for manual login... (up to ${waitMinutes} minutes)`); console.error(`Please complete the login in the ${useDefaultBrowser ? 'default' : 'Puppeteer'} browser window.`); console.error(`To continue immediately after login, use the 'signal-login-complete' tool or navigate away from the login page.`); if (successIndicator) { try { // If it's a URL pattern if (successIndicator.startsWith('http') || successIndicator.includes('/')) { await page.waitForFunction( (pattern) => window.location.href.includes(pattern), { timeout: maxWaitTime }, successIndicator ); } else { // Otherwise treat as CSS selector await page.waitForSelector(successIndicator, { timeout: maxWaitTime }); } } catch (timeoutError) { // Continue even if indicator not found console.error('Success indicator not found, but continuing...'); } } else { // Wait for user confirmation via multiple methods await new Promise((resolve) => { const checkInterval = setInterval(() => { if (Date.now() - startTime > maxWaitTime) { clearInterval(checkInterval); resolve(null); } }, 1000); // Method 1: Page navigation detection page?.on('framenavigated', () => { const currentUrl = page?.url() || ''; // Check if we've navigated away from login pages if (!currentUrl.includes('accounts.google.com') && !currentUrl.includes('login') && !currentUrl.includes('signin') && !currentUrl.includes('auth')) { setTimeout(() => { clearInterval(checkInterval); resolve(null); }, 2000); } }); // Method 2: Check for a completion marker file const completionFile = path.join(os.tmpdir(), 'mcp-login-complete.txt'); const fileCheckInterval = setInterval(async () => { try { if (fs.existsSync(completionFile)) { await fsPromises.unlink(completionFile).catch(() => {}); clearInterval(checkInterval); clearInterval(fileCheckInterval); resolve(null); } } catch (e) { // Ignore file check errors } }, 1000); // Clean up file checker when main interval ends setTimeout(() => { clearInterval(fileCheckInterval); }, maxWaitTime); }); } // Save cookies after login const cookies = await page.cookies(); await saveCookies(url, cookies); const finalUrl = page.url(); const browserType = useDefaultBrowser ? 'default browser' : 'Puppeteer browser'; return { content: [ { type: "text", text: `Login session established and cookies saved!\n\nBrowser: ${browserType}\nInitial URL: ${url}\nFinal URL: ${finalUrl}\nCookies saved: ${cookies.length}\n\nLogin completed via: ${successIndicator ? 'success indicator detected' : 'automatic navigation detection or manual signal'}\n\nThe browser window will remain open for future screenshots.` } ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [ { type: "text", text: `Error during login process: ${errorMessage}`, }, ], }; } // Don't close the page - keep it for future use }
- src/index.ts:336-341 (schema)Zod schema defining input parameters for the login-and-wait tool.{ url: z.string().url().describe("The URL of the login page"), waitMinutes: z.number().optional().default(3).describe("Maximum minutes to wait for login (default: 3)"), successIndicator: z.string().optional().describe("Optional CSS selector or URL pattern that indicates successful login"), useDefaultBrowser: z.boolean().optional().default(true).describe("Whether to use the system's default browser instead of Puppeteer's bundled Chromium") },
- src/index.ts:332-495 (registration)Registration of the 'login-and-wait' tool using server.tool() with name, description, schema, and handler.// Register the login-and-wait tool server.tool( "login-and-wait", "Opens a webpage in a visible browser window for manual login, waits for user to complete login, then saves cookies", { url: z.string().url().describe("The URL of the login page"), waitMinutes: z.number().optional().default(3).describe("Maximum minutes to wait for login (default: 3)"), successIndicator: z.string().optional().describe("Optional CSS selector or URL pattern that indicates successful login"), useDefaultBrowser: z.boolean().optional().default(true).describe("Whether to use the system's default browser instead of Puppeteer's bundled Chromium") }, async ({ url, waitMinutes, successIndicator, useDefaultBrowser }) => { let page: Page | null = null; try { // Initialize browser in non-headless mode with default browser option const browserInstance = await initBrowser(false, useDefaultBrowser); // Create or reuse persistent page if (!persistentPage || persistentPage.isClosed()) { persistentPage = await browserInstance.newPage(); } page = persistentPage; // Load existing cookies if available const existingCookies = await loadCookies(url); if (existingCookies.length > 0) { await page.setCookie(...existingCookies); } // Set user agent and anti-detection measures for login 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'); // Additional anti-detection measures for Google login await page.evaluateOnNewDocument(() => { // Remove webdriver property delete (window.navigator as any).webdriver; // Override the plugins property to add fake plugins Object.defineProperty(window.navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); // Override the languages property Object.defineProperty(window.navigator, 'languages', { get: () => ['en-US', 'en'] }); // Override permissions Object.defineProperty(window.navigator, 'permissions', { get: () => ({ query: () => Promise.resolve({ state: 'granted' }) }) }); }); // Navigate to the URL await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 }); const startTime = Date.now(); const maxWaitTime = waitMinutes * 60 * 1000; // Wait for login console.error(`Waiting for manual login... (up to ${waitMinutes} minutes)`); console.error(`Please complete the login in the ${useDefaultBrowser ? 'default' : 'Puppeteer'} browser window.`); console.error(`To continue immediately after login, use the 'signal-login-complete' tool or navigate away from the login page.`); if (successIndicator) { try { // If it's a URL pattern if (successIndicator.startsWith('http') || successIndicator.includes('/')) { await page.waitForFunction( (pattern) => window.location.href.includes(pattern), { timeout: maxWaitTime }, successIndicator ); } else { // Otherwise treat as CSS selector await page.waitForSelector(successIndicator, { timeout: maxWaitTime }); } } catch (timeoutError) { // Continue even if indicator not found console.error('Success indicator not found, but continuing...'); } } else { // Wait for user confirmation via multiple methods await new Promise((resolve) => { const checkInterval = setInterval(() => { if (Date.now() - startTime > maxWaitTime) { clearInterval(checkInterval); resolve(null); } }, 1000); // Method 1: Page navigation detection page?.on('framenavigated', () => { const currentUrl = page?.url() || ''; // Check if we've navigated away from login pages if (!currentUrl.includes('accounts.google.com') && !currentUrl.includes('login') && !currentUrl.includes('signin') && !currentUrl.includes('auth')) { setTimeout(() => { clearInterval(checkInterval); resolve(null); }, 2000); } }); // Method 2: Check for a completion marker file const completionFile = path.join(os.tmpdir(), 'mcp-login-complete.txt'); const fileCheckInterval = setInterval(async () => { try { if (fs.existsSync(completionFile)) { await fsPromises.unlink(completionFile).catch(() => {}); clearInterval(checkInterval); clearInterval(fileCheckInterval); resolve(null); } } catch (e) { // Ignore file check errors } }, 1000); // Clean up file checker when main interval ends setTimeout(() => { clearInterval(fileCheckInterval); }, maxWaitTime); }); } // Save cookies after login const cookies = await page.cookies(); await saveCookies(url, cookies); const finalUrl = page.url(); const browserType = useDefaultBrowser ? 'default browser' : 'Puppeteer browser'; return { content: [ { type: "text", text: `Login session established and cookies saved!\n\nBrowser: ${browserType}\nInitial URL: ${url}\nFinal URL: ${finalUrl}\nCookies saved: ${cookies.length}\n\nLogin completed via: ${successIndicator ? 'success indicator detected' : 'automatic navigation detection or manual signal'}\n\nThe browser window will remain open for future screenshots.` } ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [ { type: "text", text: `Error during login process: ${errorMessage}`, }, ], }; } // Don't close the page - keep it for future use } );
- src/index.ts:654-684 (helper)'signal-login-complete' helper tool that creates a temp file to signal the login-and-wait tool to continue after manual login.server.tool( "signal-login-complete", "Signals that manual login is complete and the login-and-wait tool should continue", {}, async () => { try { const completionFile = path.join(os.tmpdir(), 'mcp-login-complete.txt'); await fsPromises.writeFile(completionFile, 'complete'); return { content: [ { type: "text", text: "Login completion signal sent! The login-and-wait tool should continue shortly." } ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { isError: true, content: [ { type: "text", text: `Error signaling login completion: ${errorMessage}`, }, ], }; } } );
- src/index.ts:273-281 (helper)loadCookies helper function used by login-and-wait to load existing cookies for the domain.async function loadCookies(url: string): Promise<Cookie[]> { try { const domain = getDomainFromUrl(url); const cookiesPath = path.join(cookiesDir, `${domain}.json`); const cookiesData = await fsPromises.readFile(cookiesPath, 'utf-8'); return JSON.parse(cookiesData); } catch { return []; }