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 [];
        }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes the tool's behavior well: opening a visible browser, waiting for manual login, and saving cookies. However, it misses details like error handling, what happens after timeout, or how cookies are saved/stored. It does not contradict annotations, as none exist.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose and steps. Every word earns its place, with no redundancy or unnecessary details, making it highly concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description adequately covers the tool's purpose and high-level behavior. However, for a tool with 4 parameters and no output schema, it lacks details on return values, error cases, or integration with sibling tools like 'signal-login-complete'. It's complete enough for basic understanding but has gaps for full contextual use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all parameters. The description does not add any parameter-specific information beyond what the schema provides (e.g., it doesn't explain 'successIndicator' usage or 'waitMinutes' implications). Baseline 3 is appropriate as the schema handles the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action sequence: 'Opens a webpage in a visible browser window for manual login, waits for user to complete login, then saves cookies.' It uses precise verbs (opens, waits, saves) and identifies the resource (webpage, cookies), distinguishing it from sibling tools like screenshot tools or cookie-clearing tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for manual login scenarios where user interaction is required, but it does not explicitly state when to use this tool versus alternatives like automated login tools or other authentication methods. It provides clear context (manual login in a browser) but lacks explicit exclusions or named alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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