Skip to main content
Glama
ananddtyagi

Webpage Screenshot MCP Server

screenshot-page

Capture screenshots of any webpage as base64 encoded images with customizable settings like viewport size, image format, and loading conditions. Supports using saved cookies for authenticated pages.

Instructions

Captures a screenshot of a given URL and returns it as base64 encoded image. Can use saved cookies from login-and-wait.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
delayNoAdditional delay in milliseconds to wait after page load
formatNoImage format for the screenshotpng
fullPageNoWhether to capture the full page or just the viewport
heightNoViewport height in pixels
qualityNoQuality of the image (0-100), only applicable for jpeg and webp
reuseAuthPageNoWhether to use the existing authenticated page instead of creating a new one
urlYesThe URL of the webpage to screenshot
useDefaultBrowserNoWhether to use the system's default browser instead of Puppeteer's bundled Chromium
useSavedAuthNoWhether to use saved cookies from previous login
visibleBrowserNoWhether to show the browser window (non-headless mode)
waitForNoWhen to consider the page loadednetworkidle2
widthNoViewport width in pixels

Implementation Reference

  • The handler function that implements the core logic for capturing screenshots of web pages using Puppeteer. Supports authentication via saved cookies, full-page screenshots, custom dimensions, formats, delays, browser visibility, and default system browser usage.
    async ({ url, fullPage, width, height, format, quality, waitFor, delay, useSavedAuth, reuseAuthPage, useDefaultBrowser, visibleBrowser }) => {
        let page: Page | null = null;
        let shouldClosePage = true;
        
        try {
            // Initialize browser with appropriate options
            const isHeadless = !visibleBrowser;
            const browserInstance = await initBrowser(isHeadless, useDefaultBrowser && visibleBrowser);
            
            // Check if we should reuse the authenticated page
            if (reuseAuthPage && persistentPage && !persistentPage.isClosed()) {
                page = persistentPage;
                shouldClosePage = false;
                
                // Navigate to the new URL if different
                const currentUrl = page.url();
                if (currentUrl !== url) {
                    await page.goto(url, {
                        waitUntil: waitFor as any,
                        timeout: 30000
                    });
                }
            } else {
                // Create a new page
                page = await browserInstance.newPage();
                
                // Load saved cookies if requested
                if (useSavedAuth) {
                    const cookies = await loadCookies(url);
                    if (cookies.length > 0) {
                        await page.setCookie(...cookies);
                    }
                }
                
                // Set viewport
                await page.setViewport({ width, height });
                
                // Set user agent to avoid bot detection
                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
                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: waitFor as any,
                    timeout: 30000
                });
            }
            
            // Optional delay
            if (delay > 0) {
                await new Promise(resolve => setTimeout(resolve, delay));
            }
            
            // Prepare screenshot options
            const screenshotOptions: any = {
                encoding: 'base64',
                fullPage,
                type: format
            };
            
            // Add quality option for jpeg and webp
            if ((format === 'jpeg' || format === 'webp') && quality !== undefined) {
                screenshotOptions.quality = quality;
            }
            
            // Take screenshot
            const screenshot = await page.screenshot(screenshotOptions) as string;
            
            // Get page title and final URL for context
            const pageTitle = await page.title();
            const finalUrl = page.url();
            
            // If using a new page, save any new cookies
            if (!reuseAuthPage && useSavedAuth) {
                const currentCookies = await page.cookies();
                if (currentCookies.length > 0) {
                    await saveCookies(url, currentCookies);
                }
            }
            
            // Determine browser type for response
            const browserType = useDefaultBrowser && visibleBrowser ? 'default browser' : 'Puppeteer browser';
            const browserMode = visibleBrowser ? 'visible' : 'headless';
            
            return {
                content: [
                    {
                        type: "text",
                        text: `Screenshot captured successfully!\n\nBrowser: ${browserType} (${browserMode})\nPage Title: ${pageTitle}\nFinal URL: ${finalUrl}\nFormat: ${format}\nDimensions: ${width}x${height}\nFull Page: ${fullPage}\nUsed saved auth: ${useSavedAuth}\nReused auth page: ${reuseAuthPage}`
                    },
                    {
                        type: "image",
                        data: screenshot,
                        mimeType: `image/${format}`
                    }
                ],
            };
        } catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                isError: true,
                content: [
                    {
                        type: "text",
                        text: `Error capturing screenshot: ${errorMessage}`,
                    },
                ],
            };
        } finally {
            // Only close the page if it's not the persistent one or if we should close it
            if (page && shouldClosePage && page !== persistentPage) {
                await page.close().catch(() => {});
            }
        }
    }
  • Zod schema defining the input parameters for the screenshot-page tool, including URL, screenshot options, authentication flags, and browser configuration.
    {
        url: z.string().url().describe("The URL of the webpage to screenshot"),
        fullPage: z.boolean().optional().default(true).describe("Whether to capture the full page or just the viewport"),
        width: z.number().optional().default(1920).describe("Viewport width in pixels"),
        height: z.number().optional().default(1080).describe("Viewport height in pixels"),
        format: z.enum(['png', 'jpeg', 'webp']).optional().default('png').describe("Image format for the screenshot"),
        quality: z.number().min(0).max(100).optional().describe("Quality of the image (0-100), only applicable for jpeg and webp"),
        waitFor: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().default('networkidle2').describe("When to consider the page loaded"),
        delay: z.number().optional().default(0).describe("Additional delay in milliseconds to wait after page load"),
        useSavedAuth: z.boolean().optional().default(true).describe("Whether to use saved cookies from previous login"),
        reuseAuthPage: z.boolean().optional().default(false).describe("Whether to use the existing authenticated page instead of creating a new one"),
        useDefaultBrowser: z.boolean().optional().default(false).describe("Whether to use the system's default browser instead of Puppeteer's bundled Chromium"),
        visibleBrowser: z.boolean().optional().default(false).describe("Whether to show the browser window (non-headless mode)")
    },
  • src/index.ts:496-651 (registration)
    Registration of the screenshot-page tool on the MCP server, including name, description, input schema, and handler function.
    // Updated screenshot-page tool with authentication support
    server.tool(
        "screenshot-page",
        "Captures a screenshot of a given URL and returns it as base64 encoded image. Can use saved cookies from login-and-wait.",
        {
            url: z.string().url().describe("The URL of the webpage to screenshot"),
            fullPage: z.boolean().optional().default(true).describe("Whether to capture the full page or just the viewport"),
            width: z.number().optional().default(1920).describe("Viewport width in pixels"),
            height: z.number().optional().default(1080).describe("Viewport height in pixels"),
            format: z.enum(['png', 'jpeg', 'webp']).optional().default('png').describe("Image format for the screenshot"),
            quality: z.number().min(0).max(100).optional().describe("Quality of the image (0-100), only applicable for jpeg and webp"),
            waitFor: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().default('networkidle2').describe("When to consider the page loaded"),
            delay: z.number().optional().default(0).describe("Additional delay in milliseconds to wait after page load"),
            useSavedAuth: z.boolean().optional().default(true).describe("Whether to use saved cookies from previous login"),
            reuseAuthPage: z.boolean().optional().default(false).describe("Whether to use the existing authenticated page instead of creating a new one"),
            useDefaultBrowser: z.boolean().optional().default(false).describe("Whether to use the system's default browser instead of Puppeteer's bundled Chromium"),
            visibleBrowser: z.boolean().optional().default(false).describe("Whether to show the browser window (non-headless mode)")
        },
        async ({ url, fullPage, width, height, format, quality, waitFor, delay, useSavedAuth, reuseAuthPage, useDefaultBrowser, visibleBrowser }) => {
            let page: Page | null = null;
            let shouldClosePage = true;
            
            try {
                // Initialize browser with appropriate options
                const isHeadless = !visibleBrowser;
                const browserInstance = await initBrowser(isHeadless, useDefaultBrowser && visibleBrowser);
                
                // Check if we should reuse the authenticated page
                if (reuseAuthPage && persistentPage && !persistentPage.isClosed()) {
                    page = persistentPage;
                    shouldClosePage = false;
                    
                    // Navigate to the new URL if different
                    const currentUrl = page.url();
                    if (currentUrl !== url) {
                        await page.goto(url, {
                            waitUntil: waitFor as any,
                            timeout: 30000
                        });
                    }
                } else {
                    // Create a new page
                    page = await browserInstance.newPage();
                    
                    // Load saved cookies if requested
                    if (useSavedAuth) {
                        const cookies = await loadCookies(url);
                        if (cookies.length > 0) {
                            await page.setCookie(...cookies);
                        }
                    }
                    
                    // Set viewport
                    await page.setViewport({ width, height });
                    
                    // Set user agent to avoid bot detection
                    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
                    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: waitFor as any,
                        timeout: 30000
                    });
                }
                
                // Optional delay
                if (delay > 0) {
                    await new Promise(resolve => setTimeout(resolve, delay));
                }
                
                // Prepare screenshot options
                const screenshotOptions: any = {
                    encoding: 'base64',
                    fullPage,
                    type: format
                };
                
                // Add quality option for jpeg and webp
                if ((format === 'jpeg' || format === 'webp') && quality !== undefined) {
                    screenshotOptions.quality = quality;
                }
                
                // Take screenshot
                const screenshot = await page.screenshot(screenshotOptions) as string;
                
                // Get page title and final URL for context
                const pageTitle = await page.title();
                const finalUrl = page.url();
                
                // If using a new page, save any new cookies
                if (!reuseAuthPage && useSavedAuth) {
                    const currentCookies = await page.cookies();
                    if (currentCookies.length > 0) {
                        await saveCookies(url, currentCookies);
                    }
                }
                
                // Determine browser type for response
                const browserType = useDefaultBrowser && visibleBrowser ? 'default browser' : 'Puppeteer browser';
                const browserMode = visibleBrowser ? 'visible' : 'headless';
                
                return {
                    content: [
                        {
                            type: "text",
                            text: `Screenshot captured successfully!\n\nBrowser: ${browserType} (${browserMode})\nPage Title: ${pageTitle}\nFinal URL: ${finalUrl}\nFormat: ${format}\nDimensions: ${width}x${height}\nFull Page: ${fullPage}\nUsed saved auth: ${useSavedAuth}\nReused auth page: ${reuseAuthPage}`
                        },
                        {
                            type: "image",
                            data: screenshot,
                            mimeType: `image/${format}`
                        }
                    ],
                };
            } catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
                return {
                    isError: true,
                    content: [
                        {
                            type: "text",
                            text: `Error capturing screenshot: ${errorMessage}`,
                        },
                    ],
                };
            } finally {
                // Only close the page if it's not the persistent one or if we should close it
                if (page && shouldClosePage && page !== persistentPage) {
                    await page.close().catch(() => {});
                }
            }
        }
    );
  • Helper function to load authentication cookies for a given URL/domain from persistent storage, used by screenshot-page for auth support.
    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 [];
        }
    }
  • Helper function to save cookies for a domain after page load, enabling persistent authentication across tool calls.
    async function saveCookies(url: string, cookies: Cookie[]) {
        await ensureCookiesDir();
        const domain = getDomainFromUrl(url);
        const cookiesPath = path.join(cookiesDir, `${domain}.json`);
        await fsPromises.writeFile(cookiesPath, JSON.stringify(cookies, null, 2));
    }
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 mentions the ability to use saved cookies, which hints at authentication behavior, but doesn't cover other important traits like performance implications (e.g., page load delays), potential failures (e.g., invalid URLs), or side effects (e.g., browser resource usage). The description adds some value but leaves significant gaps for a tool with 12 parameters.

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, well-structured sentence that efficiently conveys the core functionality and a key feature (cookie reuse). Every word earns its place with no redundancy or fluff, making it appropriately sized and front-loaded.

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 the tool's complexity (12 parameters, no annotations, no output schema), the description is incomplete. It covers the basic purpose and authentication context but lacks details on behavioral traits, error handling, or output specifics (beyond base64 encoding). For a screenshot tool with many configuration options, more guidance on usage scenarios or limitations would be helpful.

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 already documents all 12 parameters thoroughly. The description adds minimal semantic context by mentioning 'saved cookies from login-and-wait,' which loosely relates to the 'useSavedAuth' parameter, but doesn't provide additional meaning beyond what the schema specifies for most parameters. Baseline 3 is appropriate when the schema does 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 ('captures a screenshot') and resource ('of a given URL'), and distinguishes from sibling tools by mentioning the ability to use saved cookies from 'login-and-wait' (differentiating from 'screenshot-element' which targets specific elements). It also specifies the output format ('returns it as base64 encoded image').

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 provides clear context by mentioning saved cookies from 'login-and-wait', which implies when to use this tool (for authenticated pages). However, it doesn't explicitly state when NOT to use it or name alternatives like 'screenshot-element' for element-specific captures, leaving some guidance implicit rather than explicit.

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