#
tokens: 10838/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .claude
│   └── commands
│       └── claude-desktop-extension.md
├── .gitignore
├── LICENSE
├── manifest.json
├── package-lock.json
├── package.json
├── README.md
├── screenshot-webpage-mcp.dxt
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/

# Build output
dist/
build/
*.tsbuildinfo

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids/
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov/

# Coverage directory used by tools like istanbul
coverage/

# nyc test coverage
.nyc_output/

# Dependency directories
.npm/
.eslintcache

# Optional npm cache directory
.npm/

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE and editor files
.idea/
.vscode/
*.swp
*.swo
.DS_Store
.AppleDouble
.LSOverride

# User-specific files
npm-debug.log
yarn-error.log
.DS_Store

# Project specific
.mcp-screenshot-cookies/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Webpage Screenshot MCP Server

An MCP (Model Context Protocol) server that captures screenshots of web pages using Puppeteer. This server allows AI agents to visually verify web applications and see their progress when generating web apps.

![Screen Recording May 27 2025 (2)](https://github.com/user-attachments/assets/9f186ec4-5a5c-449b-9a30-a5ec0cdba695)


## Features

- **Full page screenshots**: Capture entire web pages or just the viewport
- **Element screenshots**: Target specific elements using CSS selectors
- **Multiple formats**: Support for PNG, JPEG, and WebP formats
- **Customizable options**: Set viewport size, image quality, wait conditions, and delays
- **Base64 encoding**: Returns screenshots as base64 encoded images for easy integration
- **Authentication support**: Manual login and cookie persistence
- **Default browser integration**: Use your system's default browser for a more natural experience
- **Session persistence**: Keep browser sessions open for multi-step workflows

## Installation

### Quick Start (Claude Desktop Extension)

Drag and drop the generated `screenshot-webpage-mcp.dxt` file into Claude Desktop for automatic installation!

### Manual Installation

To install and build the MCP from source:

```bash
# Clone the repository (if you haven't already)
git clone https://github.com/ananddtyagi/webpage-screenshot-mcp.git
cd webpage-screenshot-mcp

# Install dependencies
npm install

# Build the project
npm run build
```

The MCP server is built using TypeScript and compiled to JavaScript. The `dist` folder contains the compiled JavaScript files. 

### Adding to Claude or Cursor

To add this MCP to Claude Desktop or Cursor:

1. **Claude Desktop**:
   - Go to Settings > Developer
   - Click "Edit Config"
   - Add the following:

   ```json
    "webpage-screenshot": {
      "command": "node",
      "args": [
        "~/path/to/webpage-screenshot-mcp/dist/index.js"
      ]
    }
   ```
   - Save and reload Claude

2. **Cursor**:
   - Open Cursor and go to Cursor Settings > MCP
   - Click "Add new global MCP server"
   - Add the following:
  
  ```json
    "webpage-screenshot": {
      "command": "node",
      "args": ["~/path/to/webpage-screenshot-mcp/dist/index.js"]
    }
   ```

   - Save and reload Cursor

## Usage

### Tools

This MCP server provides several tools:

#### 1. login-and-wait

Opens a webpage in a visible browser window for manual login, waits for user to complete login, then saves cookies.

```json
{
  "url": "https://example.com/login",
  "waitMinutes": 5,
  "successIndicator": ".dashboard-welcome",
  "useDefaultBrowser": true
}
```

- `url` (required): The URL of the login page
- `waitMinutes` (optional): Maximum minutes to wait for login (default: 5)
- `successIndicator` (optional): CSS selector or URL pattern that indicates successful login
- `useDefaultBrowser` (optional): Whether to use the system's default browser (default: true)

#### 2. screenshot-page

Captures a screenshot of a given URL and returns it as base64 encoded image.

```json
{
  "url": "https://example.com/dashboard",
  "fullPage": true,
  "width": 1920,
  "height": 1080,
  "format": "png",
  "quality": 80,
  "waitFor": "networkidle2",
  "delay": 500,
  "useSavedAuth": true,
  "reuseAuthPage": true,
  "useDefaultBrowser": true,
  "visibleBrowser": true
}
```

- `url` (required): The URL of the webpage to screenshot
- `fullPage` (optional): Whether to capture the full page or just the viewport (default: true)
- `width` (optional): Viewport width in pixels (default: 1920)
- `height` (optional): Viewport height in pixels (default: 1080)
- `format` (optional): Image format - "png", "jpeg", or "webp" (default: "png")
- `quality` (optional): Quality of the image (0-100), only applicable for jpeg and webp
- `waitFor` (optional): When to consider page loaded - "load", "domcontentloaded", "networkidle0", or "networkidle2" (default: "networkidle2")
- `delay` (optional): Additional delay in milliseconds after page load (default: 0)
- `useSavedAuth` (optional): Whether to use saved cookies from previous login (default: true)
- `reuseAuthPage` (optional): Whether to use the existing authenticated page (default: false)
- `useDefaultBrowser` (optional): Whether to use the system's default browser (default: false)
- `visibleBrowser` (optional): Whether to show the browser window (default: false)

#### 3. screenshot-element

Captures a screenshot of a specific element on a webpage using a CSS selector.

```json
{
  "url": "https://example.com/dashboard",
  "selector": ".user-profile",
  "waitForSelector": true,
  "format": "png",
  "quality": 80,
  "padding": 10,
  "useSavedAuth": true,
  "useDefaultBrowser": true,
  "visibleBrowser": true
}
```

- `url` (required): The URL of the webpage
- `selector` (required): CSS selector for the element to screenshot
- `waitForSelector` (optional): Whether to wait for the selector to appear (default: true)
- `format` (optional): Image format - "png", "jpeg", or "webp" (default: "png")
- `quality` (optional): Quality of the image (0-100), only applicable for jpeg and webp
- `padding` (optional): Padding around the element in pixels (default: 0)
- `useSavedAuth` (optional): Whether to use saved cookies from previous login (default: true)
- `useDefaultBrowser` (optional): Whether to use the system's default browser (default: false)
- `visibleBrowser` (optional): Whether to show the browser window (default: false)

#### 4. clear-auth-cookies

Clears saved authentication cookies for a specific domain or all domains.

```json
{
  "url": "https://example.com"
}
```

- `url` (optional): URL of the domain to clear cookies for. If not provided, clears all cookies.

## Default Browser Mode

The default browser mode allows you to use your system's regular browser (Chrome, Edge, etc.) instead of Puppeteer's bundled Chromium. This is useful for:

1. Using your existing browser sessions and extensions
2. Manually logging in to websites with your saved credentials
3. Having a more natural browsing experience for multi-step workflows
4. Testing with the same browser environment as your users

To enable default browser mode, set `useDefaultBrowser: true` and `visibleBrowser: true` in your tool parameters.

### How Default Browser Mode Works

When you enable default browser mode:

1. The tool will attempt to locate your system's default browser (Chrome, Edge, etc.)
2. It launches your browser with remote debugging enabled on a random port
3. Puppeteer connects to this browser instance instead of launching its own
4. Your existing profiles, extensions, and cookies are available during the session
5. The browser window remains visible so you can interact with it manually

This mode is particularly useful for workflows that require authentication or complex user interactions.

## Browser Persistence

The MCP server can maintain a persistent browser session across multiple tool calls:

1. When you use `login-and-wait`, the browser session is kept open
2. Subsequent calls to `screenshot-page` or `screenshot-element` with `reuseAuthPage: true` will use the same page
3. This allows for multi-step workflows without having to re-authenticate

## Cookie Management

Cookies are automatically saved for each domain you visit:

1. After using `login-and-wait`, cookies are saved to the `.mcp-screenshot-cookies` directory in your home folder
2. These cookies are automatically loaded when visiting the same domain again with `useSavedAuth: true`
3. You can clear cookies using the `clear-auth-cookies` tool

## Example Workflow: Protected Page Screenshots

Here's an example workflow for taking screenshots of pages that require authentication:

1. **Manual Login Phase**

```json
{
  "name": "login-and-wait",
  "parameters": {
    "url": "https://example.com/login",
    "waitMinutes": 3,
    "successIndicator": ".dashboard-welcome",
    "useDefaultBrowser": true
  }
}
```

This will open your default browser with the login page. You can manually log in, and once complete (either by detecting the success indicator or after navigating away from the login page), the session cookies will be saved.

2. **Take Screenshots Using Saved Session**

```json
{
  "name": "screenshot-page",
  "parameters": {
    "url": "https://example.com/account",
    "fullPage": true,
    "useSavedAuth": true,
    "reuseAuthPage": true,
    "useDefaultBrowser": true,
    "visibleBrowser": true
  }
}
```

This will take a screenshot of the account page using your saved authentication cookies in the same browser window.

3. **Take Screenshots of Specific Elements**

```json
{
  "name": "screenshot-element",
  "parameters": {
    "url": "https://example.com/dashboard",
    "selector": ".user-profile-section",
    "useSavedAuth": true,
    "useDefaultBrowser": true,
    "visibleBrowser": true
  }
}
```

4. **Clear Cookies When Done**

```json
{
  "name": "clear-auth-cookies",
  "parameters": {
    "url": "https://example.com"
  }
}
```

This workflow allows you to interact with protected pages as if you were a regular user, completing the full authentication flow in your default browser.

## Headless vs. Visible Mode

- **Headless mode** (`visibleBrowser: false`): Faster and more suitable for automated workflows where no user interaction is needed.
- **Visible mode** (`visibleBrowser: true`): Shows the browser window, allowing for user interaction and manual verification. Required for `useDefaultBrowser: true`.

## Platform Support

The default browser detection works on:

- **macOS**: Detects Chrome, Edge, and Safari
- **Windows**: Detects Chrome and Edge via registry or common installation paths
- **Linux**: Detects Chrome and Chromium via system commands

## Troubleshooting

### Common Issues

1. **Default browser not found**: If the system can't find your default browser, it will fall back to Puppeteer's bundled Chromium.
2. **Connection issues**: If there are problems connecting to the browser's debugging port, check if another instance is already using that port.
3. **Cookie issues**: If authentication isn't working, try clearing cookies with the `clear-auth-cookies` tool.

### Debugging

The MCP server logs helpful error messages to the console when issues occur. Check these messages for troubleshooting information.

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "webpage-screenshot-mcp",
  "version": "1.0.0",
  "description": "MCP server for capturing screenshots of web pages",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "keywords": [
    "mcp",
    "screenshot",
    "puppeteer",
    "web-scraping"
  ],
  "author": "Anand Tyagi",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "puppeteer": "24.9.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  },
  "files": [
    "dist/**/*",
    "README.md",
    "package.json"
  ]
}
```

--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------

```json
{
  "dxt_version": "0.1",
  "name": "webpage-screenshot-mcp",
  "display_name": "Webpage Screenshot MCP Server",
  "version": "1.0.0",
  "description": "MCP server for capturing screenshots of web pages using Puppeteer",
  "long_description": "This MCP server allows AI agents to capture screenshots of web pages, with support for full page captures, element screenshots, authentication handling, and multiple image formats.",
  "author": {
    "name": "Anand Tyagi"
  },
  "license": "MIT",
  "server": {
    "type": "node",
    "entry_point": "dist/index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/dist/index.js"]
    }
  },
  "tools": [
    {
      "name": "screenshot-page",
      "description": "Captures a screenshot of a given URL"
    },
    {
      "name": "screenshot-element",
      "description": "Captures a screenshot of a specific element on a webpage"
    },
    {
      "name": "login-and-wait",
      "description": "Opens a webpage for manual login and saves cookies"
    },
    {
      "name": "clear-auth-cookies",
      "description": "Clears saved authentication cookies"
    }
  ],
  "homepage": "https://github.com/ananddtyagi/webpage-screenshot-mcp",
  "keywords": ["mcp", "screenshot", "puppeteer", "web-scraping"]
}
```

--------------------------------------------------------------------------------
/.claude/commands/claude-desktop-extension.md:
--------------------------------------------------------------------------------

```markdown
I want to build this as a Desktop Extension, abbreviated as "DXT". Please follow these steps:

1. **Read the specifications thoroughly:**
   - https://github.com/anthropics/dxt/blob/main/README.md - DXT architecture overview, capabilities, and integration patterns
   - https://github.com/anthropics/dxt/blob/main/MANIFEST.md - Complete extension manifest structure and field definitions
   - https://github.com/anthropics/dxt/tree/main/examples - Reference implementations including a "Hello World" example

2. **Create a proper extension structure:**
   - Generate a valid manifest.json following the MANIFEST.md spec
   - Implement an MCP server using @modelcontextprotocol/sdk with proper tool definitions
   - Include proper error handling and timeout management

3. **Follow best development practices:**
   - Implement proper MCP protocol communication via stdio transport
   - Structure tools with clear schemas, validation, and consistent JSON responses
   - Make use of the fact that this extension will be running locally
   - Add appropriate logging and debugging capabilities
   - Include proper documentation and setup instructions

4. **Test considerations:**
   - Validate that all tool calls return properly structured responses
   - Verify manifest loads correctly and host integration works

Generate complete, production-ready code that can be immediately tested. Focus on defensive programming, clear error messages, and following the exact
DXT specifications to ensure compatibility with the ecosystem.
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { execSync, spawn } from 'child_process';
import fs, { promises as fsPromises } from 'fs';
import os from 'os';
import path from 'path';
import puppeteer, { Browser, Cookie, Page } from 'puppeteer';
import { z } from 'zod';

// Create the MCP server
const server = new McpServer({
    name: "screenshot-page",
    version: "1.0.0",
});

let browser: Browser | null = null;
let persistentPage: Page | null = null;
const cookiesDir = path.join(os.homedir(), '.mcp-screenshot-cookies');

// Ensure cookies directory exists
async function ensureCookiesDir() {
    try {
        await fsPromises.mkdir(cookiesDir, { recursive: true });
    } catch (error) {
        console.error('Error creating cookies directory:', error);
    }
}

// Get path to default Chrome/Edge installation
function getDefaultBrowserPath(): string | null {
    try {
        if (process.platform === 'darwin') {
            // Check for Chrome first
            try {
                return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
            } catch (e) {
                // Then check for Edge
                try {
                    return '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge';
                } catch (e) {
                    // Then Safari (though Puppeteer doesn't work well with Safari)
                    return '/Applications/Safari.app/Contents/MacOS/Safari';
                }
            }
        } else if (process.platform === 'win32') {
            // On Windows, try to find Chrome or Edge
            try {
                const chromePath = execSync('where chrome').toString().trim();
                if (chromePath) return chromePath;
            } catch (e) {
                try {
                    const edgePath = execSync('where msedge').toString().trim();
                    if (edgePath) return edgePath;
                } catch (e) {
                    // Fall back to default installation paths
                    const programFiles = process.env['PROGRAMFILES'] || 'C:\\Program Files';
                    const chromePath = `${programFiles}\\Google\\Chrome\\Application\\chrome.exe`;
                    const edgePath = `${programFiles}\\Microsoft\\Edge\\Application\\msedge.exe`;
                    
                    try {
                        if (fs.existsSync(chromePath)) return chromePath;
                        if (fs.existsSync(edgePath)) return edgePath;
                    } catch (e) {
                        // Ignore filesystem errors
                    }
                }
            }
        } else if (process.platform === 'linux') {
            // On Linux, try common browser paths
            try {
                const chromePath = execSync('which google-chrome').toString().trim();
                if (chromePath) return chromePath;
            } catch (e) {
                try {
                    const chromiumPath = execSync('which chromium-browser').toString().trim();
                    if (chromiumPath) return chromiumPath;
                } catch (e) {
                    // No default browser found
                }
            }
        }
    } catch (e) {
        console.error('Error finding default browser:', e);
    }
    return null;
}

// Initialize browser instance
async function initBrowser(headless: boolean = true, useDefaultBrowser: boolean = false): Promise<Browser> {
    if (browser) {
        // Check if we need to switch modes or browser type
        const isHeadless = browser.process()?.spawnargs?.includes('--headless') ?? true;
        const isUsingDefaultBrowser = browser.process()?.spawnargs?.includes('--remote-debugging-port') ?? false;
        
        if (isHeadless !== headless || isUsingDefaultBrowser !== useDefaultBrowser) {
            await browser.close();
            browser = null;
            persistentPage = null;
        }
    }
    
    if (!browser) {
        if (useDefaultBrowser && !headless) {
            // Try to connect to default browser
            const defaultBrowserPath = getDefaultBrowserPath();
            
            if (!defaultBrowserPath) {
                console.error('Could not find default browser. Falling back to bundled Chromium.');
                                    browser = await puppeteer.launch({
                        executablePath: defaultBrowserPath ?? undefined,
                        headless: headless,
                        args: [
                            '--no-sandbox',
                            '--disable-setuid-sandbox',
                            '--disable-dev-shm-usage',
                            '--disable-accelerated-2d-canvas',
                            '--no-first-run',
                            '--no-zygote',
                            '--disable-blink-features=AutomationControlled',
                            '--disable-features=VizDisplayCompositor',
                            '--disable-extensions-file-access-check',
                            '--disable-extensions-http-throttling',
                            '--disable-extensions-https-error-pages',
                            '--disable-extensions',
                            '--disable-background-timer-throttling',
                            '--disable-renderer-backgrounding',
                            '--disable-backgrounding-occluded-windows',
                            '--disable-ipc-flooding-protection',
                            '--disable-default-apps',
                            '--disable-sync',
                            '--disable-translate',
                            '--hide-scrollbars',
                            '--mute-audio',
                            '--no-default-browser-check',
                            '--no-pings',
                            '--disable-web-security',
                            '--disable-features=TranslateUI',
                            '--disable-features=BlinkGenPropertyTrees',
                            '--disable-client-side-phishing-detection',
                            '--disable-component-extensions-with-background-pages',
                            '--disable-default-apps',
                            '--disable-hang-monitor',
                            '--disable-prompt-on-repost',
                            headless ? '--disable-gpu' : ''
                        ].filter(Boolean)
                    });
            } else {
                // Use random debug port in allowed range (9222-9322)
                const debuggingPort = 9222 + Math.floor(Math.random() * 100);
                
                // Launch browser with debugging port
                const userDataDir = path.join(os.tmpdir(), `puppeteer_user_data_${Date.now()}`);
                
                // Launch browser process using spawn instead of execSync
                const browserProcess = spawn(
                    defaultBrowserPath,
                    [
                        `--remote-debugging-port=${debuggingPort}`,
                        `--user-data-dir=${userDataDir}`,
                        '--no-first-run',
                        'about:blank'
                    ],
                    { stdio: 'ignore', detached: true }
                );
                
                // Detach the process so it continues running after our process exits
                browserProcess.unref();
                
                // Wait for browser to start
                await new Promise(resolve => setTimeout(resolve, 1000));
                
                // Connect to the browser
                try {
                    browser = await puppeteer.connect({
                        browserURL: `http://localhost:${debuggingPort}`,
                        defaultViewport: null
                    });
                    
                    // Store user data dir for cleanup
                    (browser as any).__userDataDir = userDataDir;
                } catch (error) {
                    console.error('Failed to connect to browser:', error);
                    // Fall back to bundled browser
                    browser = await puppeteer.launch({
                        executablePath: defaultBrowserPath ?? undefined,
                        headless: headless,
                        args: [
                            '--no-sandbox',
                            '--disable-setuid-sandbox',
                            '--disable-dev-shm-usage',
                            '--disable-accelerated-2d-canvas',
                            '--no-first-run',
                            '--no-zygote',
                            '--disable-blink-features=AutomationControlled',
                            '--disable-features=VizDisplayCompositor',
                            '--disable-extensions',
                            '--disable-background-timer-throttling',
                            '--disable-renderer-backgrounding',
                            '--disable-backgrounding-occluded-windows',
                            '--disable-ipc-flooding-protection',
                            '--disable-default-apps',
                            '--disable-sync',
                            '--disable-translate',
                            '--hide-scrollbars',
                            '--mute-audio',
                            '--no-default-browser-check',
                            '--no-pings',
                            '--disable-web-security',
                            '--disable-features=TranslateUI',
                            '--disable-features=BlinkGenPropertyTrees',
                            '--disable-client-side-phishing-detection'
                        ].filter(Boolean)
                    });
                }
            }
        } else {
            // Use bundled browser
            browser = await puppeteer.launch({
                headless: headless,
                args: [
                    '--no-sandbox',
                    '--disable-setuid-sandbox',
                    '--disable-dev-shm-usage',
                    '--disable-accelerated-2d-canvas',
                    '--no-first-run',
                    '--no-zygote',
                    '--disable-blink-features=AutomationControlled',
                    '--disable-features=VizDisplayCompositor',
                    '--disable-extensions',
                    '--disable-background-timer-throttling',
                    '--disable-renderer-backgrounding',
                    '--disable-backgrounding-occluded-windows',
                    '--disable-ipc-flooding-protection',
                    '--disable-default-apps',
                    '--disable-sync',
                    '--disable-translate',
                    '--hide-scrollbars',
                    '--mute-audio',
                    '--no-default-browser-check',
                    '--no-pings',
                    '--disable-web-security',
                    '--disable-features=TranslateUI',
                    '--disable-features=BlinkGenPropertyTrees',
                    '--disable-client-side-phishing-detection',
                    headless ? '--disable-gpu' : ''
                ].filter(Boolean)
            });
        }
    }
    return browser;
}

// Get domain from URL for cookie storage
function getDomainFromUrl(url: string): string {
    try {
        const urlObj = new URL(url);
        return urlObj.hostname.replace(/\./g, '_');
    } catch {
        return 'unknown';
    }
}

// Save cookies for a domain
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));
}

// Load cookies for a 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 [];
    }
}

// Function to clean up resources
async function cleanupBrowser() {
    if (browser) {
        // Clean up user data directory if it exists (for default browser)
        const userDataDir = (browser as any).__userDataDir;
        
        try {
            await browser.close();
        } catch (error) {
            console.error('Error closing browser:', error);
        }
        
        // Clean up user data directory if it exists
        if (userDataDir) {
            try {
                await fsPromises.rm(userDataDir, { recursive: true, force: true });
            } catch (error) {
                console.error('Error cleaning up user data directory:', error);
            }
        }
        
        browser = null;
        persistentPage = null;
    }
}

// Cleanup browser on exit
process.on('exit', () => {
    if (browser) {
        // Can't use async here, so just do a sync cleanup of what we can
        try {
            browser.close().catch(() => {});
        } catch (e) {
            // Ignore errors on exit
        }
    }
});

process.on('SIGINT', async () => {
    await cleanupBrowser();
    process.exit(0);
});

process.on('SIGTERM', async () => {
    await cleanupBrowser();
    process.exit(0);
});

// 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
    }
);

// 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(() => {});
            }
        }
    }
);

// Tool to signal login completion
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}`,
                    },
                ],
            };
        }
    }
);

// Tool to clear saved cookies
server.tool(
    "clear-auth-cookies",
    "Clears saved authentication cookies for a specific domain or all domains",
    {
        url: z.string().url().optional().describe("URL of the domain to clear cookies for. If not provided, clears all cookies."),
    },
    async ({ url }) => {
        try {
            await ensureCookiesDir();
            
            if (url) {
                // Clear cookies for specific domain
                const domain = getDomainFromUrl(url);
                const cookiesPath = path.join(cookiesDir, `${domain}.json`);
                try {
                    await fsPromises.unlink(cookiesPath);
                    return {
                        content: [
                            {
                                type: "text",
                                text: `Cookies cleared for domain: ${domain}`
                            }
                        ],
                    };
                } catch {
                    return {
                        content: [
                            {
                                type: "text",
                                text: `No cookies found for domain: ${domain}`
                            }
                        ],
                    };
                }
            } else {
                // Clear all cookies
                const files = await fsPromises.readdir(cookiesDir);
                for (const file of files) {
                    if (file.endsWith('.json')) {
                        await fsPromises.unlink(path.join(cookiesDir, file));
                    }
                }
                return {
                    content: [
                        {
                            type: "text",
                            text: `All saved cookies cleared (${files.length} domains)`
                        }
                    ],
                };
            }
        } catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                isError: true,
                content: [
                    {
                        type: "text",
                        text: `Error clearing cookies: ${errorMessage}`,
                    },
                ],
            };
        }
    }
);

// Keep the screenshot-element tool as before, but add default browser support
server.tool(
    "screenshot-element",
    "Captures a screenshot of a specific element on a webpage using a CSS selector",
    {
        url: z.string().url().describe("The URL of the webpage"),
        selector: z.string().describe("CSS selector for the element to screenshot"),
        waitForSelector: z.boolean().optional().default(true).describe("Whether to wait for the selector to appear"),
        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"),
        padding: z.number().optional().default(0).describe("Padding around the element in pixels"),
        useSavedAuth: z.boolean().optional().default(true).describe("Whether to use saved cookies from previous login"),
        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, selector, waitForSelector, format, quality, padding, useSavedAuth, useDefaultBrowser, visibleBrowser }) => {
        let page: Page | null = null;
        
        try {
            // Initialize browser with appropriate options
            const isHeadless = !visibleBrowser;
            const browserInstance = await initBrowser(isHeadless, useDefaultBrowser && visibleBrowser);
            
            // 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 (matching screenshot-page tool)
            await page.setViewport({ width: 1920, height: 1080 });
            
            // 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: 'networkidle2',
                timeout: 30000
            });
            
            // Wait for the selector if requested
            if (waitForSelector) {
                await page.waitForSelector(selector, { timeout: 10000 });
            }
            
            // Get the element
            const element = await page.$(selector);
            
            if (!element) {
                return {
                    isError: true,
                    content: [
                        {
                            type: "text",
                            text: `Element not found with selector: ${selector}`,
                        },
                    ],
                };
            }
            
            // Add padding if requested
            if (padding > 0) {
                await page.evaluate((sel, pad) => {
                    const el = document.querySelector(sel);
                    if (el) {
                        (el as HTMLElement).style.padding = `${pad}px`;
                    }
                }, selector, padding);
            }
            
            // Prepare screenshot options
            const screenshotOptions: any = {
                encoding: 'base64',
                type: format
            };
            
            // Add quality option for jpeg and webp
            if ((format === 'jpeg' || format === 'webp') && quality !== undefined) {
                screenshotOptions.quality = quality;
            }
            
            // Take screenshot of the element
            const screenshot = await element.screenshot(screenshotOptions) as string;
            
            // Determine browser type for response
            const browserType = useDefaultBrowser && visibleBrowser ? 'default browser' : 'Puppeteer browser';
            const browserMode = visibleBrowser ? 'visible' : 'headless';
            
            return {
                content: [
                    {
                        type: "text",
                        text: `Element screenshot captured successfully!\n\nBrowser: ${browserType} (${browserMode})\nURL: ${url}\nSelector: ${selector}\nFormat: ${format}`
                    },
                    {
                        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 element screenshot: ${errorMessage}`,
                    },
                ],
            };
        } finally {
            // Close the page
            if (page) {
                await page.close().catch(() => {});
            }
        }
    }
);

// Run the server
async function main() {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error("Screenshot MCP Server running on stdio");
}

main().catch((error) => {
    console.error("Fatal error in main():", error);
    process.exit(1);
});
```