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

```
├── .gitignore
├── .nvmrc
├── index.ts
├── LICENSE
├── package.json
├── README.md
├── sql.png
├── tsconfig.json
├── xss.png
└── yarn.lock
```

# Files

--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------

```
v20.16.0

```

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

```
node_modules
dist

```

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

```markdown
<h1 align="center">MCP Server Pentest</h1>


## Features

- Full browser xss, sql vulnerability automatic detection
- Screenshots of the entire page or specific elements
- Comprehensive network interaction (navigation, clicks, form filling)
- Console log monitoring
- JavaScript execution in the browser context

## Installation

### Installing 

```
npx playwright install firefox
yarn install 
npm run build 
```

## Configuration

The installation process will automatically add the following configuration to your Claude config file:

```json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "-y",
        "/Users/...../dist/index.js"
      ],
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

## Components

### Tools


#### `broser_url_reflected_xss`
Test whether the URL has an XSS vulnerability
```javascript
{
  "url": "https://test.com",
  "paramName":"text"
}
```
![](xss.png)


#### `browser_url_sql_injection`

Test whether the URL has SQL injection vulnerabilities

```javascript
{
  "url": "https://test.com",
  "paramName":"text"
}
```

![](sql.png)




#### `browser_navigate`
Navigate to any URL in the browser
```javascript
{
  "url": "https://stealthbrowser.cloud"
}
```

#### `browser_screenshot`
Capture screenshots of the entire page or specific elements
```javascript
{
  "name": "screenshot-name",     // required
  "selector": "#element-id",     // optional
  "fullPage": true              // optional, default: false
}
```

#### `browser_click`
Click elements on the page using CSS selector
```javascript
{
  "selector": "#button-id"
}
```

#### `browser_click_text`
Click elements on the page by their text content
```javascript
{
  "text": "Click me"
}
```

#### `browser_hover`
Hover over elements on the page using CSS selector
```javascript
{
  "selector": "#menu-item"
}
```

#### `browser_hover_text`
Hover over elements on the page by their text content
```javascript
{
  "text": "Hover me"
}
```

#### `browser_fill`
Fill out input fields
```javascript
{
  "selector": "#input-field",
  "value": "Hello World"
}
```

#### `browser_select`
Select an option in a SELECT element using CSS selector
```javascript
{
  "selector": "#dropdown",
  "value": "option-value"
}
```

#### `browser_select_text`
Select an option in a SELECT element by its text content
```javascript
{
  "text": "Choose me",
  "value": "option-value"
}
```

#### `browser_evaluate`
Execute JavaScript in the browser console
```javascript
{
  "script": "document.title"
}
```



```

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

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

```

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

```json
{
  "name": "@automatalabs/mcp-server-playwright",
  "version": "1.2.1",
  "description": "MCP server for browser automation using Playwright",
  "license": "MIT",
  "author": "Automata Labs (https://automatalabs.io)",
  "homepage": "https://automatalabs.io",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Automata-Labs-team/MCP-Server-Playwright.git"
  },
  "bugs": "https://github.com/Automata-Labs-team/MCP-Server-Playwright/issues",
  "type": "module",
  "bin": {
    "mcp-server-playwright": "dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.5.0",
    "playwright": "^1.48.0",
    "yargs": "^17.7.2"
  },
  "devDependencies": {
    "@types/node": "^22.10.2",
    "@types/yargs": "^17.0.33",
    "shx": "^0.3.4",
    "typescript": "^5.6.2"
  }
}
```

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

```typescript
#!/usr/bin/env node

import yargs from "yargs/yargs";
import { hideBin } from 'yargs/helpers'
import os from "os";
import path from "path";
import { promises as fs } from "fs";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
  CallToolResult,
  TextContent,
  ImageContent,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";
import playwright, { Browser, Page } from "playwright";

enum ToolName {
  BrowserNavigate = "browser_navigate",
  BrowserScreenshot = "browser_screenshot",
  BrowserClick = "browser_click",
  BrowserClickText = "browser_click_text",
  BrowserFill = "browser_fill",
  BrowserSelect = "browser_select",
  BrowserSelectText = "browser_select_text",
  BrowserHover = "browser_hover",
  BrowserHoverText = "browser_hover_text",
  BrowserEvaluate = "browser_evaluate",
  BrowserUrlReflectedXss = "broser_url_reflected_xss",
  BrowserUrlSqlInjection = "browser_url_sql_injection"
}

// Define the tools once to avoid repetition
const TOOLS: Tool[] = [
  {
    name: ToolName.BrowserNavigate,
    description: "Navigate to a URL",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string" },
      },
      required: ["url"],
    },
  },
  {
    name: ToolName.BrowserScreenshot,
    description: "Take a screenshot of the current page or a specific element",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Name for the screenshot" },
        selector: { type: "string", description: "CSS selector for element to screenshot" },
        fullPage: { type: "boolean", description: "Take a full page screenshot (default: false)", default: false },
      },
      required: ["name"],
    },
  },
  {
    name: ToolName.BrowserClick,
    description: "Click an element on the page using CSS selector",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for element to click" },
      },
      required: ["selector"],
    },
  },
  {
    name: ToolName.BrowserUrlReflectedXss,
    description: "Test whether the URL has an XSS vulnerability",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string" },
        paramName: { type: "string", description: "Parameter name for XSS testing" },
      },
      required: ["url"],
    },
  },
  {
    name: ToolName.BrowserClickText,
    description: "Click an element on the page by its text content",
    inputSchema: {
      type: "object",
      properties: {
        text: { type: "string", description: "Text content of the element to click" },
      },
      required: ["text"],
    },
  },
  {
    name: ToolName.BrowserFill,
    description: "Fill out an input field",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for input field" },
        value: { type: "string", description: "Value to fill" },
      },
      required: ["selector", "value"],
    },
  },
  {
    name: ToolName.BrowserSelect,
    description: "Select an element on the page with Select tag using CSS selector",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for element to select" },
        value: { type: "string", description: "Value to select" },
      },
      required: ["selector", "value"],
    },
  },
  {
    name: ToolName.BrowserSelectText,
    description: "Select an element on the page with Select tag by its text content",
    inputSchema: {
      type: "object",
      properties: {
        text: { type: "string", description: "Text content of the element to select" },
        value: { type: "string", description: "Value to select" },
      },
      required: ["text", "value"],
    },
  },
  {
    name: ToolName.BrowserHover,
    description: "Hover an element on the page using CSS selector",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for element to hover" },
      },
      required: ["selector"],
    },
  },
  {
    name: ToolName.BrowserHoverText,
    description: "Hover an element on the page by its text content",
    inputSchema: {
      type: "object",
      properties: {
        text: { type: "string", description: "Text content of the element to hover" },
      },
      required: ["text"],
    },
  },
  {
    name: ToolName.BrowserEvaluate,
    description: "Execute JavaScript in the browser console",
    inputSchema: {
      type: "object",
      properties: {
        script: { type: "string", description: "JavaScript code to execute" },
      },
      required: ["script"],
    },
  },
  {
    name: ToolName.BrowserUrlSqlInjection,
    description: "Test whether the URL has SQL injection vulnerabilities",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string" },
        paramName: { type: "string", description: "Parameter name for SQL injection testing" },
      },
      required: ["url"],
    },
  },
];

// Global state
let browser: Browser | undefined;
let page: Page | undefined;
const consoleLogs: string[] = [];
const screenshots = new Map<string, string>();

async function ensureBrowser() {
  if (!browser) {
    browser = await playwright.firefox.launch({ headless: false });
  }

  if (!page) {
    page = await browser.newPage();
  }

  page.on("console", (msg) => {
    const logEntry = `[${msg.type()}] ${msg.text()}`;
    consoleLogs.push(logEntry);
    server.notification({
      method: "notifications/resources/updated",
      params: { uri: "console://logs" },
    });
  });
  return page!;
}

async function handleToolCall(name: ToolName, args: any): Promise<CallToolResult> {
  const page = await ensureBrowser();

  switch (name) {
    case ToolName.BrowserNavigate:
      await page.goto(args.url);
      return {
        content: [{
          type: "text",
            text: `Navigated to ${args.url}`,
          }],
        isError: false,
      };

    case ToolName.BrowserScreenshot: {
      const fullPage = (args.fullPage === 'true');

      const screenshot = await (args.selector ?
        page.locator(args.selector).screenshot() :
        page.screenshot({ fullPage }));
      const base64Screenshot = screenshot.toString('base64');

      if (!base64Screenshot) {
        return {
          content: [{
            type: "text",
            text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed",
          }],
          isError: true,
        };
      }

      screenshots.set(args.name, base64Screenshot);
      server.notification({
        method: "notifications/resources/list_changed",
      });

      return {
        content: [
          {
            type: "text",
            text: `Screenshot '${args.name}' taken`,
          } as TextContent,
          {
            type: "image",
              data: base64Screenshot,
              mimeType: "image/png",
            } as ImageContent,
          ],
        isError: false,
      };
    }

    case ToolName.BrowserClick:
      try {
        await page.locator(args.selector).click();
        return {
          content: [{
            type: "text",
            text: `Clicked: ${args.selector}`,
          }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.locator(args.selector).first().click();
                return {
                    content: [{
                        type: "text",
                        text: `Clicked: ${args.selector}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to click ${args.selector}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        
        return {
          content: [{
            type: "text",
            text: `Failed to click ${args.selector}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }

    case ToolName.BrowserClickText:
      try {
        await page.getByText(args.text).click();
        return {
          content: [{
            type: "text",
            text: `Clicked element with text: ${args.text}`,
          }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.getByText(args.text).first().click();
                return {
                    content: [{
                        type: "text",
                        text: `Clicked element with text: ${args.text}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to click element with text ${args.text}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
            text: `Failed to click element with text ${args.text}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }

    case ToolName.BrowserFill:
      try {
        await page.locator(args.selector).pressSequentially(args.value, { delay: 100 });
        return {
          content: [{
            type: "text",
              text: `Filled ${args.selector} with: ${args.value}`,
            }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.locator(args.selector).first().pressSequentially(args.value, { delay: 100 });
                return {
                    content: [{
                        type: "text",
                        text: `Filled ${args.selector} with: ${args.value}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to fill ${args.selector}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
              text: `Failed to fill ${args.selector}: ${(error as Error).message}`,
            }],
          isError: true,
        };
      }

    case ToolName.BrowserSelect:
      try {
        await page.locator(args.selector).selectOption(args.value);
        return {
          content: [{
            type: "text",
              text: `Selected ${args.selector} with: ${args.value}`,
            }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.locator(args.selector).first().selectOption(args.value);
                return {
                    content: [{
                        type: "text",
                        text: `Selected ${args.selector} with: ${args.value}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to select ${args.selector}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
              text: `Failed to select ${args.selector}: ${(error as Error).message}`,
            }],
          isError: true,
        };
      }

    case ToolName.BrowserSelectText:
      try {
        await page.getByText(args.text).selectOption(args.value);
        return {
          content: [{
            type: "text",
            text: `Selected element with text ${args.text} with value: ${args.value}`,
          }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.getByText(args.text).first().selectOption(args.value);
                return {
                    content: [{
                        type: "text",
                        text: `Selected element with text ${args.text} with value: ${args.value}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to select element with text ${args.text}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
            text: `Failed to select element with text ${args.text}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }

    case ToolName.BrowserHover:
      try {
        await page.locator(args.selector).hover();
        return {
          content: [{
            type: "text",
              text: `Hovered ${args.selector}`,
            }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.locator(args.selector).first().hover();
                return {
                    content: [{
                        type: "text",
                        text: `Hovered ${args.selector}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
              text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
            }],
          isError: true,
        };
      }

    case ToolName.BrowserHoverText:
      try {
        await page.getByText(args.text).hover();
        return {
          content: [{
            type: "text",
            text: `Hovered element with text: ${args.text}`,
          }],
          isError: false,
        };
      } catch (error) {
        if((error as Error).message.includes("strict mode violation")) {
            console.log("Strict mode violation, retrying on first element...");
            try {
                await page.getByText(args.text).first().hover();
                return {
                    content: [{
                        type: "text",
                        text: `Hovered element with text: ${args.text}`,
                    }],
                    isError: false,
                };
            } catch (error) {
                return {
                    content: [{
                        type: "text",
                        text: `Failed (twice) to hover element with text ${args.text}: ${(error as Error).message}`,
                    }],
                    isError: true,
                };
            }
        }
        return {
          content: [{
            type: "text",
            text: `Failed to hover element with text ${args.text}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }

    case ToolName.BrowserUrlReflectedXss: {
      const baseUrl = args.url;
      const paramName = args.paramName || 'name';
      const xssPayloads = [
        "<script>alert(1)</script>",
        "\"><script>alert(1)</script>",
        "javascript:alert(1)",
        "<img src=x onerror=alert(1)>",
        "<svg onload=alert(1)>",
        "';alert(1);//"
      ];
      
      let vulnerablePayloads = [];
      
      for (const payload of xssPayloads) {
        const encodedPayload = encodeURIComponent(payload);
        const testUrl = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${paramName}=${encodedPayload}`;
        
        try {
          await page.goto(testUrl);
          
          // 检查页面源代码中是否包含未编码的payload
          const content = await page.content();
          const decodedPayload = decodeURIComponent(payload);
          
          if (content.includes(decodedPayload)) {
            vulnerablePayloads.push({
              payload: payload,
              url: testUrl
            });
          }
          
          // 检查是否有JavaScript执行
          const hasXss = await page.evaluate((testPayload) => {
            return document.documentElement.innerHTML.includes(testPayload);
          }, payload);
          
          if (hasXss) {
            vulnerablePayloads.push({
              payload: payload,
              url: testUrl
            });
          }
        } catch (error) {
          console.error(`Error testing payload ${payload}: ${error}`);
        }
      }
      
      if (vulnerablePayloads.length > 0) {
        return {
          content: [{
            type: "text",
            text: `发现反射型XSS漏洞!\n\n可利用的Payload:\n${vulnerablePayloads.map(v => 
              `Payload: ${v.payload}\nURL: ${v.url}\n`
            ).join('\n')}`
          }],
          isError: false
        };
      } else {
        return {
          content: [{
            type: "text",
            text: "未发现明显的反射型XSS漏洞。"
          }],
          isError: false
        };
      }
    }

    case ToolName.BrowserUrlSqlInjection: {
      const baseUrl = args.url;
      const paramName = args.paramName || 'id';
      const sqlPayloads = [
        "1' OR '1'='1",
        "1' OR '1'='1' --",
        "1' OR '1'='1' #",
        "1; DROP TABLE users--",
        "1 UNION SELECT null,null,null--",
        "1' UNION SELECT null,null,null--",
        "admin' --",
        "admin' #",
        "' OR 1=1--",
        "' OR 'x'='x",
        "1' AND SLEEP(5)--",
        "1' AND BENCHMARK(5000000,MD5(1))--",
        "1' WAITFOR DELAY '0:0:5'--"
      ];
      
      let vulnerablePayloads = [];
      let originalResponse = '';
      
      try {
        // 首先获取原始响应
        await page.goto(baseUrl);
        originalResponse = await page.content();
      } catch (error) {
        console.error(`Error getting original response: ${error}`);
      }
      
      for (const payload of sqlPayloads) {
        const encodedPayload = encodeURIComponent(payload);
        const testUrl = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}${paramName}=${encodedPayload}`;
        
        try {
          const startTime = Date.now();
          await page.goto(testUrl);
          const endTime = Date.now();
          const responseTime = endTime - startTime;
          
          const newResponse = await page.content();
          
          // 检查SQL错误关键字
          const sqlErrorPatterns = [
            /SQL syntax/i,
            /MySQL/i,
            /ORA-[0-9][0-9][0-9][0-9]/,
            /PostgreSQL/i,
            /SQLite/i,
            /SQLSTATE/,
            /Microsoft SQL/i,
            /ODBC Driver/i,
            /DB2 SQL/i,
            /Warning.*mysql_/i,
            /Warning.*pg_/i,
            /Warning.*sqlite_/i
          ];
          
          const hasError = sqlErrorPatterns.some(pattern => pattern.test(newResponse));
          
          // 检查响应差异
          const isDifferent = originalResponse !== newResponse;
          
          // 检查时间延迟(针对基于时间的注入)
          const hasTimeDelay = responseTime > 5000 && payload.toLowerCase().includes('sleep') || 
                             payload.toLowerCase().includes('benchmark') || 
                             payload.toLowerCase().includes('waitfor');
          
          if (hasError || isDifferent || hasTimeDelay) {
            vulnerablePayloads.push({
              payload: payload,
              url: testUrl,
              reason: [
                hasError ? '发现SQL错误信息' : '',
                isDifferent ? '响应内容发生变化' : '',
                hasTimeDelay ? '发现时间延迟' : ''
              ].filter(Boolean).join(', ')
            });
          }
        } catch (error) {
          console.error(`Error testing payload ${payload}: ${error}`);
        }
      }
      
      if (vulnerablePayloads.length > 0) {
        return {
          content: [{
            type: "text",
            text: `发现潜在的SQL注入漏洞!\n\n可能的漏洞点:\n${vulnerablePayloads.map(v => 
              `Payload: ${v.payload}\nURL: ${v.url}\n原因: ${v.reason}\n`
            ).join('\n')}`
          }],
          isError: false
        };
      } else {
        return {
          content: [{
            type: "text",
            text: "未发现明显的SQL注入漏洞。"
          }],
          isError: false
        };
      }
    }

    case ToolName.BrowserEvaluate:
      try {
        const result = await page.evaluate((script) => {
          const logs: string[] = [];
          const originalConsole = { ...console };

          ['log', 'info', 'warn', 'error'].forEach(method => {
            (console as any)[method] = (...args: any[]) => { 
              logs.push(`[${method}] ${args.join(' ')}`);
              (originalConsole as any)[method](...args);
            };
          });

          try {
            const result = eval(script);
            Object.assign(console, originalConsole);
            return { result, logs };
          } catch (error) {
            Object.assign(console, originalConsole);
            throw error;
          }
        }, args.script);

        return {
          content: [
            {
                type: "text",
                text: `Execution result:\n${JSON.stringify(result.result, null, 2)}\n\nConsole output:\n${result.logs.join('\n')}`,
              },
            ],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
              text: `Script execution failed: ${(error as Error).message}`,
            }],
          isError: true,
        };
      }

    default:
      return {
        content: [{
          type: "text",
            text: `Unknown tool: ${name}`,
          }],
        isError: true,
      };
  }
}

const server = new Server(
  {
    name: "automatalabs/playwright",
    version: "0.1.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
    },
  },
);


// Setup request handlers
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "console://logs",
      mimeType: "text/plain",
      name: "Browser console logs",
    },
    ...Array.from(screenshots.keys()).map(name => ({
      uri: `screenshot://${name}`,
      mimeType: "image/png",
      name: `Screenshot: ${name}`,
    })),
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const uri = request.params.uri.toString();

  if (uri === "console://logs") {
    return {
      contents: [{
        uri,
        mimeType: "text/plain",
        text: consoleLogs.join("\n"),
      }],
    };
  }

  if (uri.startsWith("screenshot://")) {
    const name = uri.split("://")[1];
    const screenshot = screenshots.get(name);
    if (screenshot) {
      return {
        contents: [{
          uri,
          mimeType: "image/png",
          blob: screenshot,
        }],
      };
    }
  }

  throw new Error(`Resource not found: ${uri}`);
});



async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);

  server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools: TOOLS,
  }));
  
  server.setRequestHandler(CallToolRequestSchema, async (request) =>
    handleToolCall(request.params.name as ToolName, request.params.arguments ?? {})
  );
}

async function checkPlatformAndInstall() {
  const platform = os.platform();
  if (platform === "win32") {
    console.log("Installing MCP Playwright Server for Windows...");
    try {
      const configFilePath = path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
      
      let config: any;
      try {
        // Try to read existing config file
        const fileContent = await fs.readFile(configFilePath, 'utf-8');
        config = JSON.parse(fileContent);
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
          // Create new config file with mcpServers object
          config = { mcpServers: {} };
          await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), 'utf-8');
          console.log("Created new Claude config file");
        } else {
          console.error("Error reading Claude config file:", error);
          process.exit(1);
        }
      }

      // Ensure mcpServers exists
      if (!config.mcpServers) {
        config.mcpServers = {};
      }

      // Update the playwright configuration
      config.mcpServers.playwright = {
        command: "npx",
        args: ["-y", "@automatalabs/mcp-server-playwright"]
      };

      // Write the updated config back to file
      await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), 'utf-8');
      console.log("✓ Successfully updated Claude configuration");
      
    } catch (error) {
      console.error("Error during installation:", error);
      process.exit(1);
    }
  } else if (platform === "darwin") {
    console.log("Installing MCP Playwright Server for macOS...");
    try {
      const configFilePath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
      
      let config: any;
      try {
        // Try to read existing config file
        const fileContent = await fs.readFile(configFilePath, 'utf-8');
        config = JSON.parse(fileContent);
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
          // Create new config file with mcpServers object
          config = { mcpServers: {} };
          await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), 'utf-8');
          console.log("Created new Claude config file");
        } else {
          console.error("Error reading Claude config file:", error);
          process.exit(1);
        }
      }

      // Ensure mcpServers exists
      if (!config.mcpServers) {
        config.mcpServers = {};
      }

      // Update the playwright configuration
      config.mcpServers.playwright = {
        command: "npx",
        args: ["-y", "@automatalabs/mcp-server-playwright"]
      };

      // Write the updated config back to file
      await fs.writeFile(configFilePath, JSON.stringify(config, null, 2), 'utf-8');
      console.log("✓ Successfully updated Claude configuration");
      
    } catch (error) {
      console.error("Error during installation:", error);
      process.exit(1);
    }
  } else {
    console.error("Unsupported platform:", platform);
    process.exit(1);
  }
}

(async () => {
  try {
    // Parse args but continue with server if no command specified
    await yargs(hideBin(process.argv))
      .command('install', 'Install MCP-Server-Playwright dependencies', () => {}, async () => {
        await checkPlatformAndInstall();
        // Exit after successful installation
        process.exit(0);
      })
      .strict()
      .help()
      .parse();

    // If we get here, no command was specified, so run the server
    await runServer().catch(console.error);
  } catch (error) {
    console.error('Error:', error);
    process.exit(1);
  }
})();

```