Skip to main content
Glama
ananddtyagi

Webpage Screenshot MCP Server

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
NameRequiredDescriptionDefault
successIndicatorNoOptional CSS selector or URL pattern that indicates successful login
urlYesThe URL of the login page
useDefaultBrowserNoWhether to use the system's default browser instead of Puppeteer's bundled Chromium
waitMinutesNoMaximum minutes to wait for login (default: 3)

Implementation Reference

  • 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 }
  • 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 } );
  • '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}`, }, ], }; } } );
  • 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 []; }

Other Tools

Related Tools

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/ananddtyagi/webpage-screenshot-mcp'

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